summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/blobstore/Android.bp2
-rw-r--r--apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java2
-rw-r--r--apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java2
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java4
-rw-r--r--apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java40
-rw-r--r--apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl1
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java19
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java8
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java5
-rw-r--r--boot/boot-image-profile.txt4
-rw-r--r--boot/preloaded-classes4
-rw-r--r--cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java76
-rw-r--r--cmds/bootanimation/BootAnimation.cpp13
-rw-r--r--config/boot-image-profile.txt4
-rw-r--r--config/preloaded-classes4
-rw-r--r--core/api/current.txt251
-rw-r--r--core/api/system-current.txt22
-rw-r--r--core/api/test-current.txt101
-rw-r--r--core/java/android/app/ActivityManagerInternal.java165
-rw-r--r--core/java/android/app/AppOpsManager.java59
-rw-r--r--core/java/android/app/ForegroundServiceTypePolicy.java1
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/IWallpaperManager.aidl14
-rw-r--r--core/java/android/app/Instrumentation.java3
-rw-r--r--core/java/android/app/Notification.java16
-rw-r--r--core/java/android/app/NotificationManager.java19
-rw-r--r--core/java/android/app/PendingIntent.java37
-rw-r--r--core/java/android/app/TEST_MAPPING3
-rw-r--r--core/java/android/app/UiAutomation.java132
-rw-r--r--core/java/android/app/UiAutomationConnection.java11
-rw-r--r--core/java/android/app/admin/DevicePolicyResources.java8
-rw-r--r--core/java/android/app/admin/IntentFilterPolicyKey.java28
-rw-r--r--core/java/android/content/pm/UserInfo.java12
-rw-r--r--core/java/android/content/res/AssetFileDescriptor.java234
-rw-r--r--core/java/android/credentials/CredentialOption.java6
-rw-r--r--core/java/android/credentials/ui/CreateCredentialProviderData.java13
-rw-r--r--core/java/android/credentials/ui/GetCredentialProviderData.java41
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java24
-rw-r--r--core/java/android/hardware/biometrics/ComponentInfoInternal.java23
-rw-r--r--core/java/android/hardware/biometrics/IBiometricService.aidl4
-rw-r--r--core/java/android/hardware/camera2/CameraDevice.java4
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java10
-rw-r--r--core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl30
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java36
-rw-r--r--core/java/android/inputmethodservice/InkWindow.java3
-rw-r--r--core/java/android/nfc/tech/NdefFormatable.java3
-rwxr-xr-xcore/java/android/os/Build.java4
-rw-r--r--core/java/android/os/UserManager.java11
-rw-r--r--core/java/android/os/image/DynamicSystemClient.java20
-rw-r--r--core/java/android/permission/OWNERS9
-rw-r--r--core/java/android/provider/Settings.java230
-rw-r--r--core/java/android/provider/Telephony.java48
-rw-r--r--core/java/android/service/credentials/BeginCreateCredentialResponse.java21
-rw-r--r--core/java/android/service/credentials/BeginGetCredentialResponse.java60
-rw-r--r--core/java/android/service/credentials/CredentialProviderInfoFactory.java38
-rw-r--r--core/java/android/service/credentials/CredentialProviderService.java2
-rw-r--r--core/java/android/service/dreams/DreamManagerInternal.java4
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java176
-rw-r--r--core/java/android/service/notification/ZenModeDiff.java542
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java16
-rw-r--r--core/java/android/service/voice/SoftwareHotwordDetector.java15
-rw-r--r--core/java/android/service/voice/SoundTriggerFailure.java21
-rw-r--r--core/java/android/service/voice/VisualQueryDetector.java11
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java54
-rw-r--r--core/java/android/service/wallpaper/IWallpaperEngine.aidl2
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java113
-rw-r--r--core/java/android/util/FeatureFlagUtils.java7
-rw-r--r--core/java/android/util/TimeUtils.java9
-rw-r--r--core/java/android/view/Choreographer.java27
-rw-r--r--core/java/android/view/InputEvent.java3
-rw-r--r--core/java/android/view/InputWindowHandle.java1
-rw-r--r--core/java/android/view/KeyEvent.java1
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java47
-rw-r--r--core/java/android/view/ViewRootImpl.java1
-rw-r--r--core/java/android/view/WindowlessWindowManager.java18
-rw-r--r--core/java/android/view/accessibility/IAccessibilityManager.aidl2
-rw-r--r--core/java/android/view/translation/Translator.java3
-rw-r--r--core/java/android/view/translation/UiTranslationController.java26
-rw-r--r--core/java/android/widget/Editor.java8
-rw-r--r--core/java/android/widget/RemoteViews.java15
-rw-r--r--core/java/android/window/BackMotionEvent.java53
-rw-r--r--core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl17
-rw-r--r--core/java/com/android/internal/app/ISoundTriggerService.aidl7
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl16
-rw-r--r--core/java/com/android/internal/app/UnlaunchableAppActivity.java11
-rw-r--r--core/java/com/android/internal/expresslog/Counter.java71
-rw-r--r--core/java/com/android/internal/expresslog/Histogram.java234
-rw-r--r--core/java/com/android/internal/expresslog/OWNERS1
-rw-r--r--core/java/com/android/internal/expresslog/TEST_MAPPING12
-rw-r--r--core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java2
-rw-r--r--core/java/com/android/internal/util/DumpUtils.java83
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp4
-rw-r--r--core/jni/OWNERS3
-rw-r--r--core/jni/android_media_AudioSystem.cpp49
-rw-r--r--core/jni/com_android_internal_expresslog_Utils.cpp57
-rw-r--r--core/proto/android/server/activitymanagerservice.proto1
-rw-r--r--core/res/AndroidManifest.xml31
-rw-r--r--core/res/res/values/config.xml21
-rw-r--r--core/res/res/values/public-final.xml339
-rw-r--r--core/res/res/values/public-staging.xml204
-rw-r--r--core/res/res/values/strings.xml20
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--core/res/res/values/themes_device_defaults.xml278
-rw-r--r--core/res/res/xml/irq_device_map.xml16
-rw-r--r--core/tests/coretests/src/android/app/OWNERS3
-rw-r--r--core/tests/coretests/src/android/app/UiAutomationTest.java144
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java4
-rw-r--r--core/tests/coretests/src/android/content/res/TEST_MAPPING13
-rw-r--r--core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java102
-rw-r--r--core/tests/coretests/src/android/hardware/biometrics/OWNERS1
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java8
-rw-r--r--core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java149
-rw-r--r--core/tests/expresslog/OWNERS3
-rw-r--r--core/tests/expresslog/TEST_MAPPING12
-rw-r--r--core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java167
-rw-r--r--core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java125
-rw-r--r--data/etc/preinstalled-packages-platform-overlays.xml4
-rw-r--r--data/etc/services.core.protolog.json90
-rw-r--r--data/keyboards/GoogleTV-Remote.idc25
-rw-r--r--graphics/java/android/graphics/Bitmap.java4
-rw-r--r--graphics/java/android/graphics/BitmapFactory.java4
-rw-r--r--graphics/java/android/graphics/BitmapShader.java3
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java41
-rw-r--r--identity/java/android/security/identity/CredstoreIdentityCredentialStore.java16
-rw-r--r--keystore/java/android/security/GenerateRkpKey.java159
-rw-r--r--keystore/java/android/security/IGenerateRkpKeyService.aidl60
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java108
-rw-r--r--libs/WindowManager/Jetpack/Android.bp18
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java10
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-core-release.aarbin3714 -> 0 bytes
-rw-r--r--libs/WindowManager/Jetpack/window-extensions-release.aarbin41055 -> 0 bytes
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java107
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java88
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java87
-rw-r--r--libs/WindowManager/Shell/tests/flicker/AndroidTest.xml1
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java25
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java54
-rw-r--r--libs/hwui/RecordingCanvas.cpp17
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.cpp33
-rw-r--r--libs/hwui/hwui/AnimatedImageDrawable.h17
-rw-r--r--libs/hwui/jni/AnimatedImageDrawable.cpp6
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp36
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h8
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp24
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.h2
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp9
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.h4
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp6
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp9
-rw-r--r--libs/hwui/renderthread/CanvasContext.h3
-rw-r--r--libs/hwui/renderthread/EglManager.cpp20
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h2
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp40
-rw-r--r--libs/hwui/renderthread/VulkanSurface.cpp33
-rw-r--r--libs/hwui/renderthread/VulkanSurface.h4
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerDetector.java43
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java630
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerManager.java25
-rw-r--r--media/java/android/media/voice/KeyphraseModelManager.java21
-rw-r--r--media/jni/android_media_MediaCodecLinearBlock.h9
-rw-r--r--media/jni/android_media_MediaExtractor.cpp11
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml8
-rw-r--r--packages/CredentialManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt18
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt10
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt12
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt64
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt38
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt18
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt34
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt9
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt1
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt55
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt3
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt3
-rw-r--r--packages/DynamicSystemInstallationService/AndroidManifest.xml4
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java36
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java4
-rw-r--r--packages/PrintSpooler/tests/outofprocess/Android.bp2
-rw-r--r--packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java12
-rw-r--r--packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java38
-rw-r--r--packages/SettingsLib/OWNERS3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt4
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt57
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt8
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt2
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt163
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt12
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt6
-rw-r--r--packages/SettingsLib/res/drawable/ic_dock_device.xml26
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java25
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java53
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java34
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java2
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java19
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java51
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java29
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java231
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java14
-rw-r--r--packages/Shell/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/AndroidManifest.xml17
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml29
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java9
-rw-r--r--packages/SystemUI/common/.gitignore9
-rw-r--r--packages/SystemUI/common/Android.bp (renamed from core/tests/expresslog/Android.bp)26
-rw-r--r--packages/SystemUI/common/AndroidManifest.xml (renamed from core/tests/expresslog/AndroidManifest.xml)14
-rw-r--r--packages/SystemUI/common/OWNERS2
-rw-r--r--packages/SystemUI/common/README.md5
-rw-r--r--packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt (renamed from packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt)2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt9
-rw-r--r--packages/SystemUI/plugin/Android.bp1
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt3
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt3
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt2
-rw-r--r--packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml165
-rw-r--r--packages/SystemUI/res-keyguard/drawable/ic_palette.xml25
-rw-r--r--packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml5
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml74
-rw-r--r--packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml22
-rw-r--r--packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml49
-rw-r--r--packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml39
-rw-r--r--packages/SystemUI/res/drawable/chipbar_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/chipbar_end_button_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/hearing.xml24
-rw-r--r--packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml (renamed from packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml)0
-rw-r--r--packages/SystemUI/res/drawable/ic_shade_sim_card.xml (renamed from packages/SystemUI/res/drawable/ic_qs_sim_card.xml)0
-rw-r--r--packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml8
-rw-r--r--packages/SystemUI/res/layout/chipbar.xml12
-rw-r--r--packages/SystemUI/res/layout/combined_qs_header.xml2
-rw-r--r--packages/SystemUI/res/layout/keyguard_bottom_area.xml74
-rw-r--r--packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml21
-rw-r--r--packages/SystemUI/res/layout/shade_carrier.xml (renamed from packages/SystemUI/res/layout/qs_carrier.xml)6
-rw-r--r--packages/SystemUI/res/layout/shade_carrier_group.xml (renamed from packages/SystemUI/res/layout/qs_carrier_group.xml)14
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml12
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml8
-rw-r--r--packages/SystemUI/res/values/integers.xml2
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/res/values/styles.xml14
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java29
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java220
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java269
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java95
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt140
-rw-r--r--packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java128
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt116
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt (renamed from core/java/com/android/internal/expresslog/Utils.java)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt175
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt200
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt547
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt206
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt202
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt344
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java194
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt)11
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java (renamed from packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java)20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java (renamed from packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java (renamed from packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java)30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java)33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java185
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt300
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt93
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt (renamed from packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java4
-rw-r--r--packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java174
-rw-r--r--packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java275
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt)9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt255
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt119
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java58
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java174
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt292
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt159
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt674
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt)8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java)84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java)56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java76
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt81
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt78
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt176
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt163
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt210
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt379
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt395
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java18
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt63
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt61
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt22
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt6
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt7
-rw-r--r--packages/overlays/Android.mk1
-rw-r--r--packages/overlays/NotesRoleEnabledOverlay/Android.bp30
-rw-r--r--packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml26
-rw-r--r--packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml28
-rw-r--r--packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml22
-rw-r--r--proto/src/system_messages.proto2
-rw-r--r--services/Android.bp2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java122
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java7
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java38
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java29
-rw-r--r--services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java15
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java39
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java5
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java115
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/android/os/BatteryStatsInternal.java2
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java2
-rw-r--r--services/core/java/com/android/server/DockObserver.java8
-rw-r--r--services/core/java/com/android/server/OWNERS1
-rw-r--r--services/core/java/com/android/server/PackageWatchdog.java30
-rw-r--r--services/core/java/com/android/server/RescueParty.java19
-rw-r--r--services/core/java/com/android/server/SoundTriggerInternal.java (renamed from services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java)6
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java184
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java85
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java117
-rw-r--r--services/core/java/com/android/server/am/AppErrors.java9
-rw-r--r--services/core/java/com/android/server/am/AppExitInfoTracker.java175
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java8
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java215
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java120
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java126
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java231
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java12
-rw-r--r--services/core/java/com/android/server/am/DropboxRateLimiter.java2
-rw-r--r--services/core/java/com/android/server/am/EventLogTags.logtags3
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java94
-rw-r--r--services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java26
-rw-r--r--services/core/java/com/android/server/am/ProcessErrorStateRecord.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java17
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java8
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java5
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java4
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java2
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java24
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java5
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java5
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricSensor.java37
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java22
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java6
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java16
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java8
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java21
-rw-r--r--services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java4
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java13
-rw-r--r--services/core/java/com/android/server/inputmethod/OWNERS8
-rw-r--r--services/core/java/com/android/server/locales/LocaleManagerService.java11
-rw-r--r--services/core/java/com/android/server/locales/SystemAppUpdateTracker.java4
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java15
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsService.java19
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java27
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java241
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java164
-rw-r--r--services/core/java/com/android/server/notification/ZenLog.java7
-rw-r--r--services/core/java/com/android/server/pm/DynamicCodeLoggingService.java2
-rw-r--r--services/core/java/com/android/server/pm/IPackageManagerBase.java1
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java9
-rw-r--r--services/core/java/com/android/server/pm/ReconcilePackageUtils.java57
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java21
-rw-r--r--services/core/java/com/android/server/pm/VerifyingSession.java3
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java2
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java21
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java23
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java29
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java18
-rw-r--r--services/core/java/com/android/server/power/ShutdownCheckPoints.java17
-rw-r--r--services/core/java/com/android/server/power/ShutdownThread.java15
-rw-r--r--services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java (renamed from services/core/java/com/android/server/power/stats/CpuWakeupStats.java)64
-rw-r--r--services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java (renamed from services/core/java/com/android/server/power/stats/IrqDeviceMap.java)2
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java48
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java58
-rw-r--r--services/core/java/com/android/server/wm/AbsAppSnapshotController.java3
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java17
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java33
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java57
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java42
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java72
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java24
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java113
-rw-r--r--services/core/java/com/android/server/wm/Dimmer.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayArea.java3
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java12
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java62
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java34
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotationReversionController.java148
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java29
-rw-r--r--services/core/java/com/android/server/wm/RecentTasks.java2
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java8
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java18
-rw-r--r--services/core/java/com/android/server/wm/Task.java48
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java86
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java102
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java28
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java110
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/ClearRequestSession.java11
-rw-r--r--services/credentials/java/com/android/server/credentials/CreateRequestSession.java17
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerService.java2
-rw-r--r--services/credentials/java/com/android/server/credentials/CredentialManagerUi.java4
-rw-r--r--services/credentials/java/com/android/server/credentials/GetRequestSession.java18
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java10
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderClearSession.java5
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderCreateSession.java28
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderGetSession.java63
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java23
-rw-r--r--services/credentials/java/com/android/server/credentials/ProviderSession.java10
-rw-r--r--services/credentials/java/com/android/server/credentials/RemoteCredentialService.java21
-rw-r--r--services/credentials/java/com/android/server/credentials/RequestSession.java36
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java15
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java18
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java23
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java257
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java605
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java14
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java22
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java14
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java25
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java76
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java12
-rw-r--r--services/java/com/android/server/SystemServer.java10
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java149
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java288
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java238
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java34
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java20
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java100
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java112
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java112
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java92
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java93
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java104
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java103
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java20
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java (renamed from services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java)103
-rw-r--r--services/tests/servicestests/res/xml/irq_device_map_3.xml3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java90
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java17
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java14
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java36
-rw-r--r--services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java30
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java (renamed from services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java)76
-rw-r--r--services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java (renamed from services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java)2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java493
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java83
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java35
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java319
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java19
-rw-r--r--services/tests/voiceinteractiontests/Android.bp1
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java335
-rw-r--r--services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java2
-rw-r--r--services/tests/wmtests/AndroidManifest.xml2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java24
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java46
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java46
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java194
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java45
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java5
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaDevice.java256
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java3
-rw-r--r--services/usb/java/com/android/server/usb/UsbAlsaManager.java147
-rw-r--r--services/voiceinteraction/Android.bp51
-rw-r--r--services/voiceinteraction/TEST_MAPPING3
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java62
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java54
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java205
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java3
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java26
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java61
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java90
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java148
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java73
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java42
-rw-r--r--telecomm/java/android/telecom/CallAttributes.java5
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java95
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java4
-rw-r--r--telephony/java/android/telephony/satellite/AntennaDirection.aidl (renamed from keystore/java/android/security/GenerateRkpKeyException.java)20
-rw-r--r--telephony/java/android/telephony/satellite/AntennaDirection.java138
-rw-r--r--telephony/java/android/telephony/satellite/AntennaPosition.aidl19
-rw-r--r--telephony/java/android/telephony/satellite/AntennaPosition.java117
-rw-r--r--telephony/java/android/telephony/satellite/PointingInfo.java3
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteCapabilities.java70
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteDatagram.java2
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java2
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java99
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java3
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteStateCallback.java3
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java4
-rw-r--r--telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl12
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl10
-rw-r--r--tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java4
-rw-r--r--tests/FlickerTests/AndroidTest.xml1
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt2
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt11
-rw-r--r--tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java116
-rw-r--r--tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java99
-rw-r--r--tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java16
-rw-r--r--tests/Input/Android.bp2
-rw-r--r--tests/Input/src/com/android/test/input/AnrTest.kt9
-rw-r--r--tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java46
-rw-r--r--tests/SilkFX/res/drawable-nodpi/dark_gradient.xml2
-rw-r--r--tests/WindowAnimationJank/Android.bp2
-rw-r--r--tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java11
-rw-r--r--tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java3
-rw-r--r--tests/testables/src/android/testing/TestableSettingsProvider.java6
-rw-r--r--tests/testables/tests/AndroidManifest.xml2
-rw-r--r--tests/testables/tests/AndroidTest.xml27
-rw-r--r--tools/aapt/SdkConstants.h1
-rw-r--r--tools/aapt2/SdkConstants.h1
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java12
-rw-r--r--wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java8
-rw-r--r--wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java7
836 files changed, 24509 insertions, 10021 deletions
diff --git a/Android.bp b/Android.bp
index cff863b44499..64d2c66f013c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -410,6 +410,7 @@ java_defaults {
"spatializer-aidl-java",
"audiopolicy-aidl-java",
"sounddose-aidl-java",
+ "modules-utils-expresslog",
],
}
diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp
index 9064b4494ed1..2590fe3d843f 100644
--- a/apct-tests/perftests/blobstore/Android.bp
+++ b/apct-tests/perftests/blobstore/Android.bp
@@ -29,7 +29,7 @@ android_test {
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
- "ub-uiautomator",
+ "androidx.test.uiautomator_uiautomator",
"collector-device-lib-platform",
"androidx.benchmark_benchmark-macro",
],
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
index 0208dab33746..4e4780ff8948 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java
@@ -21,10 +21,10 @@ import android.app.UiAutomation;
import android.os.ParcelFileDescriptor;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
-import android.support.test.uiautomator.UiDevice;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import java.io.BufferedReader;
import java.io.IOException;
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
index 03e5468a342a..3cd9f50aee6a 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -25,12 +25,12 @@ import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.PerfManualStatusReporter;
import android.perftests.utils.TraceMarkParser;
import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
-import android.support.test.uiautomator.UiDevice;
import android.util.DataUnit;
import androidx.benchmark.macro.MacrobenchmarkScope;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
import com.android.utils.blob.FakeBlobData;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
index f56e1eea0f23..36174c6aad3d 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java
@@ -20,6 +20,7 @@ import android.annotation.SystemApi;
import android.app.JobSchedulerImpl;
import android.app.SystemServiceRegistry;
import android.app.tare.EconomyManager;
+import android.app.tare.IEconomyManager;
import android.content.Context;
import android.os.DeviceIdleManager;
import android.os.IDeviceIdleController;
@@ -58,6 +59,7 @@ public class JobSchedulerFrameworkInitializer {
Context.POWER_EXEMPTION_SERVICE, PowerExemptionManager.class,
PowerExemptionManager::new);
SystemServiceRegistry.registerStaticService(
- Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class, EconomyManager::new);
+ Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class,
+ (b) -> new EconomyManager(IEconomyManager.Stub.asInterface(b)));
}
}
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 581ea7adc465..0bea028e6f50 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -19,7 +19,9 @@ package android.app.tare;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.Context;
+import android.os.RemoteException;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -30,6 +32,7 @@ import java.lang.annotation.RetentionPolicy;
*
* @hide
*/
+@TestApi
@SystemService(Context.RESOURCE_ECONOMY_SERVICE)
public class EconomyManager {
private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName();
@@ -95,13 +98,17 @@ public class EconomyManager {
}
}
-
+ /** @hide */
+ @TestApi
public static final int ENABLED_MODE_OFF = 0;
+ /** @hide */
public static final int ENABLED_MODE_ON = 1;
/**
* Go through the motions, tracking events, updating balances and other TARE state values,
* but don't use TARE to affect actual device behavior.
+ * @hide
*/
+ @TestApi
public static final int ENABLED_MODE_SHADOW = 2;
/** @hide */
@@ -114,6 +121,7 @@ public class EconomyManager {
public @interface EnabledMode {
}
+ /** @hide */
public static String enabledModeToString(@EnabledMode int mode) {
switch (mode) {
case ENABLED_MODE_OFF: return "ENABLED_MODE_OFF";
@@ -123,11 +131,18 @@ public class EconomyManager {
}
}
+ /** @hide */
+ @TestApi
public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+ /** @hide */
public static final String KEY_ENABLE_POLICY_ALARM = "enable_policy_alarm";
+ /** @hide */
public static final String KEY_ENABLE_POLICY_JOB_SCHEDULER = "enable_policy_job";
+ /** @hide */
public static final int DEFAULT_ENABLE_TARE_MODE = ENABLED_MODE_OFF;
+ /** @hide */
public static final boolean DEFAULT_ENABLE_POLICY_ALARM = true;
+ /** @hide */
public static final boolean DEFAULT_ENABLE_POLICY_JOB_SCHEDULER = true;
// Keys for AlarmManager TARE factors
@@ -612,4 +627,27 @@ public class EconomyManager {
public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1);
/** @hide */
public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60);
+
+ //////// APIs below ////////
+
+ private final IEconomyManager mService;
+
+ /** @hide */
+ public EconomyManager(IEconomyManager service) {
+ mService = service;
+ }
+
+ /**
+ * Returns the current enabled status of TARE.
+ * @hide
+ */
+ @EnabledMode
+ @TestApi
+ public int getEnabledMode() {
+ try {
+ return mService.getEnabledMode();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
index bb150118ebb1..2be0db7a4c9f 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
+++ b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl
@@ -21,4 +21,5 @@ package android.app.tare;
* {@hide}
*/
interface IEconomyManager {
+ int getEnabledMode();
}
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 0650ce3d141c..f779b4d96b45 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -355,7 +355,7 @@ public class DeviceIdleController extends SystemService
@GuardedBy("this")
private boolean mHasGps;
@GuardedBy("this")
- private boolean mHasNetworkLocation;
+ private boolean mHasFusedLocation;
@GuardedBy("this")
private Location mLastGenericLocation;
@GuardedBy("this")
@@ -3782,12 +3782,14 @@ public class DeviceIdleController extends SystemService
scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT);
LocationManager locationManager = mInjector.getLocationManager();
if (locationManager != null
- && locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
- locationManager.requestLocationUpdates(mLocationRequest,
- mGenericLocationListener, mHandler.getLooper());
+ && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) {
+ locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER,
+ mLocationRequest,
+ AppSchedulingModuleThread.getExecutor(),
+ mGenericLocationListener);
mLocating = true;
} else {
- mHasNetworkLocation = false;
+ mHasFusedLocation = false;
}
if (locationManager != null
&& locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
@@ -5301,9 +5303,10 @@ public class DeviceIdleController extends SystemService
pw.print(" "); pw.print(mStationaryListeners.size());
pw.println(" stationary listeners registered");
}
- pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
- pw.print(mHasGps); pw.print(" mHasNetwork=");
- pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated);
+ pw.print(" mLocating="); pw.print(mLocating);
+ pw.print(" mHasGps="); pw.print(mHasGps);
+ pw.print(" mHasFused="); pw.print(mHasFusedLocation);
+ pw.print(" mLocated="); pw.println(mLocated);
if (mLastGenericLocation != null) {
pw.print(" mLastGenericLocation="); pw.println(mLastGenericLocation);
}
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 2588cd0dbb1c..6eeff82f1158 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1588,12 +1588,12 @@ public class JobSchedulerService extends com.android.server.SystemService
final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
- // Write out for loop to avoid addAll() creating an Iterator.
+ // Write out for loop to avoid creating an Iterator.
for (int i = jobs.size() - 1; i >= 0; i--) {
final JobStatus job = jobs.valueAt(i);
List<JobInfo> outList = outMap.get(job.getNamespace());
if (outList == null) {
- outList = new ArrayList<JobInfo>(jobs.size());
+ outList = new ArrayList<>();
outMap.put(job.getNamespace(), outList);
}
@@ -1606,7 +1606,7 @@ public class JobSchedulerService extends com.android.server.SystemService
private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
synchronized (mLock) {
ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
- ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
+ ArrayList<JobInfo> outList = new ArrayList<>();
// Write out for loop to avoid addAll() creating an Iterator.
for (int i = jobs.size() - 1; i >= 0; i--) {
final JobStatus job = jobs.valueAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index fc6022859f5f..ba62e96b2a32 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -31,7 +31,7 @@ import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index eb43c38f76a3..ef634b565b65 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -92,13 +92,17 @@ public class ThermalStatusRestriction extends JobRestriction {
final int priority = job.getEffectivePriority();
if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
// For moderate throttling:
- // Only let expedited & user-initiated jobs run if:
+ // Let all user-initiated jobs run.
+ // Only let expedited jobs run if:
// 1. They haven't previously run
// 2. They're already running and aren't yet in overtime
// Only let high priority jobs run if:
// They are already running and aren't yet in overtime
// Don't let any other job run.
- if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) {
+ if (job.shouldTreatAsUserInitiatedJob()) {
+ return false;
+ }
+ if (job.shouldTreatAsExpeditedJob()) {
return job.getNumPreviousAttempts() > 0
|| (mService.isCurrentlyRunningLocked(job)
&& mService.isJobInOvertimeLocked(job));
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index 7f6a75e75397..c7070695ecc2 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -1351,6 +1351,11 @@ public class InternalResourceService extends SystemService {
}
@Override
+ public int getEnabledMode() {
+ return InternalResourceService.this.getEnabledMode();
+ }
+
+ @Override
public int handleShellCommand(@NonNull ParcelFileDescriptor in,
@NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
@NonNull String[] args) {
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index 996c3882701d..aebace57aa2c 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -33583,8 +33583,8 @@ Lcom/android/internal/dynamicanimation/animation/DynamicAnimation;
Lcom/android/internal/dynamicanimation/animation/Force;
Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
Lcom/android/internal/graphics/ColorUtils;
Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 21ae13474d84..4293caf57aea 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -10784,8 +10784,8 @@ com.android.internal.dynamicanimation.animation.DynamicAnimation
com.android.internal.dynamicanimation.animation.Force
com.android.internal.dynamicanimation.animation.SpringAnimation
com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
com.android.internal.graphics.ColorUtils$ContrastCalculator
com.android.internal.graphics.ColorUtils
com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 699808156033..b6dc32a29f04 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -21,6 +21,7 @@ import android.annotation.UserIdInt;
import android.app.backup.BackupManager;
import android.app.backup.BackupManagerMonitor;
import android.app.backup.BackupProgress;
+import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
@@ -821,14 +822,22 @@ public class Bmgr {
doRestorePackage(arg);
} else {
try {
+ @Monitor int monitor = Monitor.OFF;
+
long token = Long.parseLong(arg, 16);
HashSet<String> filter = null;
while ((arg = nextArg()) != null) {
- if (filter == null) filter = new HashSet<String>();
- filter.add(arg);
+ if (arg.equals("--monitor")) {
+ monitor = Monitor.NORMAL;
+ } else if (arg.equals("--monitor-verbose")) {
+ monitor = Monitor.VERBOSE;
+ } else {
+ if (filter == null) filter = new HashSet<String>();
+ filter.add(arg);
+ }
}
- doRestoreAll(userId, token, filter);
+ doRestoreAll(userId, token, filter, monitor);
} catch (NumberFormatException e) {
showUsage();
return;
@@ -841,7 +850,8 @@ public class Bmgr {
System.err.println("'restore <token> <package>'.");
}
- private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) {
+ private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter,
+ @Monitor int monitorState) {
RestoreObserver observer = new RestoreObserver();
try {
@@ -852,8 +862,11 @@ public class Bmgr {
return;
}
RestoreSet[] sets = null;
- // TODO implement monitor here
- int err = mRestore.getAvailableRestoreSets(observer, null);
+ BackupMonitor monitor =
+ (monitorState != Monitor.OFF)
+ ? new BackupMonitor(monitorState == Monitor.VERBOSE)
+ : null;
+ int err = mRestore.getAvailableRestoreSets(observer, monitor);
if (err == 0) {
observer.waitForCompletion();
sets = observer.sets;
@@ -862,12 +875,12 @@ public class Bmgr {
if (s.token == token) {
System.out.println("Scheduling restore: " + s.name);
if (filter == null) {
- didRestore = (mRestore.restoreAll(token, observer, null) == 0);
+ didRestore = (mRestore.restoreAll(token, observer, monitor) == 0);
} else {
String[] names = new String[filter.size()];
filter.toArray(names);
didRestore = (mRestore.restorePackages(token, observer, names,
- null) == 0);
+ monitor) == 0);
}
break;
}
@@ -958,8 +971,8 @@ public class Bmgr {
System.err.println(" bmgr list transports [-c]");
System.err.println(" bmgr list sets");
System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT");
- System.err.println(" bmgr restore TOKEN");
- System.err.println(" bmgr restore TOKEN PACKAGE...");
+ System.err.println(" bmgr restore TOKEN [--monitor|--monitor-verbose]");
+ System.err.println(" bmgr restore TOKEN PACKAGE... [--monitor|--monitor-verbose]");
System.err.println(" bmgr run");
System.err.println(" bmgr wipe TRANSPORT PACKAGE");
System.err.println(" bmgr fullbackup PACKAGE...");
@@ -1005,12 +1018,18 @@ public class Bmgr {
System.err.println("restore operation from the currently active transport. It will deliver");
System.err.println("the restore set designated by the TOKEN argument to each application");
System.err.println("that had contributed data to that restore set.");
+ System.err.println(" --monitor flag prints monitor events (important events and errors");
+ System.err.println(" encountered during restore).");
+ System.err.println(" --monitor-verbose flag prints monitor events with all keys.");
System.err.println("");
System.err.println("The 'restore' command when given a token and one or more package names");
System.err.println("initiates a restore operation of just those given packages from the restore");
System.err.println("set designated by the TOKEN argument. It is effectively the same as the");
System.err.println("'restore' operation supplying only a token, but applies a filter to the");
System.err.println("set of applications to be restored.");
+ System.err.println(" --monitor flag prints monitor events (important events and errors");
+ System.err.println(" encountered during restore).");
+ System.err.println(" --monitor-verbose flag prints monitor events with all keys.");
System.err.println("");
System.err.println("The 'run' command causes any scheduled backup operation to be initiated");
System.err.println("immediately, without the usual waiting period for batching together");
@@ -1026,7 +1045,8 @@ public class Bmgr {
System.err.println("");
System.err.println("The 'backupnow' command runs an immediate backup for one or more packages.");
System.err.println(" --all flag runs backup for all eligible packages.");
- System.err.println(" --monitor flag prints monitor events.");
+ System.err.println(" --monitor flag prints monitor events (important events and errors");
+ System.err.println(" encountered during backup).");
System.err.println(" --monitor-verbose flag prints monitor events with all keys.");
System.err.println("For each package it will run key/value or full data backup ");
System.err.println("depending on the package's manifest declarations.");
@@ -1076,6 +1096,37 @@ public class Bmgr {
out.append("(v").append(version).append(")");
}
}
+ if (event.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) {
+ ArrayList<BackupRestoreEventLogger.DataTypeResult> results =
+ event.getParcelableArrayList(
+ BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS,
+ BackupRestoreEventLogger.DataTypeResult.class);
+ out.append(", results = [");
+ for (BackupRestoreEventLogger.DataTypeResult result : results) {
+ out.append("\n{\n\tdataType: ");
+ out.append(result.getDataType());
+ out.append("\n\tsuccessCount: ");
+ out.append(result.getSuccessCount());
+ out.append("\n\tfailCount: ");
+ out.append(result.getFailCount());
+ out.append("\n\tmetadataHash: ");
+ out.append(Arrays.toString(result.getMetadataHash()));
+
+ if (!result.getErrors().isEmpty()) {
+ out.append("\n\terrors: [");
+ for (String error : result.getErrors().keySet()) {
+ out.append(error);
+ out.append(": ");
+ out.append(result.getErrors().get(error));
+ out.append(";");
+ }
+ out.append("]");
+ }
+ out.append("\n}");
+
+ }
+ out.append("]");
+ }
if (mVerbose) {
Set<String> remainingKeys = new ArraySet<>(event.keySet());
remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
@@ -1083,6 +1134,7 @@ public class Bmgr {
remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME);
remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION);
remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION);
+ remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
if (!remainingKeys.isEmpty()) {
out.append(", other keys =");
for (String key : remainingKeys) {
@@ -1192,6 +1244,8 @@ public class Bmgr {
return "NO_PACKAGES";
case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL:
return "TRANSPORT_IS_NULL";
+ case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS:
+ return "AGENT_LOGGING_RESULTS";
default:
return "UNKNOWN_ID";
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4e8b0e49af3..a8b6c0b70804 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -105,6 +105,7 @@ static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress";
static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
static const char CLOCK_ENABLED_PROP_NAME[] = "persist.sys.bootanim.clock.enabled";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
+static const int MAX_CHECK_EXIT_INTERVAL_US = 50000;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
static const int DYNAMIC_COLOR_COUNT = 4;
static const char U_TEXTURE[] = "uTexture";
@@ -1678,7 +1679,17 @@ bool BootAnimation::playAnimation(const Animation& animation) {
checkExit();
}
- usleep(part.pause * ns2us(frameDuration));
+ int pauseDuration = part.pause * ns2us(frameDuration);
+ while(pauseDuration > 0 && !exitPending()){
+ if (pauseDuration > MAX_CHECK_EXIT_INTERVAL_US) {
+ usleep(MAX_CHECK_EXIT_INTERVAL_US);
+ pauseDuration -= MAX_CHECK_EXIT_INTERVAL_US;
+ } else {
+ usleep(pauseDuration);
+ break;
+ }
+ checkExit();
+ }
if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
!part.hasFadingPhase()) {
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 3cc990873c87..bb0748764cc8 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -43717,8 +43717,8 @@ Lcom/android/internal/dynamicanimation/animation/DynamicAnimation;
Lcom/android/internal/dynamicanimation/animation/Force;
Lcom/android/internal/dynamicanimation/animation/SpringAnimation;
Lcom/android/internal/dynamicanimation/animation/SpringForce;
-Lcom/android/internal/expresslog/Counter;
-Lcom/android/internal/expresslog/Utils;
+Lcom/android/modules/expresslog/Counter;
+Lcom/android/modules/expresslog/Utils;
Lcom/android/internal/graphics/ColorUtils$ContrastCalculator;
Lcom/android/internal/graphics/ColorUtils;
Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 8e50fe8e4e0f..1812c2bb61d6 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -10815,8 +10815,8 @@ com.android.internal.dynamicanimation.animation.DynamicAnimation
com.android.internal.dynamicanimation.animation.Force
com.android.internal.dynamicanimation.animation.SpringAnimation
com.android.internal.dynamicanimation.animation.SpringForce
-com.android.internal.expresslog.Counter
-com.android.internal.expresslog.Utils
+com.android.modules.expresslog.Counter
+com.android.modules.expresslog.Utils
com.android.internal.graphics.ColorUtils$ContrastCalculator
com.android.internal.graphics.ColorUtils
com.android.internal.graphics.SfVsyncFrameCallbackProvider
diff --git a/core/api/current.txt b/core/api/current.txt
index 80abd84733d7..288ab479c0fb 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -377,7 +377,7 @@ package android {
public static final class R.attr {
ctor public R.attr();
field public static final int absListViewStyle = 16842858; // 0x101006a
- field public static final int accessibilityDataSensitive;
+ field public static final int accessibilityDataSensitive = 16844407; // 0x1010677
field public static final int accessibilityEventTypes = 16843648; // 0x1010380
field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
field public static final int accessibilityFlags = 16843652; // 0x1010384
@@ -445,12 +445,12 @@ package android {
field public static final int allowGameFpsOverride = 16844378; // 0x101065a
field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
field public static final int allowParallelSyncs = 16843570; // 0x1010332
- field public static final int allowSharedIsolatedProcess;
+ field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d
field public static final int allowSingleTap = 16843353; // 0x1010259
field public static final int allowTaskReparenting = 16843268; // 0x1010204
field public static final int allowUndo = 16843999; // 0x10104df
field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669
- field public static final int allowUpdateOwnership;
+ field public static final int allowUpdateOwnership = 16844416; // 0x1010680
field public static final int alpha = 16843551; // 0x101031f
field public static final int alphabeticModifiers = 16844110; // 0x101054e
field public static final int alphabeticShortcut = 16843235; // 0x10101e3
@@ -556,7 +556,7 @@ package android {
field public static final int canTakeScreenshot = 16844303; // 0x101060f
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final int cantSaveState = 16844142; // 0x101056e
- field public static final int capability;
+ field public static final int capability = 16844423; // 0x1010687
field @Deprecated public static final int capitalize = 16843113; // 0x1010169
field public static final int category = 16843752; // 0x10103e8
field public static final int centerBright = 16842956; // 0x10100cc
@@ -741,7 +741,7 @@ package android {
field public static final int ellipsize = 16842923; // 0x10100ab
field public static final int ems = 16843096; // 0x1010158
field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c
- field public static final int enableTextStylingShortcuts;
+ field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678
field public static final int enableVrMode = 16844069; // 0x1010525
field public static final int enabled = 16842766; // 0x101000e
field public static final int end = 16843996; // 0x10104dc
@@ -810,7 +810,7 @@ package android {
field public static final int focusableInTouchMode = 16842971; // 0x10100db
field public static final int focusedByDefault = 16844100; // 0x1010544
field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343
- field public static final int focusedSearchResultHighlightColor;
+ field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683
field public static final int font = 16844082; // 0x1010532
field public static final int fontFamily = 16843692; // 0x10103ac
field public static final int fontFeatureSettings = 16843959; // 0x10104b7
@@ -896,10 +896,10 @@ package android {
field public static final int hand_secondTintMode = 16844349; // 0x101063d
field public static final int handle = 16843354; // 0x101025a
field public static final int handleProfiling = 16842786; // 0x1010022
- field public static final int handwritingBoundsOffsetBottom;
- field public static final int handwritingBoundsOffsetLeft;
- field public static final int handwritingBoundsOffsetRight;
- field public static final int handwritingBoundsOffsetTop;
+ field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676
+ field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673
+ field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675
+ field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674
field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e
field public static final int hardwareAccelerated = 16843475; // 0x10102d3
field public static final int hasCode = 16842764; // 0x101000c
@@ -986,7 +986,7 @@ package android {
field public static final int isAlwaysSyncable = 16843571; // 0x1010333
field public static final int isAsciiCapable = 16843753; // 0x10103e9
field public static final int isAuxiliary = 16843647; // 0x101037f
- field public static final int isCredential;
+ field public static final int isCredential = 16844417; // 0x1010681
field public static final int isDefault = 16843297; // 0x1010221
field public static final int isFeatureSplit = 16844123; // 0x101055b
field public static final int isGame = 16843764; // 0x10103f4
@@ -1021,8 +1021,8 @@ package android {
field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
field public static final int keyboardLayout = 16843691; // 0x10103ab
- field public static final int keyboardLayoutType;
- field public static final int keyboardLocale;
+ field public static final int keyboardLayoutType = 16844415; // 0x101067f
+ field public static final int keyboardLocale = 16844414; // 0x101067e
field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
field public static final int keycode = 16842949; // 0x10100c5
@@ -1263,8 +1263,8 @@ package android {
field public static final int persistentDrawingCache = 16842990; // 0x10100ee
field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563
field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167
- field public static final int physicalKeyboardHintLanguageTag;
- field public static final int physicalKeyboardHintLayoutType;
+ field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b
+ field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c
field public static final int pivotX = 16843189; // 0x10101b5
field public static final int pivotY = 16843190; // 0x10101b6
field public static final int pointerIcon = 16844041; // 0x1010509
@@ -1354,7 +1354,7 @@ package android {
field public static final int requireDeviceUnlock = 16843756; // 0x10103ec
field public static final int required = 16843406; // 0x101028e
field public static final int requiredAccountType = 16843734; // 0x10103d6
- field public static final int requiredDisplayCategory;
+ field public static final int requiredDisplayCategory = 16844409; // 0x1010679
field public static final int requiredFeature = 16844116; // 0x1010554
field public static final int requiredForAllUsers = 16843728; // 0x10103d0
field public static final int requiredNotFeature = 16844117; // 0x1010555
@@ -1422,7 +1422,7 @@ package android {
field public static final int searchHintIcon = 16843988; // 0x10104d4
field public static final int searchIcon = 16843907; // 0x1010483
field public static final int searchMode = 16843221; // 0x10101d5
- field public static final int searchResultHighlightColor;
+ field public static final int searchResultHighlightColor = 16844418; // 0x1010682
field public static final int searchSettingsDescription = 16843402; // 0x101028a
field public static final int searchSuggestAuthority = 16843222; // 0x10101d6
field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9
@@ -1449,7 +1449,7 @@ package android {
field public static final int sessionService = 16843837; // 0x101043d
field public static final int settingsActivity = 16843301; // 0x1010225
field public static final int settingsSliceUri = 16844179; // 0x1010593
- field public static final int settingsSubtitle;
+ field public static final int settingsSubtitle = 16844422; // 0x1010686
field public static final int setupActivity = 16843766; // 0x10103f6
field public static final int shadowColor = 16843105; // 0x1010161
field public static final int shadowDx = 16843106; // 0x1010162
@@ -1555,7 +1555,7 @@ package android {
field public static final int strokeLineJoin = 16843788; // 0x101040c
field public static final int strokeMiterLimit = 16843789; // 0x101040d
field public static final int strokeWidth = 16843783; // 0x1010407
- field public static final int stylusHandwritingSettingsActivity;
+ field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684
field public static final int subMenuArrow = 16844019; // 0x10104f3
field public static final int submitBackground = 16843912; // 0x1010488
field public static final int subtitle = 16843473; // 0x10102d1
@@ -1861,7 +1861,7 @@ package android {
field public static final int windowMinWidthMajor = 16843606; // 0x1010356
field public static final int windowMinWidthMinor = 16843607; // 0x1010357
field public static final int windowNoDisplay = 16843294; // 0x101021e
- field public static final int windowNoMoveAnimation;
+ field public static final int windowNoMoveAnimation = 16844421; // 0x1010685
field public static final int windowNoTitle = 16842838; // 0x1010056
field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf
field public static final int windowReenterTransition = 16843951; // 0x10104af
@@ -1966,18 +1966,18 @@ package android {
field public static final int system_accent3_700 = 17170522; // 0x106005a
field public static final int system_accent3_800 = 17170523; // 0x106005b
field public static final int system_accent3_900 = 17170524; // 0x106005c
- field public static final int system_background_dark;
- field public static final int system_background_light;
- field public static final int system_control_activated_dark;
- field public static final int system_control_activated_light;
- field public static final int system_control_highlight_dark;
- field public static final int system_control_highlight_light;
- field public static final int system_control_normal_dark;
- field public static final int system_control_normal_light;
- field public static final int system_error_container_dark;
- field public static final int system_error_container_light;
- field public static final int system_error_dark;
- field public static final int system_error_light;
+ field public static final int system_background_dark = 17170581; // 0x1060095
+ field public static final int system_background_light = 17170538; // 0x106006a
+ field public static final int system_control_activated_dark = 17170599; // 0x10600a7
+ field public static final int system_control_activated_light = 17170556; // 0x106007c
+ field public static final int system_control_highlight_dark = 17170601; // 0x10600a9
+ field public static final int system_control_highlight_light = 17170558; // 0x106007e
+ field public static final int system_control_normal_dark = 17170600; // 0x10600a8
+ field public static final int system_control_normal_light = 17170557; // 0x106007d
+ field public static final int system_error_container_dark = 17170597; // 0x10600a5
+ field public static final int system_error_container_light = 17170554; // 0x106007a
+ field public static final int system_error_dark = 17170595; // 0x10600a3
+ field public static final int system_error_light = 17170552; // 0x1060078
field public static final int system_neutral1_0 = 17170461; // 0x106001d
field public static final int system_neutral1_10 = 17170462; // 0x106001e
field public static final int system_neutral1_100 = 17170464; // 0x1060020
@@ -2004,94 +2004,94 @@ package android {
field public static final int system_neutral2_700 = 17170483; // 0x1060033
field public static final int system_neutral2_800 = 17170484; // 0x1060034
field public static final int system_neutral2_900 = 17170485; // 0x1060035
- field public static final int system_on_background_dark;
- field public static final int system_on_background_light;
- field public static final int system_on_error_container_dark;
- field public static final int system_on_error_container_light;
- field public static final int system_on_error_dark;
- field public static final int system_on_error_light;
- field public static final int system_on_primary_container_dark;
- field public static final int system_on_primary_container_light;
- field public static final int system_on_primary_dark;
- field public static final int system_on_primary_fixed;
- field public static final int system_on_primary_fixed_variant;
- field public static final int system_on_primary_light;
- field public static final int system_on_secondary_container_dark;
- field public static final int system_on_secondary_container_light;
- field public static final int system_on_secondary_dark;
- field public static final int system_on_secondary_fixed;
- field public static final int system_on_secondary_fixed_variant;
- field public static final int system_on_secondary_light;
- field public static final int system_on_surface_dark;
- field public static final int system_on_surface_light;
- field public static final int system_on_surface_variant_dark;
- field public static final int system_on_surface_variant_light;
- field public static final int system_on_tertiary_container_dark;
- field public static final int system_on_tertiary_container_light;
- field public static final int system_on_tertiary_dark;
- field public static final int system_on_tertiary_fixed;
- field public static final int system_on_tertiary_fixed_variant;
- field public static final int system_on_tertiary_light;
- field public static final int system_outline_dark;
- field public static final int system_outline_light;
- field public static final int system_outline_variant_dark;
- field public static final int system_outline_variant_light;
- field public static final int system_palette_key_color_neutral_dark;
- field public static final int system_palette_key_color_neutral_light;
- field public static final int system_palette_key_color_neutral_variant_dark;
- field public static final int system_palette_key_color_neutral_variant_light;
- field public static final int system_palette_key_color_primary_dark;
- field public static final int system_palette_key_color_primary_light;
- field public static final int system_palette_key_color_secondary_dark;
- field public static final int system_palette_key_color_secondary_light;
- field public static final int system_palette_key_color_tertiary_dark;
- field public static final int system_palette_key_color_tertiary_light;
- field public static final int system_primary_container_dark;
- field public static final int system_primary_container_light;
- field public static final int system_primary_dark;
- field public static final int system_primary_fixed;
- field public static final int system_primary_fixed_dim;
- field public static final int system_primary_light;
- field public static final int system_secondary_container_dark;
- field public static final int system_secondary_container_light;
- field public static final int system_secondary_dark;
- field public static final int system_secondary_fixed;
- field public static final int system_secondary_fixed_dim;
- field public static final int system_secondary_light;
- field public static final int system_surface_bright_dark;
- field public static final int system_surface_bright_light;
- field public static final int system_surface_container_dark;
- field public static final int system_surface_container_high_dark;
- field public static final int system_surface_container_high_light;
- field public static final int system_surface_container_highest_dark;
- field public static final int system_surface_container_highest_light;
- field public static final int system_surface_container_light;
- field public static final int system_surface_container_low_dark;
- field public static final int system_surface_container_low_light;
- field public static final int system_surface_container_lowest_dark;
- field public static final int system_surface_container_lowest_light;
- field public static final int system_surface_dark;
- field public static final int system_surface_dim_dark;
- field public static final int system_surface_dim_light;
- field public static final int system_surface_light;
- field public static final int system_surface_variant_dark;
- field public static final int system_surface_variant_light;
- field public static final int system_tertiary_container_dark;
- field public static final int system_tertiary_container_light;
- field public static final int system_tertiary_dark;
- field public static final int system_tertiary_fixed;
- field public static final int system_tertiary_fixed_dim;
- field public static final int system_tertiary_light;
- field public static final int system_text_hint_inverse_dark;
- field public static final int system_text_hint_inverse_light;
- field public static final int system_text_primary_inverse_dark;
- field public static final int system_text_primary_inverse_disable_only_dark;
- field public static final int system_text_primary_inverse_disable_only_light;
- field public static final int system_text_primary_inverse_light;
- field public static final int system_text_secondary_and_tertiary_inverse_dark;
- field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark;
- field public static final int system_text_secondary_and_tertiary_inverse_disabled_light;
- field public static final int system_text_secondary_and_tertiary_inverse_light;
+ field public static final int system_on_background_dark = 17170582; // 0x1060096
+ field public static final int system_on_background_light = 17170539; // 0x106006b
+ field public static final int system_on_error_container_dark = 17170598; // 0x10600a6
+ field public static final int system_on_error_container_light = 17170555; // 0x106007b
+ field public static final int system_on_error_dark = 17170596; // 0x10600a4
+ field public static final int system_on_error_light = 17170553; // 0x1060079
+ field public static final int system_on_primary_container_dark = 17170570; // 0x106008a
+ field public static final int system_on_primary_container_light = 17170527; // 0x106005f
+ field public static final int system_on_primary_dark = 17170572; // 0x106008c
+ field public static final int system_on_primary_fixed = 17170614; // 0x10600b6
+ field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7
+ field public static final int system_on_primary_light = 17170529; // 0x1060061
+ field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e
+ field public static final int system_on_secondary_container_light = 17170531; // 0x1060063
+ field public static final int system_on_secondary_dark = 17170576; // 0x1060090
+ field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba
+ field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb
+ field public static final int system_on_secondary_light = 17170533; // 0x1060065
+ field public static final int system_on_surface_dark = 17170584; // 0x1060098
+ field public static final int system_on_surface_light = 17170541; // 0x106006d
+ field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1
+ field public static final int system_on_surface_variant_light = 17170550; // 0x1060076
+ field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092
+ field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067
+ field public static final int system_on_tertiary_dark = 17170580; // 0x1060094
+ field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be
+ field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf
+ field public static final int system_on_tertiary_light = 17170537; // 0x1060069
+ field public static final int system_outline_dark = 17170594; // 0x10600a2
+ field public static final int system_outline_light = 17170551; // 0x1060077
+ field public static final int system_outline_variant_dark = 17170625; // 0x10600c1
+ field public static final int system_outline_variant_light = 17170624; // 0x10600c0
+ field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2
+ field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087
+ field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3
+ field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088
+ field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af
+ field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084
+ field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0
+ field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085
+ field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1
+ field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086
+ field public static final int system_primary_container_dark = 17170569; // 0x1060089
+ field public static final int system_primary_container_light = 17170526; // 0x106005e
+ field public static final int system_primary_dark = 17170571; // 0x106008b
+ field public static final int system_primary_fixed = 17170612; // 0x10600b4
+ field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5
+ field public static final int system_primary_light = 17170528; // 0x1060060
+ field public static final int system_secondary_container_dark = 17170573; // 0x106008d
+ field public static final int system_secondary_container_light = 17170530; // 0x1060062
+ field public static final int system_secondary_dark = 17170575; // 0x106008f
+ field public static final int system_secondary_fixed = 17170616; // 0x10600b8
+ field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9
+ field public static final int system_secondary_light = 17170532; // 0x1060064
+ field public static final int system_surface_bright_dark = 17170590; // 0x106009e
+ field public static final int system_surface_bright_light = 17170547; // 0x1060073
+ field public static final int system_surface_container_dark = 17170587; // 0x106009b
+ field public static final int system_surface_container_high_dark = 17170588; // 0x106009c
+ field public static final int system_surface_container_high_light = 17170545; // 0x1060071
+ field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d
+ field public static final int system_surface_container_highest_light = 17170546; // 0x1060072
+ field public static final int system_surface_container_light = 17170544; // 0x1060070
+ field public static final int system_surface_container_low_dark = 17170585; // 0x1060099
+ field public static final int system_surface_container_low_light = 17170542; // 0x106006e
+ field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a
+ field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f
+ field public static final int system_surface_dark = 17170583; // 0x1060097
+ field public static final int system_surface_dim_dark = 17170591; // 0x106009f
+ field public static final int system_surface_dim_light = 17170548; // 0x1060074
+ field public static final int system_surface_light = 17170540; // 0x106006c
+ field public static final int system_surface_variant_dark = 17170592; // 0x10600a0
+ field public static final int system_surface_variant_light = 17170549; // 0x1060075
+ field public static final int system_tertiary_container_dark = 17170577; // 0x1060091
+ field public static final int system_tertiary_container_light = 17170534; // 0x1060066
+ field public static final int system_tertiary_dark = 17170579; // 0x1060093
+ field public static final int system_tertiary_fixed = 17170620; // 0x10600bc
+ field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd
+ field public static final int system_tertiary_light = 17170536; // 0x1060068
+ field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae
+ field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083
+ field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa
+ field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac
+ field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081
+ field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f
+ field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab
+ field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad
+ field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082
+ field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080
field public static final int tab_indicator_text = 17170441; // 0x1060009
field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010
field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011
@@ -2310,7 +2310,7 @@ package android {
field public static final int accessibilityActionPageUp = 16908358; // 0x1020046
field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a
field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
- field public static final int accessibilityActionScrollInDirection;
+ field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e
field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b
field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037
@@ -2331,7 +2331,7 @@ package android {
field public static final int addToDictionary = 16908330; // 0x102002a
field public static final int autofill = 16908355; // 0x1020043
field public static final int background = 16908288; // 0x1020000
- field public static final int bold;
+ field public static final int bold = 16908379; // 0x102005b
field public static final int button1 = 16908313; // 0x1020019
field public static final int button2 = 16908314; // 0x102001a
field public static final int button3 = 16908315; // 0x102001b
@@ -2357,7 +2357,7 @@ package android {
field public static final int inputExtractAccessories = 16908378; // 0x102005a
field public static final int inputExtractAction = 16908377; // 0x1020059
field public static final int inputExtractEditText = 16908325; // 0x1020025
- field public static final int italic;
+ field public static final int italic = 16908380; // 0x102005c
field @Deprecated public static final int keyboardView = 16908326; // 0x1020026
field public static final int list = 16908298; // 0x102000a
field public static final int list_container = 16908351; // 0x102003f
@@ -2389,7 +2389,7 @@ package android {
field public static final int textAssist = 16908353; // 0x1020041
field public static final int title = 16908310; // 0x1020016
field public static final int toggle = 16908311; // 0x1020017
- field public static final int underline;
+ field public static final int underline = 16908381; // 0x102005d
field public static final int undo = 16908338; // 0x1020032
field public static final int widget_frame = 16908312; // 0x1020018
}
@@ -13662,7 +13662,6 @@ package android.credentials {
}
public final class CredentialOption implements android.os.Parcelable {
- ctor @Deprecated public CredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
method public int describeContents();
method @NonNull public java.util.Set<android.content.ComponentName> getAllowedProviders();
method @NonNull public android.os.Bundle getCandidateQueryData();
@@ -32653,7 +32652,7 @@ package android.os {
field public static final int S = 31; // 0x1f
field public static final int S_V2 = 32; // 0x20
field public static final int TIRAMISU = 33; // 0x21
- field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710
+ field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22
}
public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index fbc69e34a644..ace7d59c9a45 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -410,14 +410,14 @@ package android {
field public static final int sdkVersion = 16844304; // 0x1010610
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
- field public static final int visualQueryDetectionService;
+ field public static final int visualQueryDetectionService = 16844410; // 0x101067a
}
public static final class R.bool {
- field public static final int config_enableDefaultNotes;
- field public static final int config_enableDefaultNotesForWorkProfile;
+ field public static final int config_enableDefaultNotes = 17891338; // 0x111000a
+ field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b
field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
- field public static final int config_safetyProtectionEnabled;
+ field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009
field public static final int config_sendPackageName = 17891328; // 0x1110000
field public static final int config_showDefaultAssistant = 17891329; // 0x1110001
field public static final int config_showDefaultEmergency = 17891330; // 0x1110002
@@ -430,7 +430,7 @@ package android {
public static final class R.dimen {
field public static final int config_restrictedIconSize = 17104903; // 0x1050007
- field public static final int config_viewConfigurationHandwritingGestureLineMargin;
+ field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a
}
public static final class R.drawable {
@@ -452,7 +452,7 @@ package android {
field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
field public static final int config_defaultCallScreening = 17039398; // 0x1040026
field public static final int config_defaultDialer = 17039395; // 0x1040023
- field public static final int config_defaultNotes;
+ field public static final int config_defaultNotes = 17039429; // 0x1040045
field public static final int config_defaultSms = 17039396; // 0x1040024
field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -468,10 +468,10 @@ package android {
field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f
field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028
field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029
- field public static final int config_systemCallStreaming;
+ field public static final int config_systemCallStreaming = 17039431; // 0x1040047
field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039
field public static final int config_systemContacts = 17039403; // 0x104002b
- field public static final int config_systemFinancedDeviceController;
+ field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046
field public static final int config_systemGallery = 17039399; // 0x1040027
field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035
field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042
@@ -483,7 +483,7 @@ package android {
field public static final int config_systemUi = 17039418; // 0x104003a
field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
- field public static final int config_systemWearHealthService;
+ field public static final int config_systemWearHealthService = 17039428; // 0x1040044
field public static final int config_systemWellbeing = 17039408; // 0x1040030
field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
field public static final int safety_protection_display_text = 17039425; // 0x1040041
@@ -10117,7 +10117,7 @@ package android.net.wifi.sharedconnectivity.app {
public final class NetworkProviderInfo implements android.os.Parcelable {
method public int describeContents();
method @IntRange(from=0, to=100) public int getBatteryPercentage();
- method @IntRange(from=0, to=3) public int getConnectionStrength();
+ method @IntRange(from=0, to=4) public int getConnectionStrength();
method @NonNull public String getDeviceName();
method public int getDeviceType();
method @NonNull public android.os.Bundle getExtras();
@@ -10136,7 +10136,7 @@ package android.net.wifi.sharedconnectivity.app {
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 setBatteryPercentage(@IntRange(from=0, to=100) int);
- method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) 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);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int);
method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ffbfe82be475..2d9a99cf0d8c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -8,8 +8,6 @@ package android {
field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS";
field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE";
- field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE";
- field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND";
field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE";
field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
@@ -779,6 +777,17 @@ package android.app.prediction {
}
+package android.app.tare {
+
+ public class EconomyManager {
+ method public int getEnabledMode();
+ field public static final int ENABLED_MODE_OFF = 0; // 0x0
+ field public static final int ENABLED_MODE_SHADOW = 2; // 0x2
+ field public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode";
+ }
+
+}
+
package android.app.usage {
public class StorageStatsManager {
@@ -1651,6 +1660,11 @@ package android.hardware.soundtrigger {
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR;
}
+ public class SoundTrigger {
+ field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
+ }
+
public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable {
ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int);
}
@@ -1663,6 +1677,19 @@ package android.hardware.soundtrigger {
ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int);
}
+ public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable {
+ ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int);
+ ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR;
+ field public final boolean allowMultipleTriggers;
+ field public final int audioCapabilities;
+ field public final boolean captureRequested;
+ field @NonNull public final byte[] data;
+ field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases;
+ }
+
public static class SoundTrigger.RecognitionEvent {
ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long);
}
@@ -2000,6 +2027,57 @@ package android.media.metrics {
}
+package android.media.soundtrigger {
+
+ public final class SoundTriggerInstrumentation {
+ method public void setResourceContention(boolean);
+ method public void triggerOnResourcesAvailable();
+ method public void triggerRestart();
+ }
+
+ public static interface SoundTriggerInstrumentation.GlobalCallback {
+ method public default void onClientAttached();
+ method public default void onClientDetached();
+ method public default void onFrameworkDetached();
+ method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession);
+ method public default void onPreempted();
+ method public default void onRestarted();
+ }
+
+ public static interface SoundTriggerInstrumentation.ModelCallback {
+ method public default void onModelUnloaded();
+ method public default void onParamSet(int, int);
+ method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession);
+ }
+
+ public class SoundTriggerInstrumentation.ModelSession {
+ method public void clearModelCallback();
+ method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases();
+ method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel();
+ method public boolean isKeyphrase();
+ method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback);
+ method public void triggerUnloadModel();
+ }
+
+ public static interface SoundTriggerInstrumentation.RecognitionCallback {
+ method public void onRecognitionStopped();
+ }
+
+ public class SoundTriggerInstrumentation.RecognitionSession {
+ method public void clearRecognitionCallback();
+ method public int getAudioSession();
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig();
+ method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback);
+ method public void triggerAbortRecognition();
+ method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
+ }
+
+ public final class SoundTriggerManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback);
+ }
+
+}
+
package android.media.tv {
public final class TvInputManager {
@@ -2024,6 +2102,14 @@ package android.media.tv.tuner {
}
+package android.media.voice {
+
+ public final class KeyphraseModelManager {
+ method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean);
+ }
+
+}
+
package android.net {
public class NetworkPolicyManager {
@@ -2277,12 +2363,12 @@ package android.os {
method public int getMainDisplayIdAssignedToUser();
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
+ method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
method public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported();
method public boolean isVisibleBackgroundUsersSupported();
- method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
+ method @Deprecated @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
public final class VibrationAttributes implements android.os.Parcelable {
@@ -2944,6 +3030,7 @@ package android.service.voice {
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback);
method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties();
+ method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean);
}
public static class VoiceInteractionSession.ActivityId {
@@ -3351,8 +3438,14 @@ package android.view {
method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier();
}
+ public abstract class InputEvent implements android.os.Parcelable {
+ method public abstract int getDisplayId();
+ method public abstract void setDisplayId(int);
+ }
+
public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable {
method public static String actionToString(int);
+ method public final int getDisplayId();
method public final void setDisplayId(int);
field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
field public static final int LAST_KEYCODE = 316; // 0x13c
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0293bb53d3f0..95e446dde4da 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -343,7 +343,170 @@ public abstract class ActivityManagerInternal {
*/
public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
- public abstract void updateOomAdj();
+ /**
+ * Oom Adj Reason: none - internal use only, do not use it.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_NONE = 0;
+
+ /**
+ * Oom Adj Reason: activity changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ACTIVITY = 1;
+
+ /**
+ * Oom Adj Reason: finishing a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
+
+ /**
+ * Oom Adj Reason: starting a broadcast receiver.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_RECEIVER = 3;
+
+ /**
+ * Oom Adj Reason: binding to a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
+
+ /**
+ * Oom Adj Reason: unbinding from a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
+
+ /**
+ * Oom Adj Reason: starting a service.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_START_SERVICE = 6;
+
+ /**
+ * Oom Adj Reason: connecting to a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
+
+ /**
+ * Oom Adj Reason: disconnecting from a content provider.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
+
+ /**
+ * Oom Adj Reason: UI visibility changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
+
+ /**
+ * Oom Adj Reason: device power allowlist changes.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_ALLOWLIST = 10;
+
+ /**
+ * Oom Adj Reason: starting a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
+
+ /**
+ * Oom Adj Reason: ending a process.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_PROCESS_END = 12;
+
+ /**
+ * Oom Adj Reason: short FGS timeout.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
+
+ /**
+ * Oom Adj Reason: system initialization.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14;
+
+ /**
+ * Oom Adj Reason: backup/restore.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_BACKUP = 15;
+
+ /**
+ * Oom Adj Reason: instrumented by the SHELL.
+ * @hide
+ */
+ public static final int OOM_ADJ_REASON_SHELL = 16;
+
+ /**
+ * Oom Adj Reason: task stack is being removed.
+ */
+ public static final int OOM_ADJ_REASON_REMOVE_TASK = 17;
+
+ /**
+ * Oom Adj Reason: uid idle.
+ */
+ public static final int OOM_ADJ_REASON_UID_IDLE = 18;
+
+ /**
+ * Oom Adj Reason: stop service.
+ */
+ public static final int OOM_ADJ_REASON_STOP_SERVICE = 19;
+
+ /**
+ * Oom Adj Reason: executing service.
+ */
+ public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20;
+
+ /**
+ * Oom Adj Reason: background restriction changes.
+ */
+ public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21;
+
+ /**
+ * Oom Adj Reason: A package or its component is disabled.
+ */
+ public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22;
+
+ @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = {
+ OOM_ADJ_REASON_NONE,
+ OOM_ADJ_REASON_ACTIVITY,
+ OOM_ADJ_REASON_FINISH_RECEIVER,
+ OOM_ADJ_REASON_START_RECEIVER,
+ OOM_ADJ_REASON_BIND_SERVICE,
+ OOM_ADJ_REASON_UNBIND_SERVICE,
+ OOM_ADJ_REASON_START_SERVICE,
+ OOM_ADJ_REASON_GET_PROVIDER,
+ OOM_ADJ_REASON_REMOVE_PROVIDER,
+ OOM_ADJ_REASON_UI_VISIBILITY,
+ OOM_ADJ_REASON_ALLOWLIST,
+ OOM_ADJ_REASON_PROCESS_BEGIN,
+ OOM_ADJ_REASON_PROCESS_END,
+ OOM_ADJ_REASON_SHORT_FGS_TIMEOUT,
+ OOM_ADJ_REASON_SYSTEM_INIT,
+ OOM_ADJ_REASON_BACKUP,
+ OOM_ADJ_REASON_SHELL,
+ OOM_ADJ_REASON_REMOVE_TASK,
+ OOM_ADJ_REASON_UID_IDLE,
+ OOM_ADJ_REASON_STOP_SERVICE,
+ OOM_ADJ_REASON_EXECUTING_SERVICE,
+ OOM_ADJ_REASON_RESTRICTION_CHANGE,
+ OOM_ADJ_REASON_COMPONENT_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface OomAdjReason {}
+
+ /**
+ * Request to update oom adj.
+ */
+ public abstract void updateOomAdj(@OomAdjReason int oomAdjReason);
public abstract void updateCpuStats();
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index b48a8fb73832..3312294865d6 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1450,9 +1450,8 @@ public class AppOpsManager {
public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
- /** @hide Access to wrist temperature sensors. */
- public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE =
- AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+ // App op deprecated/removed.
+ private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
/**
* Send an intent to launch instead of posting the notification to the status bar.
@@ -1461,9 +1460,25 @@ public class AppOpsManager {
*/
public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+ /**
+ * Hides camera indicator for sandboxed detection apps that directly access the service.
+ *
+ * @hide
+ */
+ public static final int OP_CAMERA_SANDBOXED =
+ AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+
+ /**
+ * Hides microphone indicator for sandboxed detection apps that directly access the service.
+ *
+ * @hide
+ */
+ public static final int OP_RECORD_AUDIO_SANDBOXED =
+ AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 134;
+ public static final int _NUM_OP = 136;
/**
* All app ops represented as strings.
@@ -1603,8 +1618,9 @@ public class AppOpsManager {
OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION,
OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION,
OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
- OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
OPSTR_USE_FULL_SCREEN_INTENT,
+ OPSTR_CAMERA_SANDBOXED,
+ OPSTR_RECORD_AUDIO_SANDBOXED
})
public @interface AppOpString {}
@@ -2013,6 +2029,20 @@ public class AppOpsManager {
public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source";
/**
+ * Camera is being recorded in sandboxed detection process.
+ *
+ * @hide
+ */
+ public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed";
+
+ /**
+ * Audio is being recorded in sandboxed detection process.
+ *
+ * @hide
+ */
+ public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed";
+
+ /**
* Allow apps to create the requests to manage the media files without user confirmation.
*
* @see android.Manifest.permission#MANAGE_MEDIA
@@ -2189,11 +2219,10 @@ public class AppOpsManager {
"android:capture_consentless_bugreport_on_userdebug_build";
/**
- * Access to wrist temperature body sensors.
+ * App op deprecated/removed.
* @hide
*/
- public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE =
- "android:body_sensors_wrist_temperature";
+ public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2";
/**
* Send an intent to launch instead of posting the notification to the status bar.
@@ -2311,7 +2340,6 @@ public class AppOpsManager {
OP_READ_MEDIA_VISUAL_USER_SELECTED,
OP_FOREGROUND_SERVICE_SPECIAL_USE,
OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD,
- OP_BODY_SENSORS_WRIST_TEMPERATURE,
OP_USE_FULL_SCREEN_INTENT
};
@@ -2731,14 +2759,15 @@ public class AppOpsManager {
"CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD")
.setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD)
.build(),
- new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE,
- OPSTR_BODY_SENSORS_WRIST_TEMPERATURE,
- "BODY_SENSORS_WRIST_TEMPERATURE")
- .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE)
- .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2")
+ .setDefaultMode(AppOpsManager.MODE_IGNORED).build(),
new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT,
"USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT)
- .build()
+ .build(),
+ new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED,
+ "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED,
+ "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build()
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java
index be012cf8e202..c0c59a24dd8d 100644
--- a/core/java/android/app/ForegroundServiceTypePolicy.java
+++ b/core/java/android/app/ForegroundServiceTypePolicy.java
@@ -473,7 +473,6 @@ public abstract class ForegroundServiceTypePolicy {
new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] {
new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION),
new RegularPermission(Manifest.permission.BODY_SENSORS),
- new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE),
new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS),
}, false),
FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */,
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 746b8f70b6af..0b4862176040 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -24,6 +24,7 @@ import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationHistory;
import android.app.NotificationManager;
+import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
@@ -225,6 +226,7 @@ interface INotificationManager
void setNotificationDelegate(String callingPkg, String delegate);
String getNotificationDelegate(String callingPkg);
boolean canNotifyAsPackage(String callingPkg, String targetPkg, int userId);
+ boolean canUseFullScreenIntent(in AttributionSource attributionSource);
void setPrivateNotificationsAllowed(boolean allow);
boolean getPrivateNotificationsAllowed();
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index ee242635bfb2..2b1558937d21 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -220,6 +220,20 @@ interface IWallpaperManager {
void notifyGoingToSleep(int x, int y, in Bundle extras);
/**
+ * Called when the screen has been fully turned on and is visible.
+ *
+ * @hide
+ */
+ void notifyScreenTurnedOn(int displayId);
+
+ /**
+ * Called when the screen starts turning on.
+ *
+ * @hide
+ */
+ void notifyScreenTurningOn(int displayId);
+
+ /**
* Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default
* dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black.
*
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index c131ce574d2c..e31486f18dbf 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -2354,8 +2354,7 @@ public class Instrumentation {
return mUiAutomation;
}
if (mustCreateNewAutomation) {
- mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(),
- mUiAutomationConnection);
+ mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection);
} else {
mUiAutomation.disconnect();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index f80373912dcb..63da0a231286 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -7016,6 +7016,22 @@ public class Notification implements Parcelable
}
/**
+ * @return true for custom notifications, including notifications
+ * with DecoratedCustomViewStyle or DecoratedMediaCustomViewStyle,
+ * and other notifications with user-provided custom views.
+ *
+ * @hide
+ */
+ public Boolean isCustomNotification() {
+ if (contentView == null
+ && bigContentView == null
+ && headsUpContentView == null) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* @return true if this notification is showing as a bubble
*
* @hide
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 80f64e03afe8..785470f2f22e 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -31,7 +31,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.PermissionChecker;
import android.content.pm.ParceledListSlice;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
@@ -877,19 +876,11 @@ public class NotificationManager {
* {@link android.provider.Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT}.
*/
public boolean canUseFullScreenIntent() {
- final int result = PermissionChecker.checkPermissionForPreflight(mContext,
- android.Manifest.permission.USE_FULL_SCREEN_INTENT,
- mContext.getAttributionSource());
-
- switch (result) {
- case PermissionChecker.PERMISSION_GRANTED:
- return true;
- case PermissionChecker.PERMISSION_SOFT_DENIED:
- case PermissionChecker.PERMISSION_HARD_DENIED:
- return false;
- default:
- if (localLOGV) Log.v(TAG, "Unknown PermissionChecker result: " + result);
- return false;
+ INotificationManager service = getService();
+ try {
+ return service.canUseFullScreenIntent(mContext.getAttributionSource());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 9bf56b374576..99a7fa21b911 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -841,7 +841,8 @@ public final class PendingIntent implements Parcelable {
/**
* Perform the operation associated with this PendingIntent.
*
- * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+ * Bundle)
*
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
@@ -855,7 +856,8 @@ public final class PendingIntent implements Parcelable {
*
* @param code Result code to supply back to the PendingIntent's target.
*
- * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+ * Bundle)
*
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
@@ -875,7 +877,8 @@ public final class PendingIntent implements Parcelable {
* original Intent. If flag {@link #FLAG_IMMUTABLE} was set when this
* pending intent was created, this argument will be ignored.
*
- * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+ * Bundle)
*
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
@@ -892,6 +895,11 @@ public final class PendingIntent implements Parcelable {
* @param options Additional options the caller would like to provide to modify the
* sending behavior. May be built from an {@link ActivityOptions} to apply to an
* activity start.
+ *
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String)
+ *
+ * @throws CanceledException Throws CanceledException if the PendingIntent
+ * is no longer allowing more intents to be sent through it.
*/
public void send(@Nullable Bundle options) throws CanceledException {
send(null, 0, null, null, null, null, options);
@@ -908,7 +916,8 @@ public final class PendingIntent implements Parcelable {
* should happen. If null, the callback will happen from the thread
* pool of the process.
*
- * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler)
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+ * Bundle)
*
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
@@ -942,11 +951,8 @@ public final class PendingIntent implements Parcelable {
* should happen. If null, the callback will happen from the thread
* pool of the process.
*
- * @see #send()
- * @see #send(int)
- * @see #send(Context, int, Intent)
- * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
- * @see #send(Context, int, Intent, OnFinished, Handler, String)
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+ * Bundle)
*
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
@@ -985,11 +991,8 @@ public final class PendingIntent implements Parcelable {
* {@link Context#sendBroadcast(Intent, String) Context.sendOrderedBroadcast(Intent, String)}.
* If null, no permission is required.
*
- * @see #send()
- * @see #send(int)
- * @see #send(Context, int, Intent)
- * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
- * @see #send(Context, int, Intent, OnFinished, Handler)
+ * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler, String,
+ * Bundle)
*
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
@@ -1032,12 +1035,6 @@ public final class PendingIntent implements Parcelable {
* @param options Additional options the caller would like to provide to modify the sending
* behavior. May be built from an {@link ActivityOptions} to apply to an activity start.
*
- * @see #send()
- * @see #send(int)
- * @see #send(Context, int, Intent)
- * @see #send(int, android.app.PendingIntent.OnFinished, Handler)
- * @see #send(Context, int, Intent, OnFinished, Handler)
- *
* @throws CanceledException Throws CanceledException if the PendingIntent
* is no longer allowing more intents to be sent through it.
*/
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 1df860258d82..bc5f7f411af5 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -110,6 +110,9 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
}
],
"file_patterns": ["(/|^)VoiceInteract[^/]*"]
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 658e08444006..247d5bc77ffb 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.view.Display.DEFAULT_DISPLAY;
+
import android.accessibilityservice.AccessibilityGestureEvent;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityService.Callbacks;
@@ -30,6 +32,7 @@ import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;
@@ -45,6 +48,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.Log;
@@ -69,8 +73,10 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import android.view.inputmethod.EditorInfo;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import libcore.io.IoUtils;
@@ -202,6 +208,8 @@ public final class UiAutomation {
private final IUiAutomationConnection mUiAutomationConnection;
+ private final int mDisplayId;
+
private HandlerThread mRemoteCallbackThread;
private IAccessibilityServiceClient mClient;
@@ -261,24 +269,49 @@ public final class UiAutomation {
/**
* Creates a new instance that will handle callbacks from the accessibility
+ * layer on the thread of the provided context main looper and perform requests for privileged
+ * operations on the provided connection, and filtering display-related features to the display
+ * associated with the context (or the user running the test, on devices that
+ * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}).
+ *
+ * @param context the context associated with the automation
+ * @param connection The connection for performing privileged operations.
+ *
+ * @hide
+ */
+ public UiAutomation(Context context, IUiAutomationConnection connection) {
+ this(getDisplayId(context), context.getMainLooper(), connection);
+ }
+
+ /**
+ * Creates a new instance that will handle callbacks from the accessibility
* layer on the thread of the provided looper and perform requests for privileged
* operations on the provided connection.
*
* @param looper The looper on which to execute accessibility callbacks.
* @param connection The connection for performing privileged operations.
*
+ * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead
+ *
* @hide
*/
+ @Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public UiAutomation(Looper looper, IUiAutomationConnection connection) {
- if (looper == null) {
- throw new IllegalArgumentException("Looper cannot be null!");
- }
- if (connection == null) {
- throw new IllegalArgumentException("Connection cannot be null!");
- }
+ this(DEFAULT_DISPLAY, looper, connection);
+ Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY");
+ }
+
+ private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) {
+ Preconditions.checkArgument(looper != null, "Looper cannot be null!");
+ Preconditions.checkArgument(connection != null, "Connection cannot be null!");
+
mLocalCallbackHandler = new Handler(looper);
mUiAutomationConnection = connection;
+ mDisplayId = displayId;
+
+ Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier()
+ + " on display " + mDisplayId);
}
/**
@@ -719,8 +752,14 @@ public final class UiAutomation {
}
/**
- * Gets the windows on the screen of the default display. This method returns only the windows
- * that a sighted user can interact with, as opposed to all windows.
+ * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the
+ * {@link android.view.Display#DEFAULT_DISPLAY default display).
+ *
+ * <p>
+ * This method returns only the windows that a sighted user can interact with, as opposed to
+ * all windows.
+
+ * <p>
* For example, if there is a modal dialog shown and the user cannot touch
* anything behind it, then only the modal window will be reported
* (assuming it is the top one). For convenience the returned windows
@@ -730,21 +769,23 @@ public final class UiAutomation {
* <strong>Note:</strong> In order to access the windows you have to opt-in
* to retrieve the interactive windows by setting the
* {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag.
- * </p>
*
* @return The windows if there are windows such, otherwise an empty list.
* @throws IllegalStateException If the connection to the accessibility subsystem is not
* established.
*/
public List<AccessibilityWindowInfo> getWindows() {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId);
+ }
final int connectionId;
synchronized (mLock) {
throwIfNotConnectedLocked();
connectionId = mConnectionId;
}
// Calling out without a lock held.
- return AccessibilityInteractionClient.getInstance()
- .getWindows(connectionId);
+ return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId,
+ mDisplayId);
}
/**
@@ -1112,8 +1153,10 @@ public final class UiAutomation {
* @return The screenshot bitmap on success, null otherwise.
*/
public Bitmap takeScreenshot() {
- Display display = DisplayManagerGlobal.getInstance()
- .getRealDisplay(Display.DEFAULT_DISPLAY);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId);
+ }
+ Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId);
Point displaySize = new Point();
display.getRealSize(displaySize);
@@ -1126,10 +1169,12 @@ public final class UiAutomation {
screenShot = mUiAutomationConnection.takeScreenshot(
new Rect(0, 0, displaySize.x, displaySize.y));
if (screenShot == null) {
+ Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
+ + mDisplayId);
return null;
}
} catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while taking screenshot!", re);
+ Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re);
return null;
}
@@ -1509,6 +1554,14 @@ public final class UiAutomation {
return executeShellCommandInternal(command, true /* includeStderr */);
}
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public int getDisplayId() {
+ return mDisplayId;
+ }
+
private ParcelFileDescriptor[] executeShellCommandInternal(
String command, boolean includeStderr) {
warnIfBetterCommand(command);
@@ -1564,6 +1617,7 @@ public final class UiAutomation {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode()));
stringBuilder.append("[id=").append(mConnectionId);
+ stringBuilder.append(", displayId=").append(mDisplayId);
stringBuilder.append(", flags=").append(mFlags);
stringBuilder.append("]");
return stringBuilder.toString();
@@ -1601,6 +1655,55 @@ public final class UiAutomation {
return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0;
}
+ /**
+ * Gets the display id associated with the UiAutomation context.
+ *
+ * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call
+ * another one.
+ */
+ private static int getDisplayId(Context context) {
+ Preconditions.checkArgument(context != null, "Context cannot be null!");
+
+ UserManager userManager = context.getSystemService(UserManager.class);
+ // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple
+ // users, the display is only set on devices that support that
+ if (!userManager.isVisibleBackgroundUsersSupported()) {
+ return DEFAULT_DISPLAY;
+ }
+
+ int displayId = context.getDisplayId();
+ if (displayId == Display.INVALID_DISPLAY) {
+ // Shouldn't happen, but we better handle it
+ Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's"
+ + " running in the display assigned to the user");
+ return getMainDisplayIdAssignedToUser(context, userManager);
+ }
+
+ if (displayId != DEFAULT_DISPLAY) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")");
+ }
+ // Context is explicitly setting the display, so we respect that...
+ return displayId;
+ }
+ // ...otherwise, we need to get the display the test's user is running on
+ int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager);
+ if (DEBUG) {
+ Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")");
+ }
+ return userDisplayId;
+ }
+
+ private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) {
+ if (!userManager.isUserVisible()) {
+ // Should also not happen, but ...
+ Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using "
+ + "DEFAULT_DISPLAY");
+ return DEFAULT_DISPLAY;
+ }
+ return userManager.getMainDisplayIdAssignedToUser();
+ }
+
private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper {
public IAccessibilityServiceClientImpl(Looper looper, int generationId) {
@@ -1621,6 +1724,7 @@ public final class UiAutomation {
if (DEBUG) {
Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken="
+ windowToken + ", user=" + Process.myUserHandle()
+ + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId
+ ", mGenerationId=" + mGenerationId
+ ", UiAutomation.mGenerationId="
+ UiAutomation.this.mGenerationId);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 13e800e38cca..d96a9d104ec2 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -22,6 +22,7 @@ import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceClient;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Bitmap;
@@ -117,7 +118,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
throw new IllegalStateException("Already connected.");
}
mOwningUid = Binder.getCallingUid();
- registerUiTestAutomationServiceLocked(client, flags);
+ registerUiTestAutomationServiceLocked(client,
+ Binder.getCallingUserHandle().getIdentifier(), flags);
storeRotationStateLocked();
}
}
@@ -553,7 +555,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
}
private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client,
- int flags) {
+ @UserIdInt int userId, int flags) {
IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface(
ServiceManager.getService(Context.ACCESSIBILITY_SERVICE));
final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -571,10 +573,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub {
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
- manager.registerUiTestAutomationService(mToken, client, info, flags);
+ manager.registerUiTestAutomationService(mToken, client, info, userId, flags);
mClient = client;
} catch (RemoteException re) {
- throw new IllegalStateException("Error while registering UiTestAutomationService.", re);
+ throw new IllegalStateException("Error while registering UiTestAutomationService for "
+ + "user " + userId + ".", re);
}
}
diff --git a/core/java/android/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java
index 593f73635617..052f670b71bd 100644
--- a/core/java/android/app/admin/DevicePolicyResources.java
+++ b/core/java/android/app/admin/DevicePolicyResources.java
@@ -1802,14 +1802,6 @@ public final class DevicePolicyResources {
PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_TITLE";
/**
- * Text for dialog shown when user tries to open a work app when the work profile is
- * turned off, confirming that the user wants to turn on access to their
- * work apps.
- */
- public static final String UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE =
- PREFIX + "UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE";
-
- /**
* Notification title shown when work profile is credential encrypted and requires
* the user to unlock before it's usable.
*/
diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java
index 30aad965c008..7526a7b2c934 100644
--- a/core/java/android/app/admin/IntentFilterPolicyKey.java
+++ b/core/java/android/app/admin/IntentFilterPolicyKey.java
@@ -28,7 +28,9 @@ import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
+import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -45,6 +47,10 @@ import java.util.Objects;
*/
@SystemApi
public final class IntentFilterPolicyKey extends PolicyKey {
+
+ private static final String TAG = "IntentFilterPolicyKey";
+
+ private static final String TAG_INTENT_FILTER_ENTRY = "filter";
private final IntentFilter mFilter;
/**
@@ -83,7 +89,9 @@ public final class IntentFilterPolicyKey extends PolicyKey {
@Override
public void saveToXml(TypedXmlSerializer serializer) throws IOException {
serializer.attribute(/* namespace= */ null, ATTR_POLICY_IDENTIFIER, getIdentifier());
+ serializer.startTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY);
mFilter.writeToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_INTENT_FILTER_ENTRY);
}
/**
@@ -93,11 +101,27 @@ public final class IntentFilterPolicyKey extends PolicyKey {
public IntentFilterPolicyKey readFromXml(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
String identifier = parser.getAttributeValue(/* namespace= */ null, ATTR_POLICY_IDENTIFIER);
- IntentFilter filter = new IntentFilter();
- filter.readFromXml(parser);
+ IntentFilter filter = readIntentFilterFromXml(parser);
return new IntentFilterPolicyKey(identifier, filter);
}
+ @Nullable
+ private IntentFilter readIntentFilterFromXml(TypedXmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ if (tag.equals(TAG_INTENT_FILTER_ENTRY)) {
+ IntentFilter filter = new IntentFilter();
+ filter.readFromXml(parser);
+ return filter;
+ }
+ Log.e(TAG, "Unknown tag: " + tag);
+ }
+ Log.e(TAG, "Error parsing IntentFilterPolicyKey, IntentFilter not found");
+ return null;
+ }
+
/**
* @hide
*/
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 81fc0293b685..23ba33698739 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -261,14 +261,17 @@ public class UserInfo implements Parcelable {
public boolean guestToRemove;
/**
- * This is used to optimize the creation of an user, i.e. OEMs might choose to pre-create a
+ * This is used to optimize the creation of a user, i.e. OEMs might choose to pre-create a
* number of users at the first boot, so the actual creation later is faster.
*
* <p>A {@code preCreated} user is not a real user yet, so it should not show up on regular
* user operations (other than user creation per se).
*
- * <p>Once the pre-created is used to create a "real" user later on, {@code preCreate} is set to
- * {@code false}.
+ * <p>Once the pre-created is used to create a "real" user later on, {@code preCreated} is set
+ * to {@code false}.
+ *
+ * <p><b>NOTE: Pre-created users are deprecated. This field remains to be able to recognize
+ * pre-created users in older versions, but will eventually be removed.
*/
public boolean preCreated;
@@ -277,6 +280,9 @@ public class UserInfo implements Parcelable {
* user.
*
* <p><b>NOTE: </b>only used for debugging purposes, it's not set when marshalled to a parcel.
+ *
+ * <p><b>NOTE: Pre-created users are deprecated. This field remains to be able to recognize
+ * pre-created users in older versions, but will eventually ve removed.
*/
public boolean convertedFromPreCreated;
diff --git a/core/java/android/content/res/AssetFileDescriptor.java b/core/java/android/content/res/AssetFileDescriptor.java
index d2a6f03cc1a1..ac65933db136 100644
--- a/core/java/android/content/res/AssetFileDescriptor.java
+++ b/core/java/android/content/res/AssetFileDescriptor.java
@@ -21,20 +21,12 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import android.system.ErrnoException;
-import android.system.Os;
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileLock;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
/**
* File descriptor of an entry in the AssetManager. This provides your own
@@ -211,26 +203,19 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
*/
public static class AutoCloseInputStream
extends ParcelFileDescriptor.AutoCloseInputStream {
- /** Size of current file. */
- private long mTotalSize;
- /** The absolute position of current file start point. */
- private final long mFileOffset;
- /** The relative position where input stream is against mFileOffset. */
- private long mOffset;
- private OffsetCorrectFileChannel mOffsetCorrectFileChannel;
+ private long mRemaining;
public AutoCloseInputStream(AssetFileDescriptor fd) throws IOException {
super(fd.getParcelFileDescriptor());
- mTotalSize = fd.getLength();
- mFileOffset = fd.getStartOffset();
+ super.skip(fd.getStartOffset());
+ mRemaining = (int) fd.getLength();
}
@Override
public int available() throws IOException {
- long available = mTotalSize - mOffset;
- return available >= 0
- ? (available < 0x7fffffff ? (int) available : 0x7fffffff)
- : 0;
+ return mRemaining >= 0
+ ? (mRemaining < 0x7fffffff ? (int) mRemaining : 0x7fffffff)
+ : super.available();
}
@Override
@@ -242,24 +227,15 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
@Override
public int read(byte[] buffer, int offset, int count) throws IOException {
- int available = available();
- if (available <= 0) {
- return -1;
- }
-
- if (count > available) count = available;
- try {
- int res = Os.pread(getFD(), buffer, offset, count, mFileOffset + mOffset);
- // pread returns 0 at end of file, while java's InputStream interface requires -1
- if (res == 0) res = -1;
- if (res > 0) {
- mOffset += res;
- updateChannelPosition(mOffset + mFileOffset);
- }
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = (int) mRemaining;
+ int res = super.read(buffer, offset, count);
+ if (res >= 0) mRemaining -= res;
return res;
- } catch (ErrnoException e) {
- throw new IOException(e);
}
+
+ return super.read(buffer, offset, count);
}
@Override
@@ -269,185 +245,41 @@ public class AssetFileDescriptor implements Parcelable, Closeable {
@Override
public long skip(long count) throws IOException {
- int available = available();
- if (available <= 0) {
- return -1;
+ if (mRemaining >= 0) {
+ if (mRemaining == 0) return -1;
+ if (count > mRemaining) count = mRemaining;
+ long res = super.skip(count);
+ if (res >= 0) mRemaining -= res;
+ return res;
}
- if (count > available) count = available;
- mOffset += count;
- updateChannelPosition(mOffset + mFileOffset);
- return count;
+ return super.skip(count);
}
@Override
public void mark(int readlimit) {
- // Not supported.
- return;
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
+ }
+ super.mark(readlimit);
}
@Override
public boolean markSupported() {
- return false;
+ if (mRemaining >= 0) {
+ return false;
+ }
+ return super.markSupported();
}
@Override
public synchronized void reset() throws IOException {
- // Not supported.
- return;
- }
-
- @Override
- public FileChannel getChannel() {
- if (mOffsetCorrectFileChannel == null) {
- mOffsetCorrectFileChannel = new OffsetCorrectFileChannel(super.getChannel());
- }
- try {
- updateChannelPosition(mOffset + mFileOffset);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return mOffsetCorrectFileChannel;
- }
-
- /**
- * Update the position of mOffsetCorrectFileChannel only after it is constructed.
- *
- * @param newPosition The absolute position mOffsetCorrectFileChannel needs to be moved to.
- */
- private void updateChannelPosition(long newPosition) throws IOException {
- if (mOffsetCorrectFileChannel != null) {
- mOffsetCorrectFileChannel.position(newPosition);
- }
- }
-
- /**
- * A FileChannel wrapper that will update mOffset of the AutoCloseInputStream
- * to correct position when using FileChannel to read. All occurrence of position
- * should be using absolute solution and each override method just do Delegation
- * besides additional check. All methods related to write mode have been disabled
- * and will throw UnsupportedOperationException with customized message.
- */
- private class OffsetCorrectFileChannel extends FileChannel {
- private final FileChannel mDelegate;
- private static final String METHOD_NOT_SUPPORTED_MESSAGE =
- "This Method is not supported in AutoCloseInputStream FileChannel.";
-
- OffsetCorrectFileChannel(FileChannel fc) {
- mDelegate = fc;
- }
-
- @Override
- public int read(ByteBuffer dst) throws IOException {
- if (available() <= 0) return -1;
- int bytesRead = mDelegate.read(dst);
- if (bytesRead != -1) mOffset += bytesRead;
- return bytesRead;
- }
-
- @Override
- public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
- if (available() <= 0) return -1;
- if (mOffset + length > mTotalSize) {
- length = (int) (mTotalSize - mOffset);
- }
- long bytesRead = mDelegate.read(dsts, offset, length);
- if (bytesRead != -1) mOffset += bytesRead;
- return bytesRead;
- }
-
- @Override
- /**The only read method that does not move channel position*/
- public int read(ByteBuffer dst, long position) throws IOException {
- if (position - mFileOffset > mTotalSize) return -1;
- return mDelegate.read(dst, position);
- }
-
- @Override
- public long position() throws IOException {
- return mDelegate.position();
- }
-
- @Override
- public FileChannel position(long newPosition) throws IOException {
- mOffset = newPosition - mFileOffset;
- return mDelegate.position(newPosition);
- }
-
- @Override
- public long size() throws IOException {
- return mTotalSize;
- }
-
- @Override
- public long transferTo(long position, long count, WritableByteChannel target)
- throws IOException {
- if (position - mFileOffset > mTotalSize) {
- return 0;
- }
- if (position - mFileOffset + count > mTotalSize) {
- count = mTotalSize - (position - mFileOffset);
- }
- return mDelegate.transferTo(position, count, target);
- }
-
- @Override
- public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
- if (position - mFileOffset > mTotalSize) {
- throw new IOException(
- "Cannot map to buffer because position exceed current file size.");
- }
- if (position - mFileOffset + size > mTotalSize) {
- size = mTotalSize - (position - mFileOffset);
- }
- return mDelegate.map(mode, position, size);
- }
-
- @Override
- protected void implCloseChannel() throws IOException {
- mDelegate.close();
- }
-
- @Override
- public int write(ByteBuffer src) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public int write(ByteBuffer src, long position) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public long transferFrom(ReadableByteChannel src, long position, long count)
- throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public FileChannel truncate(long size) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public void force(boolean metaData) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public FileLock lock(long position, long size, boolean shared) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
- }
-
- @Override
- public FileLock tryLock(long position, long size, boolean shared) throws IOException {
- throw new UnsupportedOperationException(METHOD_NOT_SUPPORTED_MESSAGE);
+ if (mRemaining >= 0) {
+ // Not supported.
+ return;
}
+ super.reset();
}
}
diff --git a/core/java/android/credentials/CredentialOption.java b/core/java/android/credentials/CredentialOption.java
index e933123d08b8..df948f17d6e0 100644
--- a/core/java/android/credentials/CredentialOption.java
+++ b/core/java/android/credentials/CredentialOption.java
@@ -37,8 +37,7 @@ import java.util.Set;
/**
* Information about a specific type of credential to be requested during a {@link
- * CredentialManager#getCredential(GetCredentialRequest, Activity, CancellationSignal, Executor,
- * OutcomeReceiver)} operation.
+ * CredentialManager#getCredential} operation.
*/
public final class CredentialOption implements Parcelable {
@@ -196,9 +195,8 @@ public final class CredentialOption implements Parcelable {
* @throws NullPointerException If {@code credentialRetrievalData}, or
* {@code candidateQueryData} is null.
*
- * @deprecated replaced by Builder
+ * @hide
*/
- @Deprecated
public CredentialOption(
@NonNull String type,
@NonNull Bundle credentialRetrievalData,
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
index 852934a808e2..629d578c7358 100644
--- a/core/java/android/credentials/ui/CreateCredentialProviderData.java
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -19,6 +19,7 @@ package android.credentials.ui;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,7 +36,7 @@ import java.util.List;
@TestApi
public final class CreateCredentialProviderData extends ProviderData implements Parcelable {
@NonNull
- private final List<Entry> mSaveEntries;
+ private final ParceledListSlice<Entry> mSaveEntries;
@Nullable
private final Entry mRemoteEntry;
@@ -43,13 +44,13 @@ public final class CreateCredentialProviderData extends ProviderData implements
@NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
@Nullable Entry remoteEntry) {
super(providerFlattenedComponentName);
- mSaveEntries = saveEntries;
+ mSaveEntries = new ParceledListSlice<>(saveEntries);
mRemoteEntry = remoteEntry;
}
@NonNull
public List<Entry> getSaveEntries() {
- return mSaveEntries;
+ return mSaveEntries.getList();
}
@Nullable
@@ -60,9 +61,7 @@ public final class CreateCredentialProviderData extends ProviderData implements
private CreateCredentialProviderData(@NonNull Parcel in) {
super(in);
- List<Entry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, Entry.CREATOR);
- mSaveEntries = credentialEntries;
+ mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -72,7 +71,7 @@ public final class CreateCredentialProviderData extends ProviderData implements
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeTypedList(mSaveEntries);
+ dest.writeParcelable(mSaveEntries, flags);
dest.writeTypedObject(mRemoteEntry, flags);
}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
index e4688a84a3fb..773dee97f7fe 100644
--- a/core/java/android/credentials/ui/GetCredentialProviderData.java
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -19,6 +19,7 @@ package android.credentials.ui;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,11 +36,11 @@ import java.util.List;
@TestApi
public final class GetCredentialProviderData extends ProviderData implements Parcelable {
@NonNull
- private final List<Entry> mCredentialEntries;
+ private final ParceledListSlice<Entry> mCredentialEntries;
@NonNull
- private final List<Entry> mActionChips;
+ private final ParceledListSlice<Entry> mActionChips;
@NonNull
- private final List<AuthenticationEntry> mAuthenticationEntries;
+ private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries;
@Nullable
private final Entry mRemoteEntry;
@@ -49,25 +50,25 @@ public final class GetCredentialProviderData extends ProviderData implements Par
@NonNull List<AuthenticationEntry> authenticationEntries,
@Nullable Entry remoteEntry) {
super(providerFlattenedComponentName);
- mCredentialEntries = credentialEntries;
- mActionChips = actionChips;
- mAuthenticationEntries = authenticationEntries;
+ mCredentialEntries = new ParceledListSlice<>(credentialEntries);
+ mActionChips = new ParceledListSlice<>(actionChips);
+ mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries);
mRemoteEntry = remoteEntry;
}
@NonNull
public List<Entry> getCredentialEntries() {
- return mCredentialEntries;
+ return mCredentialEntries.getList();
}
@NonNull
public List<Entry> getActionChips() {
- return mActionChips;
+ return mActionChips.getList();
}
@NonNull
public List<AuthenticationEntry> getAuthenticationEntries() {
- return mAuthenticationEntries;
+ return mAuthenticationEntries.getList();
}
@Nullable
@@ -77,20 +78,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par
private GetCredentialProviderData(@NonNull Parcel in) {
super(in);
-
- List<Entry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, Entry.CREATOR);
- mCredentialEntries = credentialEntries;
+ mCredentialEntries = in.readParcelable(null,
+ android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
- List<Entry> actionChips = new ArrayList<>();
- in.readTypedList(actionChips, Entry.CREATOR);
- mActionChips = actionChips;
+ mActionChips = in.readParcelable(null,
+ android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mActionChips);
- List<AuthenticationEntry> authenticationEntries = new ArrayList<>();
- in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR);
- mAuthenticationEntries = authenticationEntries;
+ mAuthenticationEntries = in.readParcelable(null,
+ android.content.pm.ParceledListSlice.class);
AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries);
Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
@@ -100,9 +97,9 @@ public final class GetCredentialProviderData extends ProviderData implements Par
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
super.writeToParcel(dest, flags);
- dest.writeTypedList(mCredentialEntries);
- dest.writeTypedList(mActionChips);
- dest.writeTypedList(mAuthenticationEntries);
+ dest.writeParcelable(mCredentialEntries, flags);
+ dest.writeParcelable(mActionChips, flags);
+ dest.writeParcelable(mAuthenticationEntries, flags);
dest.writeTypedObject(mRemoteEntry, flags);
}
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index fa678fc5ee1a..2e40f6096ccb 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -142,6 +142,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
private PromptInfo mPromptInfo;
private ButtonInfo mNegativeButtonInfo;
private Context mContext;
+ private IAuthService mService;
/**
* Creates a builder for a {@link BiometricPrompt} dialog.
@@ -212,6 +213,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * @param service
+ * @return This builder.
+ * @hide
+ */
+ @RequiresPermission(TEST_BIOMETRIC)
+ @NonNull
+ public Builder setService(@NonNull IAuthService service) {
+ mService = service;
+ return this;
+ }
+
+ /**
* Sets an optional title, subtitle, and/or description that will override other text when
* the user is authenticating with PIN/pattern/password. Currently for internal use only.
* @return This builder.
@@ -472,7 +485,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
throw new IllegalArgumentException("Can't have both negative button behavior"
+ " and device credential enabled");
}
- return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo);
+ mService = (mService == null) ? IAuthService.Stub.asInterface(
+ ServiceManager.getService(Context.AUTH_SERVICE)) : mService;
+ return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService);
}
}
@@ -521,7 +536,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
public void onAuthenticationFailed() {
mExecutor.execute(() -> {
mAuthenticationCallback.onAuthenticationFailed();
- mIsPromptShowing = false;
});
}
@@ -604,12 +618,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
private boolean mIsPromptShowing;
- private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) {
+ private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo,
+ IAuthService service) {
mContext = context;
mPromptInfo = promptInfo;
mNegativeButtonInfo = negativeButtonInfo;
- mService = IAuthService.Stub.asInterface(
- ServiceManager.getService(Context.AUTH_SERVICE));
+ mService = service;
mIsPromptShowing = false;
}
diff --git a/core/java/android/hardware/biometrics/ComponentInfoInternal.java b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
index 3b61a56bd9f1..2e708de21762 100644
--- a/core/java/android/hardware/biometrics/ComponentInfoInternal.java
+++ b/core/java/android/hardware/biometrics/ComponentInfoInternal.java
@@ -19,6 +19,8 @@ package android.hardware.biometrics;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
/**
* The internal class for storing the component info for a subsystem of the biometric sensor,
@@ -90,12 +92,19 @@ public class ComponentInfoInternal implements Parcelable {
dest.writeString(softwareVersion);
}
- @Override
- public String toString() {
- return "ComponentId: " + componentId
- + ", HardwareVersion: " + hardwareVersion
- + ", FirmwareVersion: " + firmwareVersion
- + ", SerialNumber " + serialNumber
- + ", SoftwareVersion: " + softwareVersion;
+ /**
+ * Print the component info into the given stream.
+ *
+ * @param pw The stream to dump the info into.
+ * @hide
+ */
+ public void dump(@NonNull IndentingPrintWriter pw) {
+ pw.println(TextUtils.formatSimple("componentId: %s", componentId));
+ pw.increaseIndent();
+ pw.println(TextUtils.formatSimple("hardwareVersion: %s", hardwareVersion));
+ pw.println(TextUtils.formatSimple("firmwareVersion: %s", firmwareVersion));
+ pw.println(TextUtils.formatSimple("serialNumber: %s", serialNumber));
+ pw.println(TextUtils.formatSimple("softwareVersion: %s", softwareVersion));
+ pw.decreaseIndent();
}
}
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index c88af5aad738..1a38c8897b76 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -58,10 +58,10 @@ interface IBiometricService {
boolean hasEnrolledBiometrics(int userId, String opPackageName);
// Registers an authenticator (e.g. face, fingerprint, iris).
- // Id must be unique, whereas strength and modality don't need to be.
+ // Sensor Id in sensor props must be unique, whereas modality doesn't need to be.
// TODO(b/123321528): Turn strength and modality into enums.
@EnforcePermission("USE_BIOMETRIC_INTERNAL")
- void registerAuthenticator(int id, int modality, int strength,
+ void registerAuthenticator(int modality, in SensorPropertiesInternal props,
IBiometricAuthenticator authenticator);
// Register callback for when keyguard biometric eligibility changes.
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index 5feda785ece3..ad68866571e3 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -1310,6 +1310,10 @@ public abstract class CameraDevice implements AutoCloseable {
* {@link Surface}, submitting a reprocess {@link CaptureRequest} with multiple
* output targets will result in a {@link CaptureFailure}.
*
+ * From Android 14 onward, {@link CaptureRequest#CONTROL_CAPTURE_INTENT} will be set to
+ * {@link CameraMetadata#CONTROL_CAPTURE_INTENT_STILL_CAPTURE} by default. Prior to Android 14,
+ * apps will need to explicitly set this key themselves.
+ *
* @param inputResult The capture result of the output image or one of the output images used
* to generate the reprocess input image for this capture request.
*
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index cb1efe8c2b55..f2d8caaab0e7 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -26,6 +26,7 @@ import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraExtensionCharacteristics;
+import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CameraOfflineSession;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
@@ -861,8 +862,13 @@ public class CameraDeviceImpl extends CameraDevice
CameraMetadataNative resultMetadata = new
CameraMetadataNative(inputResult.getNativeCopy());
- return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true,
- inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null);
+ CaptureRequest.Builder builder = new CaptureRequest.Builder(resultMetadata,
+ /*reprocess*/true, inputResult.getSessionId(), getId(),
+ /*physicalCameraIdSet*/ null);
+ builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+ CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE);
+
+ return builder;
}
}
diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
index dcc336966810..466373030c78 100644
--- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
+++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl
@@ -42,12 +42,6 @@ oneway interface IRecognitionStatusCallback {
void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent);
/**
- * Called when the detection fails due to an error.
- *
- * @param status The error code that was seen.
- */
- void onError(int status);
- /**
* Called when the recognition is paused temporarily for some reason.
*/
void onRecognitionPaused();
@@ -55,4 +49,28 @@ oneway interface IRecognitionStatusCallback {
* Called when the recognition is resumed after it was temporarily paused.
*/
void onRecognitionResumed();
+
+ // Error callbacks to follow
+ /**
+ * Called when this recognition has been preempted by another.
+ */
+ void onPreempted();
+
+ /**
+ * Called when the underlying ST module service has died.
+ */
+ void onModuleDied();
+
+ /**
+ * Called when the service failed to gracefully resume recognition following a pause.
+ * @param status - The received error code.
+ */
+ void onResumeFailed(int status);
+
+ /**
+ * Called when the service failed to pause recognition when required.
+ * TODO(b/276507281) Remove. This should never happen, so we should abort instead.
+ * @param status - The received error code.
+ */
+ void onPauseFailed(int status);
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index fa16e167f7d1..6d43ddf7fe94 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1051,6 +1051,29 @@ public class SoundTrigger {
return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]";
}
}
+ /**
+ * SoundTrigger model parameter types.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = {
+ MODEL_PARAM_INVALID,
+ MODEL_PARAM_THRESHOLD_FACTOR
+ })
+ public @interface ModelParamTypes {}
+
+ /**
+ * See {@link ModelParams.INVALID}
+ * @hide
+ */
+ @TestApi
+ public static final int MODEL_PARAM_INVALID = ModelParams.INVALID;
+ /**
+ * See {@link ModelParams.THRESHOLD_FACTOR}
+ * @hide
+ */
+ @TestApi
+ public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR;
/**
* Modes for key phrase recognition
@@ -1450,7 +1473,8 @@ public class SoundTrigger {
*
* @hide
*/
- public static class RecognitionConfig implements Parcelable {
+ @TestApi
+ public static final class RecognitionConfig implements Parcelable {
/** True if the DSP should capture the trigger sound and make it available for further
* capture. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -1464,6 +1488,7 @@ public class SoundTrigger {
* options for each keyphrase. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@NonNull
+ @SuppressLint("ArrayReturn")
public final KeyphraseRecognitionExtra keyphrases[];
/** Opaque data for use by system applications who know about voice engine internals,
* typically during enrollment. */
@@ -1479,8 +1504,8 @@ public class SoundTrigger {
public final int audioCapabilities;
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
- int audioCapabilities) {
+ @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+ @Nullable byte[] data, int audioCapabilities) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
@@ -1490,7 +1515,8 @@ public class SoundTrigger {
@UnsupportedAppUsage
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases,
+ @Nullable byte[] data) {
this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
@@ -1517,7 +1543,7 @@ public class SoundTrigger {
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeByte((byte) (captureRequested ? 1 : 0));
dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
dest.writeTypedArray(keyphrases, flags);
diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java
index 15ed45041d32..24d1c9577f82 100644
--- a/core/java/android/inputmethodservice/InkWindow.java
+++ b/core/java/android/inputmethodservice/InkWindow.java
@@ -195,6 +195,7 @@ final class InkWindow extends PhoneWindow {
Objects.requireNonNull(decor);
final ViewRootImpl viewRoot = decor.getViewRootImpl();
Objects.requireNonNull(viewRoot);
- viewRoot.enqueueInputEvent(event);
+ // The view root will own the event that we enqueue, so provide a copy of the event.
+ viewRoot.enqueueInputEvent(MotionEvent.obtain(event));
}
}
diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java
index f19d30258393..2240fe7f7d3b 100644
--- a/core/java/android/nfc/tech/NdefFormatable.java
+++ b/core/java/android/nfc/tech/NdefFormatable.java
@@ -124,6 +124,9 @@ public final class NdefFormatable extends BasicTagTechnology {
try {
int serviceHandle = mTag.getServiceHandle();
INfcTag tagService = mTag.getTagService();
+ if (tagService == null) {
+ throw new IOException();
+ }
int errorCode = tagService.formatNdef(serviceHandle, MifareClassic.KEY_DEFAULT);
switch (errorCode) {
case ErrorCodes.SUCCESS:
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 244632a87593..7383e633fb93 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -493,7 +493,7 @@ public class Build {
* @hide
*/
@TestApi
- public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length;
+ public static final int RESOURCES_SDK_INT = SDK_INT;
/**
* The current lowest supported value of app target SDK. Applications targeting
@@ -1222,7 +1222,7 @@ public class Build {
/**
* Upside Down Cake.
*/
- public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT;
+ public static final int UPSIDE_DOWN_CAKE = 34;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index b3604da49f5e..24e28e95cd98 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -3706,17 +3706,24 @@ public class UserManager {
* {@link android.Manifest.permission#CREATE_USERS} suffices if flags are in
* com.android.server.pm.UserManagerService#ALLOWED_FLAGS_FOR_CREATE_USERS_PERMISSION.
*
+ *
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
* @return the {@link UserInfo} object for the created user.
*
* @throws UserOperationException if the user could not be created.
+ *
+ * @deprecated Pre-created users are deprecated. This method should no longer be used, and will
+ * be removed once all the callers are removed.
+ *
* @hide
*/
+ @Deprecated
@TestApi
@RequiresPermission(anyOf = {Manifest.permission.MANAGE_USERS,
Manifest.permission.CREATE_USERS})
public @NonNull UserInfo preCreateUser(@NonNull String userType)
throws UserOperationException {
+ Log.w(TAG, "preCreateUser(): Pre-created user is deprecated.");
try {
return mService.preCreateUserWithThrow(userType);
} catch (ServiceSpecificException e) {
@@ -4296,8 +4303,12 @@ public class UserManager {
/**
* Returns information for all users on this device, based on the filtering parameters.
*
+ * @deprecated Pre-created users are deprecated and no longer supported.
+ * Use {@link #getUsers()}, {@link #getUsers(boolean)}, or {@link #getAliveUsers()}
+ * instead.
* @hide
*/
+ @Deprecated
@TestApi
@RequiresPermission(anyOf = {
android.Manifest.permission.MANAGE_USERS,
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 63259edeae7d..ecce2ff8e960 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -202,6 +202,13 @@ public class DynamicSystemClient {
public static final String ACTION_NOTIFY_IF_IN_USE =
"android.os.image.action.NOTIFY_IF_IN_USE";
+ /**
+ * Intent action: hide notifications about the status of {@code DynamicSystem}.
+ * @hide
+ */
+ public static final String ACTION_HIDE_NOTIFICATION =
+ "android.os.image.action.HIDE_NOTIFICATION";
+
/*
* Intent Keys
*/
@@ -217,6 +224,19 @@ public class DynamicSystemClient {
*/
public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE";
+ /**
+ * Intent key: Whether to enable DynamicSystem immediately after installation is done.
+ * Note this will reboot the device automatically.
+ * @hide
+ */
+ public static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+
+ /**
+ * Intent key: Whether to leave DynamicSystem on device reboot.
+ * False indicates a sticky mode where device stays in DynamicSystem across reboots.
+ * @hide
+ */
+ public static final String KEY_ONE_SHOT = "KEY_ONE_SHOT";
private static class IncomingHandler extends Handler {
private final WeakReference<DynamicSystemClient> mWeakClient;
diff --git a/core/java/android/permission/OWNERS b/core/java/android/permission/OWNERS
index d34b45bf1ff1..4603e43fd164 100644
--- a/core/java/android/permission/OWNERS
+++ b/core/java/android/permission/OWNERS
@@ -1,18 +1,19 @@
# Bug component: 137825
-evanseverson@google.com
-evanxinchen@google.com
ashfall@google.com
-guojing@google.com
+augale@google.com
+evanseverson@google.com
+fayey@google.com
jaysullivan@google.com
+joecastro@google.com
kvakil@google.com
mrulhania@google.com
narayan@google.com
ntmyren@google.com
olekarg@google.com
pyuli@google.com
-raphk@google.com
rmacgregor@google.com
sergeynv@google.com
theianchen@google.com
+yutingfang@google.com
zhanghai@google.com
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c473d3f81823..aa5a0d0c162b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2585,7 +2585,7 @@ public final class Settings {
* <p>
* To start an activity with this intent, apps should set the wellbeing package explicitly in
* the intent together with this action. The wellbeing package is defined in
- * {@code com.android.internal.R.string.config_defaultWellbeingPackage}.
+ * {@code com.android.internal.R.string.config_systemWellbeing}.
* <p>
* Output: Nothing
*
@@ -3431,7 +3431,7 @@ public final class Settings {
+ " type:" + mUri.getPath()
+ " in package:" + cr.getPackageName());
}
- for (int i = 0; i < mValues.size(); ++i) {
+ for (int i = mValues.size() - 1; i >= 0; i--) {
String key = mValues.keyAt(i);
if (key.startsWith(prefix)) {
mValues.remove(key);
@@ -18125,12 +18125,6 @@ public final class Settings {
public static final String WEAR_OS_VERSION_STRING = "wear_os_version_string";
/**
- * Whether the physical button has been set.
- * @hide
- */
- public static final String BUTTON_SET = "button_set";
-
- /**
* Whether there is a side button.
* @hide
*/
@@ -18302,6 +18296,12 @@ public final class Settings {
public static final int COMPANION_OS_VERSION_UNDEFINED = -1;
/**
+ * The companion App name.
+ * @hide
+ */
+ public static final String COMPANION_APP_NAME = "wear_companion_app_name";
+
+ /**
* A boolean value to indicate if we want to support all languages in LE edition on
* wear. 1 for supporting, 0 for not supporting.
* @hide
@@ -18413,10 +18413,13 @@ public final class Settings {
public static final String BURN_IN_PROTECTION_ENABLED = "burn_in_protection";
/**
-
* Whether the device has combined location setting enabled.
+ *
+ * @deprecated Use LocationManager as the source of truth for all location states.
+ *
* @hide
*/
+ @Deprecated
public static final String COMBINED_LOCATION_ENABLED = "combined_location_enable";
/**
@@ -18482,67 +18485,36 @@ public final class Settings {
public static final String CLOCKWORK_LONG_PRESS_TO_ASSISTANT_ENABLED =
"clockwork_long_press_to_assistant_enabled";
- /*
+ /**
* Whether the device has Cooldown Mode enabled.
* @hide
*/
public static final String COOLDOWN_MODE_ON = "cooldown_mode_on";
- /*
+ /**
* Whether the device has Wet Mode/ Touch Lock Mode enabled.
* @hide
*/
public static final String WET_MODE_ON = "wet_mode_on";
- /*
+ /**
* Whether the RSB wake feature is enabled.
* @hide
*/
public static final String RSB_WAKE_ENABLED = "rsb_wake_enabled";
- /*
+ /**
* Whether the screen-unlock (keyguard) sound is enabled.
* @hide
*/
public static final String SCREEN_UNLOCK_SOUND_ENABLED = "screen_unlock_sound_enabled";
- /*
+ /**
* Whether charging sounds are enabled.
* @hide
*/
public static final String CHARGING_SOUNDS_ENABLED = "wear_charging_sounds_enabled";
- /** The status of the early updates process.
- * @hide
- */
- public static final String EARLY_UPDATES_STATUS = "early_updates_status";
-
- /**
- * Early updates not started
- * @hide
- */
- public static final int EARLY_UPDATES_STATUS_NOT_STARTED = 0;
- /**
- * Early updates started and in progress
- * @hide
- */
- public static final int EARLY_UPDATES_STATUS_STARTED = 1;
- /**
- * Early updates completed and was successful
- * @hide
- */
- public static final int EARLY_UPDATES_STATUS_SUCCESS = 2;
- /**
- * Early updates skipped
- * @hide
- */
- public static final int EARLY_UPDATES_STATUS_SKIPPED = 3;
- /**
- * Early updates aborted due to timeout
- * @hide
- */
- public static final int EARLY_UPDATES_STATUS_ABORTED = 4;
-
/**
* Whether dynamic color theming (e.g. Material You) is enabled for apps which support
* it.
@@ -18669,6 +18641,174 @@ public final class Settings {
* @hide
*/
public static final int UPGRADE_DATA_MIGRATION_DONE = 2;
+
+ /**
+ * Whether to disable AOD while plugged.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String DISABLE_AOD_WHILE_PLUGGED = "disable_aod_while_plugged";
+
+ /**
+ * Whether the user has consented for network location provider (NLP).
+ * This setting key will only be used once during OOBE to set NLP initial value through
+ * the companion app ToS. This setting key will be synced over from Companion and
+ * corresponding toggle in GMS will be enabled.
+ * @hide
+ */
+ public static final String NETWORK_LOCATION_OPT_IN = "network_location_opt_in";
+
+ /**
+ * The custom foreground color.
+ * @hide
+ */
+ public static final String CUSTOM_COLOR_FOREGROUND = "custom_foreground_color";
+
+ /**
+ * The custom background color.
+ * @hide
+ */
+ public static final String CUSTOM_COLOR_BACKGROUND = "custom_background_color";
+
+ /** The status of the phone switching process.
+ * @hide
+ */
+ public static final String PHONE_SWITCHING_STATUS = "phone_switching_status";
+
+ /**
+ * Phone switching not started
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_NOT_STARTED = 0;
+
+ /**
+ * Phone switching started
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_STARTED = 1;
+
+ /**
+ * Phone switching completed and was successful
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_SUCCESS = 2;
+
+ /**
+ * Phone switching was cancelled
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_CANCELLED = 3;
+
+ /**
+ * Phone switching failed
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_FAILED = 4;
+
+ /**
+ * Phone switching is in progress of advertising to new companion device.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_ADVERTISING = 5;
+
+ /**
+ * Phone switching successfully bonded with new companion device.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_BONDED = 6;
+
+ /**
+ * Phone switching successfully completed on phone side.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_PHONE_COMPLETE = 7;
+
+ /**
+ * Connection config migration in progress.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION = 8;
+
+ /**
+ * Connection config migration failed.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_FAILED = 9;
+
+ /**
+ * Connection config migration cancellation in progress.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_CANCELLED = 10;
+
+ /**
+ * Connection config migration success.
+ * @hide
+ */
+ public static final int PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS = 11;
+
+
+ /**
+ * Whether the device has enabled the feature to reduce motion and animation
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String REDUCE_MOTION = "reduce_motion";
+
+ /**
+ * Whether RTL swipe-to-dismiss is enabled by developer options.
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String RTL_SWIPE_TO_DISMISS_ENABLED_DEV =
+ "rtl_swipe_to_dismiss_enabled_dev";
+
+ /**
+ * Tethered Configuration state.
+ * @hide
+ */
+ public static final String TETHER_CONFIG_STATE = "tethered_config_state";
+
+ /**
+ * Tethered configuration state is unknown.
+ * @hide
+ */
+ public static final int TETHERED_CONFIG_UNKNOWN = 0;
+
+ /**
+ * Device is set into standalone mode.
+ * @hide
+ */
+ public static final int TETHERED_CONFIG_STANDALONE = 1;
+
+ /**
+ * Device is set in tethered mode.
+ * @hide
+ */
+ public static final int TETHERED_CONFIG_TETHERED = 2;
+
+
+ /**
+ * Whether phone switching is supported.
+ *
+ * (0 = false, 1 = true)
+ * @hide
+ */
+ public static final String PHONE_SWITCHING_SUPPORTED = "phone_switching_supported";
+
+ /**
+ * Setting indicating the name of the Wear OS package that hosts the Media Controls UI.
+ *
+ * @hide
+ */
+ public static final String WEAR_MEDIA_CONTROLS_PACKAGE = "wear_media_controls_package";
+
+ /**
+ * Setting indicating the name of the Wear OS package responsible for bridging media.
+ *
+ * @hide
+ */
+ public static final String WEAR_MEDIA_SESSIONS_PACKAGE = "wear_media_sessions_package";
}
}
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index d2f9ff01ca98..59b945c9c9a4 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -2051,6 +2051,14 @@ public final class Telephony {
* <P>Type: TEXT</P>
*/
public static final String ADDRESS = "address";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
/**
@@ -2119,6 +2127,14 @@ public final class Telephony {
* <P>Type: INTEGER (boolean)</P>
*/
public static final String ARCHIVED = "archived";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
/**
@@ -2477,6 +2493,14 @@ public final class Telephony {
public static final String CHARSET = "charset";
/**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
* Generates a Addr {@link Uri} for message, used to perform Addr table operation
* for mms.
*
@@ -2597,6 +2621,14 @@ public final class Telephony {
public static final String TEXT = "text";
/**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
+
+ /**
* Generates a Part {@link Uri} for message, used to perform Part table operation
* for mms.
*
@@ -2635,6 +2667,14 @@ public final class Telephony {
* <P>Type: INTEGER (long)</P>
*/
public static final String SENT_TIME = "sent_time";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
/**
@@ -2868,6 +2908,14 @@ public final class Telephony {
* <P>Type: TEXT</P>
*/
public static final String INDEXED_TEXT = "index_text";
+
+ /**
+ * The subscription to which the message belongs to. Its value will be less than 0
+ * if the sub id cannot be determined.
+ * <p>Type: INTEGER (long) </p>
+ * @hide
+ */
+ public static final String SUBSCRIPTION_ID = "sub_id";
}
}
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index cd53cb6afc71..df934335e49d 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,7 +34,7 @@ import java.util.Objects;
* Response to a {@link BeginCreateCredentialRequest}.
*/
public final class BeginCreateCredentialResponse implements Parcelable {
- private final @NonNull List<CreateEntry> mCreateEntries;
+ private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries;
private final @Nullable RemoteEntry mRemoteCreateEntry;
/**
@@ -41,19 +42,19 @@ public final class BeginCreateCredentialResponse implements Parcelable {
* to return.
*/
public BeginCreateCredentialResponse() {
- this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null);
+ this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+ /*remoteCreateEntry=*/null);
}
private BeginCreateCredentialResponse(@NonNull Parcel in) {
- List<CreateEntry> createEntries = new ArrayList<>();
- in.readTypedList(createEntries, CreateEntry.CREATOR);
- mCreateEntries = createEntries;
+ mCreateEntries = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mCreateEntries);
+ dest.writeParcelable(mCreateEntries, flags);
dest.writeTypedObject(mRemoteCreateEntry, flags);
}
@@ -76,7 +77,7 @@ public final class BeginCreateCredentialResponse implements Parcelable {
};
/* package-private */ BeginCreateCredentialResponse(
- @NonNull List<CreateEntry> createEntries,
+ @NonNull ParceledListSlice<CreateEntry> createEntries,
@Nullable RemoteEntry remoteCreateEntry) {
this.mCreateEntries = createEntries;
com.android.internal.util.AnnotationValidations.validate(
@@ -86,7 +87,7 @@ public final class BeginCreateCredentialResponse implements Parcelable {
/** Returns the list of create entries to be displayed on the UI. */
public @NonNull List<CreateEntry> getCreateEntries() {
- return mCreateEntries;
+ return mCreateEntries.getList();
}
/** Returns the remote create entry to be displayed on the UI. */
@@ -159,7 +160,9 @@ public final class BeginCreateCredentialResponse implements Parcelable {
* Builds a new instance of {@link BeginCreateCredentialResponse}.
*/
public @NonNull BeginCreateCredentialResponse build() {
- return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry);
+ return new BeginCreateCredentialResponse(
+ new ParceledListSlice<>(mCreateEntries),
+ mRemoteCreateEntry);
}
}
}
diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java
index e25b6869605d..5ed06ac1ade7 100644
--- a/core/java/android/service/credentials/BeginGetCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java
@@ -20,6 +20,7 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.content.pm.ParceledListSlice;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,13 +36,13 @@ import java.util.Objects;
*/
public final class BeginGetCredentialResponse implements Parcelable {
/** List of credential entries to be displayed on the UI. */
- private final @NonNull List<CredentialEntry> mCredentialEntries;
+ private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries;
/** List of authentication entries to be displayed on the UI. */
- private final @NonNull List<Action> mAuthenticationEntries;
+ private final @NonNull ParceledListSlice<Action> mAuthenticationEntries;
/** List of provider actions to be displayed on the UI. */
- private final @NonNull List<Action> mActions;
+ private final @NonNull ParceledListSlice<Action> mActions;
/** Remote credential entry to get the response from a different device. */
private final @Nullable RemoteEntry mRemoteCredentialEntry;
@@ -51,31 +52,30 @@ public final class BeginGetCredentialResponse implements Parcelable {
* or {@link Action} to return.
*/
public BeginGetCredentialResponse() {
- this(/*credentialEntries=*/new ArrayList<>(),
- /*authenticationActions=*/new ArrayList<>(),
- /*actions=*/new ArrayList<>(),
+ this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+ /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()),
+ /*actions=*/new ParceledListSlice<>(new ArrayList<>()),
/*remoteCredentialEntry=*/null);
}
- private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries,
- @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions,
+ private BeginGetCredentialResponse(
+ @NonNull ParceledListSlice<CredentialEntry> credentialEntries,
+ @NonNull ParceledListSlice<Action> authenticationEntries,
+ @NonNull ParceledListSlice<Action> actions,
@Nullable RemoteEntry remoteCredentialEntry) {
- mCredentialEntries = new ArrayList<>(credentialEntries);
- mAuthenticationEntries = new ArrayList<>(authenticationEntries);
- mActions = new ArrayList<>(actions);
+ mCredentialEntries = credentialEntries;
+ mAuthenticationEntries = authenticationEntries;
+ mActions = actions;
mRemoteCredentialEntry = remoteCredentialEntry;
}
private BeginGetCredentialResponse(@NonNull Parcel in) {
- List<CredentialEntry> credentialEntries = new ArrayList<>();
- in.readTypedList(credentialEntries, CredentialEntry.CREATOR);
- mCredentialEntries = credentialEntries;
- List<Action> authenticationEntries = new ArrayList<>();
- in.readTypedList(authenticationEntries, Action.CREATOR);
- mAuthenticationEntries = authenticationEntries;
- List<Action> actions = new ArrayList<>();
- in.readTypedList(actions, Action.CREATOR);
- mActions = actions;
+ mCredentialEntries = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
+ mAuthenticationEntries = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
+ mActions = in.readParcelable(
+ null, android.content.pm.ParceledListSlice.class);
mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR);
}
@@ -99,9 +99,9 @@ public final class BeginGetCredentialResponse implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeTypedList(mCredentialEntries, flags);
- dest.writeTypedList(mAuthenticationEntries, flags);
- dest.writeTypedList(mActions, flags);
+ dest.writeParcelable(mCredentialEntries, flags);
+ dest.writeParcelable(mAuthenticationEntries, flags);
+ dest.writeParcelable(mActions, flags);
dest.writeTypedObject(mRemoteCredentialEntry, flags);
}
@@ -109,21 +109,22 @@ public final class BeginGetCredentialResponse implements Parcelable {
* Returns the list of credential entries to be displayed on the UI.
*/
public @NonNull List<CredentialEntry> getCredentialEntries() {
- return mCredentialEntries;
+ return mCredentialEntries.getList();
}
/**
* Returns the list of authentication entries to be displayed on the UI.
*/
public @NonNull List<Action> getAuthenticationActions() {
- return mAuthenticationEntries;
+ return mAuthenticationEntries.getList();
}
/**
* Returns the list of actions to be displayed on the UI.
*/
public @NonNull List<Action> getActions() {
- return mActions;
+
+ return mActions.getList();
}
/**
@@ -268,8 +269,11 @@ public final class BeginGetCredentialResponse implements Parcelable {
* Builds a {@link BeginGetCredentialResponse} instance.
*/
public @NonNull BeginGetCredentialResponse build() {
- return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries,
- mActions, mRemoteCredentialEntry);
+ return new BeginGetCredentialResponse(
+ new ParceledListSlice<>(mCredentialEntries),
+ new ParceledListSlice<>(mAuthenticationEntries),
+ new ParceledListSlice<>(mActions),
+ mRemoteCredentialEntry);
}
}
}
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index d2a4a660452c..fb2f4ad2abe6 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -42,7 +42,6 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.util.Slog;
import android.util.Xml;
@@ -54,7 +53,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -181,7 +179,8 @@ public final class CredentialProviderInfoFactory {
if (disableSystemAppVerificationForTests) {
Bundle metadata = serviceInfo.metaData;
if (metadata == null) {
- Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo);
+ Slog.w(TAG, "metadata is null while reading "
+ + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + serviceInfo);
return false;
}
return metadata.getBoolean(
@@ -200,7 +199,7 @@ public final class CredentialProviderInfoFactory {
// 1. Get the metadata for the service.
final Bundle metadata = serviceInfo.metaData;
if (metadata == null) {
- Log.i(TAG, "populateMetadata - metadata is null");
+ Slog.w(TAG, "Metadata is null for provider: " + serviceInfo.getComponentName());
return builder;
}
@@ -209,12 +208,13 @@ public final class CredentialProviderInfoFactory {
try {
resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Failed to get app resources", e);
+ Slog.e(TAG, "Failed to get app resources", e);
}
// 3. Stop if we are missing data.
- if (metadata == null || resources == null) {
- Log.i(TAG, "populateMetadata - resources is null");
+ if (resources == null) {
+ Slog.w(TAG, "Resources are null for the serviceInfo being processed: "
+ + serviceInfo.getComponentName());
return builder;
}
@@ -222,7 +222,7 @@ public final class CredentialProviderInfoFactory {
try {
builder = extractXmlMetadata(context, builder, serviceInfo, pm, resources);
} catch (Exception e) {
- Log.e(TAG, "Failed to get XML metadata", e);
+ Slog.e(TAG, "Failed to get XML metadata", e);
}
return builder;
@@ -259,7 +259,7 @@ public final class CredentialProviderInfoFactory {
afsAttributes.getString(
R.styleable.CredentialProvider_settingsSubtitle));
} catch (Exception e) {
- Log.e(TAG, "Failed to get XML attr", e);
+ Slog.e(TAG, "Failed to get XML attr", e);
} finally {
if (afsAttributes != null) {
afsAttributes.recycle();
@@ -267,10 +267,10 @@ public final class CredentialProviderInfoFactory {
}
builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources));
} else {
- Log.e(TAG, "Meta-data does not start with credential-provider-service tag");
+ Slog.w(TAG, "Meta-data does not start with credential-provider-service tag");
}
} catch (IOException | XmlPullParserException e) {
- Log.e(TAG, "Error parsing credential provider service meta-data", e);
+ Slog.e(TAG, "Error parsing credential provider service meta-data", e);
}
return builder;
@@ -329,7 +329,7 @@ public final class CredentialProviderInfoFactory {
return si;
}
} catch (RemoteException e) {
- Slog.v(TAG, e.getMessage());
+ Slog.e(TAG, "Unable to get serviceInfo", e);
}
throw new PackageManager.NameNotFoundException(serviceComponent.toString());
}
@@ -377,10 +377,8 @@ public final class CredentialProviderInfoFactory {
}
services.add(serviceInfo);
- } catch (SecurityException e) {
- Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
+ } catch (SecurityException | PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Error getting info for " + serviceInfo, e);
}
}
return services;
@@ -432,7 +430,7 @@ public final class CredentialProviderInfoFactory {
return pp;
} catch (SecurityException e) {
// If the current user is not enrolled in DPM then this can throw a security error.
- Log.e(TAG, "Failed to get device policy: " + e);
+ Slog.e(TAG, "Failed to get device policy: " + e);
}
return null;
@@ -593,7 +591,7 @@ public final class CredentialProviderInfoFactory {
for (ResolveInfo resolveInfo : resolveInfos) {
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo == null) {
- Log.i(TAG, "No serviceInfo found for resolveInfo so skipping this provider");
+ Slog.d(TAG, "No serviceInfo found for resolveInfo, so skipping provider");
continue;
}
@@ -608,10 +606,8 @@ public final class CredentialProviderInfoFactory {
if (!cpi.isSystemProvider()) {
services.add(cpi);
}
- } catch (SecurityException e) {
- Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
} catch (Exception e) {
- Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e);
+ Slog.e(TAG, "Error getting info for " + serviceInfo, e);
}
}
return services;
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index e87333fe4941..53a5fd5c634d 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -226,7 +226,7 @@ public abstract class CredentialProviderService extends Service {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return mInterface.asBinder();
}
- Log.i(TAG, "Failed to bind with intent: " + intent);
+ Log.d(TAG, "Failed to bind with intent: " + intent);
return null;
}
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index dd5373ff8c38..82571db469be 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -80,10 +80,10 @@ public abstract class DreamManagerInternal {
*/
public interface DreamManagerStateListener {
/**
- * Called when keep dreaming when undocked has changed.
+ * Called when keep dreaming when plug has changed.
*
* @param keepDreaming True if the current dream should continue when undocking.
*/
- void onKeepDreamingWhenUndockedChanged(boolean keepDreaming);
+ void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming);
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 402da28b3c5c..828c062d955d 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -45,7 +45,6 @@ import android.provider.Settings.Global;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -310,86 +308,6 @@ public class ZenModeConfig implements Parcelable {
return buffer.toString();
}
- public Diff diff(ZenModeConfig to) {
- final Diff d = new Diff();
- if (to == null) {
- return d.addLine("config", "delete");
- }
- if (user != to.user) {
- d.addLine("user", user, to.user);
- }
- if (allowAlarms != to.allowAlarms) {
- d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
- }
- if (allowMedia != to.allowMedia) {
- d.addLine("allowMedia", allowMedia, to.allowMedia);
- }
- if (allowSystem != to.allowSystem) {
- d.addLine("allowSystem", allowSystem, to.allowSystem);
- }
- if (allowCalls != to.allowCalls) {
- d.addLine("allowCalls", allowCalls, to.allowCalls);
- }
- if (allowReminders != to.allowReminders) {
- d.addLine("allowReminders", allowReminders, to.allowReminders);
- }
- if (allowEvents != to.allowEvents) {
- d.addLine("allowEvents", allowEvents, to.allowEvents);
- }
- if (allowRepeatCallers != to.allowRepeatCallers) {
- d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
- }
- if (allowMessages != to.allowMessages) {
- d.addLine("allowMessages", allowMessages, to.allowMessages);
- }
- if (allowCallsFrom != to.allowCallsFrom) {
- d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
- }
- if (allowMessagesFrom != to.allowMessagesFrom) {
- d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
- }
- if (suppressedVisualEffects != to.suppressedVisualEffects) {
- d.addLine("suppressedVisualEffects", suppressedVisualEffects,
- to.suppressedVisualEffects);
- }
- final ArraySet<String> allRules = new ArraySet<>();
- addKeys(allRules, automaticRules);
- addKeys(allRules, to.automaticRules);
- final int N = allRules.size();
- for (int i = 0; i < N; i++) {
- final String rule = allRules.valueAt(i);
- final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
- final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
- ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
- }
- ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);
-
- if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
- d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
- to.areChannelsBypassingDnd);
- }
- return d;
- }
-
- public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
- if (from == null) {
- final Diff d = new Diff();
- if (to != null) {
- d.addLine("config", "insert");
- }
- return d;
- }
- return from.diff(to);
- }
-
- private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
- if (map != null) {
- for (int i = 0; i < map.size(); i++) {
- set.add(map.keyAt(i));
- }
- }
- }
-
public boolean isValid() {
if (!isValidManualRule(manualRule)) return false;
final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@ public class ZenModeConfig implements Parcelable {
proto.end(token);
}
- private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
- if (d == null) return;
- if (from == null) {
- if (to != null) {
- d.addLine(item, "insert");
- }
- return;
- }
- from.appendDiff(d, item, to);
- }
-
- private void appendDiff(Diff d, String item, ZenRule to) {
- if (to == null) {
- d.addLine(item, "delete");
- return;
- }
- if (enabled != to.enabled) {
- d.addLine(item, "enabled", enabled, to.enabled);
- }
- if (snoozing != to.snoozing) {
- d.addLine(item, "snoozing", snoozing, to.snoozing);
- }
- if (!Objects.equals(name, to.name)) {
- d.addLine(item, "name", name, to.name);
- }
- if (zenMode != to.zenMode) {
- d.addLine(item, "zenMode", zenMode, to.zenMode);
- }
- if (!Objects.equals(conditionId, to.conditionId)) {
- d.addLine(item, "conditionId", conditionId, to.conditionId);
- }
- if (!Objects.equals(condition, to.condition)) {
- d.addLine(item, "condition", condition, to.condition);
- }
- if (!Objects.equals(component, to.component)) {
- d.addLine(item, "component", component, to.component);
- }
- if (!Objects.equals(configurationActivity, to.configurationActivity)) {
- d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
- }
- if (!Objects.equals(id, to.id)) {
- d.addLine(item, "id", id, to.id);
- }
- if (creationTime != to.creationTime) {
- d.addLine(item, "creationTime", creationTime, to.creationTime);
- }
- if (!Objects.equals(enabler, to.enabler)) {
- d.addLine(item, "enabler", enabler, to.enabler);
- }
- if (!Objects.equals(zenPolicy, to.zenPolicy)) {
- d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
- }
- if (modified != to.modified) {
- d.addLine(item, "modified", modified, to.modified);
- }
- if (!Objects.equals(pkg, to.pkg)) {
- d.addLine(item, "pkg", pkg, to.pkg);
- }
- }
-
@Override
public boolean equals(@Nullable Object o) {
if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@ public class ZenModeConfig implements Parcelable {
};
}
- public static class Diff {
- private final ArrayList<String> lines = new ArrayList<>();
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder("Diff[");
- final int N = lines.size();
- for (int i = 0; i < N; i++) {
- if (i > 0) {
- sb.append(",\n");
- }
- sb.append(lines.get(i));
- }
- return sb.append(']').toString();
- }
-
- private Diff addLine(String item, String action) {
- lines.add(item + ":" + action);
- return this;
- }
-
- public Diff addLine(String item, String subitem, Object from, Object to) {
- return addLine(item + "." + subitem, from, to);
- }
-
- public Diff addLine(String item, Object from, Object to) {
- return addLine(item, from + "->" + to);
- }
-
- public boolean isEmpty() {
- return lines.isEmpty();
- }
- }
-
/**
* Determines whether dnd behavior should mute all ringer-controlled sounds
* This includes notification, ringer and system sounds
diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java
new file mode 100644
index 000000000000..c7b89eb284b6
--- /dev/null
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -0,0 +1,542 @@
+/*
+ * 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.service.notification;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
+ * subcomponents (automatic and manual ZenRules).
+ * @hide
+ */
+public class ZenModeDiff {
+ /**
+ * Enum representing whether the existence of a config or rule has changed (added or removed,
+ * or "none" meaning there is no change, which may either mean both null, or there exists a
+ * diff in fields rather than add/remove).
+ */
+ @IntDef(value = {
+ NONE,
+ ADDED,
+ REMOVED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ExistenceChange{}
+
+ public static final int NONE = 0;
+ public static final int ADDED = 1;
+ public static final int REMOVED = 2;
+
+ /**
+ * Diff class representing an individual field diff.
+ * @param <T> The type of the field.
+ */
+ public static class FieldDiff<T> {
+ private final T mFrom;
+ private final T mTo;
+
+ /**
+ * Constructor to create a FieldDiff object with the given values.
+ * @param from from (old) value
+ * @param to to (new) value
+ */
+ public FieldDiff(@Nullable T from, @Nullable T to) {
+ mFrom = from;
+ mTo = to;
+ }
+
+ /**
+ * Get the "from" value
+ */
+ public T from() {
+ return mFrom;
+ }
+
+ /**
+ * Get the "to" value
+ */
+ public T to() {
+ return mTo;
+ }
+
+ /**
+ * Get the string representation of this field diff, in the form of "from->to".
+ */
+ @Override
+ public String toString() {
+ return mFrom + "->" + mTo;
+ }
+
+ /**
+ * Returns whether this represents an actual diff.
+ */
+ public boolean hasDiff() {
+ // note that Objects.equals handles null values gracefully.
+ return !Objects.equals(mFrom, mTo);
+ }
+ }
+
+ /**
+ * Base diff class that contains info about whether something was added, and a set of named
+ * fields that changed.
+ * Extend for diffs of specific types of objects.
+ */
+ private abstract static class BaseDiff {
+ // Whether the diff was added or removed
+ @ExistenceChange private int mExists = NONE;
+
+ // Map from field name to diffs for any standalone fields in the object.
+ private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
+
+ // Functions for actually diffing objects and string representations have to be implemented
+ // by subclasses.
+
+ /**
+ * Return whether this diff represents any changes.
+ */
+ public abstract boolean hasDiff();
+
+ /**
+ * Return a string representation of the diff.
+ */
+ public abstract String toString();
+
+ /**
+ * Constructor that takes the two objects meant to be compared. This constructor sets
+ * whether there is an existence change (added or removed).
+ * @param from previous Object
+ * @param to new Object
+ */
+ BaseDiff(Object from, Object to) {
+ if (from == null) {
+ if (to != null) {
+ mExists = ADDED;
+ }
+ // If both are null, there isn't an existence change; callers/inheritors must handle
+ // the both null case.
+ } else if (to == null) {
+ // in this case, we know that from != null
+ mExists = REMOVED;
+ }
+
+ // Subclasses should implement the actual diffing functionality in their own
+ // constructors.
+ }
+
+ /**
+ * Add a diff for a specific field to the map.
+ * @param name field name
+ * @param diff FieldDiff object representing the diff
+ */
+ final void addField(String name, FieldDiff diff) {
+ mFields.put(name, diff);
+ }
+
+ /**
+ * Returns whether this diff represents a config being newly added.
+ */
+ public final boolean wasAdded() {
+ return mExists == ADDED;
+ }
+
+ /**
+ * Returns whether this diff represents a config being removed.
+ */
+ public final boolean wasRemoved() {
+ return mExists == REMOVED;
+ }
+
+ /**
+ * Returns whether this diff represents an object being either added or removed.
+ */
+ public final boolean hasExistenceChange() {
+ return mExists != NONE;
+ }
+
+ /**
+ * Returns whether there are any individual field diffs.
+ */
+ public final boolean hasFieldDiffs() {
+ return mFields.size() > 0;
+ }
+
+ /**
+ * Returns the diff for the specific named field if it exists
+ */
+ public final FieldDiff getDiffForField(String name) {
+ return mFields.getOrDefault(name, null);
+ }
+
+ /**
+ * Get the set of all field names with some diff.
+ */
+ public final Set<String> fieldNamesWithDiff() {
+ return mFields.keySet();
+ }
+ }
+
+ /**
+ * Diff class representing a diff between two ZenModeConfigs.
+ */
+ public static class ConfigDiff extends BaseDiff {
+ // Rules. Automatic rule map is keyed by the rule name.
+ private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
+ private RuleDiff mManualRuleDiff;
+
+ // Helpers for string generation
+ private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom";
+ private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom";
+ private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom";
+ private static final Set<String> PEOPLE_TYPE_FIELDS =
+ Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD);
+
+ /**
+ * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
+ *
+ * @param from previous ZenModeConfig
+ * @param to new ZenModeConfig
+ */
+ public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
+ super(from, to);
+ // If both are null skip
+ if (from == null && to == null) {
+ return;
+ }
+ if (hasExistenceChange()) {
+ // either added or removed; return here. otherwise (they're not both null) there's
+ // field diffs.
+ return;
+ }
+
+ // Now we compare all the fields, knowing there's a diff and that neither is null
+ if (from.user != to.user) {
+ addField("user", new FieldDiff<>(from.user, to.user));
+ }
+ if (from.allowAlarms != to.allowAlarms) {
+ addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms));
+ }
+ if (from.allowMedia != to.allowMedia) {
+ addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia));
+ }
+ if (from.allowSystem != to.allowSystem) {
+ addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem));
+ }
+ if (from.allowCalls != to.allowCalls) {
+ addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls));
+ }
+ if (from.allowReminders != to.allowReminders) {
+ addField("allowReminders",
+ new FieldDiff<>(from.allowReminders, to.allowReminders));
+ }
+ if (from.allowEvents != to.allowEvents) {
+ addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents));
+ }
+ if (from.allowRepeatCallers != to.allowRepeatCallers) {
+ addField("allowRepeatCallers",
+ new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
+ }
+ if (from.allowMessages != to.allowMessages) {
+ addField("allowMessages",
+ new FieldDiff<>(from.allowMessages, to.allowMessages));
+ }
+ if (from.allowConversations != to.allowConversations) {
+ addField("allowConversations",
+ new FieldDiff<>(from.allowConversations, to.allowConversations));
+ }
+ if (from.allowCallsFrom != to.allowCallsFrom) {
+ addField("allowCallsFrom",
+ new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
+ }
+ if (from.allowMessagesFrom != to.allowMessagesFrom) {
+ addField("allowMessagesFrom",
+ new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
+ }
+ if (from.allowConversationsFrom != to.allowConversationsFrom) {
+ addField("allowConversationsFrom",
+ new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
+ }
+ if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
+ addField("suppressedVisualEffects",
+ new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
+ }
+ if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
+ addField("areChannelsBypassingDnd",
+ new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
+ }
+
+ // Compare automatic and manual rules
+ final ArraySet<String> allRules = new ArraySet<>();
+ addKeys(allRules, from.automaticRules);
+ addKeys(allRules, to.automaticRules);
+ final int num = allRules.size();
+ for (int i = 0; i < num; i++) {
+ final String rule = allRules.valueAt(i);
+ final ZenModeConfig.ZenRule
+ fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
+ : null;
+ final ZenModeConfig.ZenRule
+ toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
+ RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
+ if (ruleDiff.hasDiff()) {
+ mAutomaticRulesDiff.put(rule, ruleDiff);
+ }
+ }
+ // If there's no diff this may turn out to be null, but that's also fine
+ RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
+ if (manualRuleDiff.hasDiff()) {
+ mManualRuleDiff = manualRuleDiff;
+ }
+ }
+
+ private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
+ if (map != null) {
+ for (int i = 0; i < map.size(); i++) {
+ set.add(map.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Returns whether this diff object contains any diffs in any field.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange()
+ || hasFieldDiffs()
+ || mManualRuleDiff != null
+ || mAutomaticRulesDiff.size() > 0;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Diff[");
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, then that's just the end of it
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Handle top-level field change
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen, but
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+
+ // Some special handling for people- and conversation-type fields for readability
+ if (PEOPLE_TYPE_FIELDS.contains(key)) {
+ sb.append(key);
+ sb.append(":");
+ sb.append(ZenModeConfig.sourceToString((int) diff.from()));
+ sb.append("->");
+ sb.append(ZenModeConfig.sourceToString((int) diff.to()));
+ } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) {
+ sb.append(key);
+ sb.append(":");
+ sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
+ sb.append("->");
+ sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
+ } else {
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+ }
+
+ // manual rule
+ if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+ sb.append("manualRule:");
+ sb.append(mManualRuleDiff);
+ }
+
+ // automatic rules
+ for (String rule : mAutomaticRulesDiff.keySet()) {
+ RuleDiff diff = mAutomaticRulesDiff.get(rule);
+ if (diff != null && diff.hasDiff()) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",\n");
+ }
+ sb.append("automaticRule[");
+ sb.append(rule);
+ sb.append("]:");
+ sb.append(diff);
+ }
+ }
+
+ return sb.append(']').toString();
+ }
+
+ /**
+ * Get the diff in manual rule, if it exists.
+ */
+ public RuleDiff getManualRuleDiff() {
+ return mManualRuleDiff;
+ }
+
+ /**
+ * Get the full map of automatic rule diffs, or null if there are no diffs.
+ */
+ public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
+ return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
+ }
+ }
+
+ /**
+ * Diff class representing a change between two ZenRules.
+ */
+ public static class RuleDiff extends BaseDiff {
+ /**
+ * Create a RuleDiff representing the difference between two ZenRule objects.
+ * @param from previous ZenRule
+ * @param to new ZenRule
+ * @return The diff between the two given ZenRules
+ */
+ public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
+ super(from, to);
+ // Short-circuit the both-null case
+ if (from == null && to == null) {
+ return;
+ }
+ // Return if the diff was added or removed
+ if (hasExistenceChange()) {
+ return;
+ }
+
+ if (from.enabled != to.enabled) {
+ addField("enabled", new FieldDiff<>(from.enabled, to.enabled));
+ }
+ if (from.snoozing != to.snoozing) {
+ addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing));
+ }
+ if (!Objects.equals(from.name, to.name)) {
+ addField("name", new FieldDiff<>(from.name, to.name));
+ }
+ if (from.zenMode != to.zenMode) {
+ addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode));
+ }
+ if (!Objects.equals(from.conditionId, to.conditionId)) {
+ addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId));
+ }
+ if (!Objects.equals(from.condition, to.condition)) {
+ addField("condition", new FieldDiff<>(from.condition, to.condition));
+ }
+ if (!Objects.equals(from.component, to.component)) {
+ addField("component", new FieldDiff<>(from.component, to.component));
+ }
+ if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
+ addField("configurationActivity", new FieldDiff<>(
+ from.configurationActivity, to.configurationActivity));
+ }
+ if (!Objects.equals(from.id, to.id)) {
+ addField("id", new FieldDiff<>(from.id, to.id));
+ }
+ if (from.creationTime != to.creationTime) {
+ addField("creationTime",
+ new FieldDiff<>(from.creationTime, to.creationTime));
+ }
+ if (!Objects.equals(from.enabler, to.enabler)) {
+ addField("enabler", new FieldDiff<>(from.enabler, to.enabler));
+ }
+ if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
+ addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy));
+ }
+ if (from.modified != to.modified) {
+ addField("modified", new FieldDiff<>(from.modified, to.modified));
+ }
+ if (!Objects.equals(from.pkg, to.pkg)) {
+ addField("pkg", new FieldDiff<>(from.pkg, to.pkg));
+ }
+ }
+
+ /**
+ * Returns whether this object represents an actual diff.
+ */
+ @Override
+ public boolean hasDiff() {
+ return hasExistenceChange() || hasFieldDiffs();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
+ // If there's no diff, probably we haven't actually let this object continue existing
+ // but might as well handle this case.
+ if (!hasDiff()) {
+ sb.append("no changes");
+ }
+
+ // If added or deleted, then that's just the end of it
+ if (hasExistenceChange()) {
+ if (wasAdded()) {
+ sb.append("added");
+ } else if (wasRemoved()) {
+ sb.append("removed");
+ }
+ }
+
+ // Go through all of the individual fields
+ boolean first = true;
+ for (String key : fieldNamesWithDiff()) {
+ FieldDiff diff = getDiffForField(key);
+ if (diff == null) {
+ // this shouldn't happen, but
+ continue;
+ }
+ if (first) {
+ first = false;
+ } else {
+ sb.append(", ");
+ }
+
+ sb.append(key);
+ sb.append(":");
+ sb.append(diff);
+ }
+
+ return sb.append("}").toString();
+ }
+ }
+}
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 8688a18880b7..24c96eae03cb 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1573,16 +1573,6 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@Override
- public void onError(int status) {
- Slog.i(TAG, "onError: " + status);
- // TODO(b/271534248): This is a workaround before the sound trigger uses the new error
- // method.
- Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
- new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN,
- "Sound trigger error")).sendToTarget();
- }
-
- @Override
public void onHotwordDetectionServiceFailure(
HotwordDetectionServiceFailure hotwordDetectionServiceFailure) {
Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure);
@@ -1605,6 +1595,12 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@Override
+ public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+ Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE,
+ Objects.requireNonNull(soundTriggerFailure)).sendToTarget();
+ }
+
+ @Override
public void onUnknownFailure(String errorMessage) throws RemoteException {
Slog.v(TAG, "onUnknownFailure: " + errorMessage);
Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE,
diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java
index eac7aee43859..7ab4fafcf312 100644
--- a/core/java/android/service/voice/SoftwareHotwordDetector.java
+++ b/core/java/android/service/voice/SoftwareHotwordDetector.java
@@ -234,14 +234,6 @@ class SoftwareHotwordDetector extends AbstractDetector {
}
@Override
- public void onError(int status) throws RemoteException {
- if (DEBUG) {
- Slog.i(TAG, "Ignored #onError (" + status + ") event");
- }
- // TODO: Check if we still need to implement this method with DetectorFailure mechanism.
- }
-
- @Override
public void onHotwordDetectionServiceFailure(
HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
throws RemoteException {
@@ -265,6 +257,13 @@ class SoftwareHotwordDetector extends AbstractDetector {
}
@Override
+ public void onSoundTriggerFailure(SoundTriggerFailure onSoundTriggerFailure)
+ throws RemoteException {
+ // It should never be called here.
+ Slog.wtf(TAG, "Unexpected STFailure in software detector: " + onSoundTriggerFailure);
+ }
+
+ @Override
public void onUnknownFailure(String errorMessage) throws RemoteException {
Slog.v(TAG, "onUnknownFailure: " + errorMessage);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java
index 5560800a373f..2ce5e5da4724 100644
--- a/core/java/android/service/voice/SoundTriggerFailure.java
+++ b/core/java/android/service/voice/SoundTriggerFailure.java
@@ -73,18 +73,28 @@ public final class SoundTriggerFailure implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface SoundTriggerErrorCode {}
- private int mErrorCode = ERROR_CODE_UNKNOWN;
- private String mErrorMessage = "Unknown";
+ private final int mErrorCode;
+ private final String mErrorMessage;
/**
* @hide
*/
@TestApi
- public SoundTriggerFailure(int errorCode, @NonNull String errorMessage) {
+ public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode,
+ @NonNull String errorMessage) {
if (TextUtils.isEmpty(errorMessage)) {
throw new IllegalArgumentException("errorMessage is empty or null.");
}
- mErrorCode = errorCode;
+ switch (errorCode) {
+ case ERROR_CODE_UNKNOWN:
+ case ERROR_CODE_MODULE_DIED:
+ case ERROR_CODE_RECOGNITION_RESUME_FAILED:
+ case ERROR_CODE_UNEXPECTED_PREEMPTION:
+ mErrorCode = errorCode;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode);
+ }
mErrorMessage = errorMessage;
}
@@ -110,13 +120,14 @@ public final class SoundTriggerFailure implements Parcelable {
@FailureSuggestedAction.FailureSuggestedActionDef
public int getSuggestedAction() {
switch (mErrorCode) {
+ case ERROR_CODE_UNKNOWN:
case ERROR_CODE_MODULE_DIED:
case ERROR_CODE_UNEXPECTED_PREEMPTION:
return FailureSuggestedAction.RECREATE_DETECTOR;
case ERROR_CODE_RECOGNITION_RESUME_FAILED:
return FailureSuggestedAction.RESTART_RECOGNITION;
default:
- return FailureSuggestedAction.NONE;
+ throw new AssertionError("Unexpected error code");
}
}
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index b4f5ff1046ae..93b7964705ba 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -391,12 +391,6 @@ public class VisualQueryDetector {
}
@Override
- public void onError(int status) throws RemoteException {
- Slog.v(TAG, "Initialization Error: (" + status + ")");
- // Do nothing
- }
-
- @Override
public void onHotwordDetectionServiceFailure(
HotwordDetectionServiceFailure hotwordDetectionServiceFailure)
throws RemoteException {
@@ -420,6 +414,11 @@ public class VisualQueryDetector {
}
@Override
+ public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) {
+ Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure);
+ }
+
+ @Override
public void onUnknownFailure(String errorMessage) throws RemoteException {
Slog.v(TAG, "onUnknownFailure: " + errorMessage);
Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> {
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fcc64b088def..68cce4ae274a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -50,6 +50,7 @@ import android.provider.Settings;
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceActionCheckCallback;
import com.android.internal.app.IVoiceInteractionManagerService;
@@ -200,6 +201,9 @@ public class VoiceInteractionService extends Service {
private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>();
+ // True if any of the createAOHD methods should use the test ST module.
+ @GuardedBy("mLock")
+ private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false;
private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) {
Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString(
@@ -512,14 +516,14 @@ public class VoiceInteractionService extends Service {
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ false, /* options= */ null,
- /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback);
+ /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor,
* AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST
* module to attach to.
- * Use {@link listModuleProperties} to get available modules to attach to.
+ * Use {@link #listModuleProperties()} to get available modules to attach to.
* @hide
*/
@TestApi
@@ -645,14 +649,14 @@ public class VoiceInteractionService extends Service {
Objects.requireNonNull(callback);
return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
/* supportHotwordDetectionService= */ true, options, sharedMemory,
- /* moduleProperties */ null, executor, callback);
+ /* moduleProperties= */ null, executor, callback);
}
/**
* Same as {@link createAlwaysOnHotwordDetector(String, Locale,
* PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)},
* but allow explicit selection of the underlying ST module to attach to.
- * Use {@link listModuleProperties} to get available modules to attach to.
+ * Use {@link #listModuleProperties()} to get available modules to attach to.
* @hide
*/
@TestApi
@@ -717,6 +721,10 @@ public class VoiceInteractionService extends Service {
try {
dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed);
+ // Check if we are currently overridden, and should use the test module.
+ if (mTestModuleForAlwaysOnHotwordDetectorEnabled) {
+ moduleProperties = getTestModuleProperties();
+ }
// If moduleProperties is null, the default STModule is used.
dspDetector.initialize(options, sharedMemory, moduleProperties);
} catch (Exception e) {
@@ -990,6 +998,44 @@ public class VoiceInteractionService extends Service {
return mKeyphraseEnrollmentInfo;
}
+
+ /**
+ * Configure {@link createAlwaysOnHotwordDetector(String, Locale,
+ * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+ * and similar overloads to utilize the test SoundTrigger module instead of the
+ * actual DSP module.
+ * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector}
+ * objects should attach to a test module. {@code false} if subsequently created
+ * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module.
+ * @hide
+ */
+ @TestApi
+ public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) {
+ synchronized (mLock) {
+ mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled;
+ }
+ }
+
+ /**
+ * Get the {@link SoundTrigger.ModuleProperties} representing the fake
+ * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale,
+ * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and
+ * similar overloads for test purposes.
+ * @return ModuleProperties to use for test purposes.
+ */
+ private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() {
+ var moduleProps = listModuleProperties()
+ .stream()
+ .filter((SoundTrigger.ModuleProperties prop)
+ -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH))
+ .findFirst()
+ .orElse(null);
+ if (moduleProps == null) {
+ throw new IllegalStateException("Fake ST HAL should always be available");
+ }
+ return moduleProps;
+ }
+
/**
* Checks if a given keyphrase and locale are supported to create an
* {@link AlwaysOnHotwordDetector}.
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index 4c51be0ab8d9..f1ae22eca873 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -32,6 +32,8 @@ interface IWallpaperEngine {
oneway void setDisplayPadding(in Rect padding);
@UnsupportedAppUsage
oneway void setVisibility(boolean visible);
+ oneway void onScreenTurningOn();
+ oneway void onScreenTurnedOn();
oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration);
@UnsupportedAppUsage
oneway void dispatchPointer(in MotionEvent event);
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 0b947fc18237..74ab7095d392 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -112,6 +112,7 @@ import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
/**
@@ -168,6 +169,7 @@ public abstract class WallpaperService extends Service {
private static final int MSG_ZOOM = 10100;
private static final int MSG_RESIZE_PREVIEW = 10110;
private static final int MSG_REPORT_SHOWN = 10150;
+ private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170;
private static final int MSG_UPDATE_DIMMING = 10200;
private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210;
@@ -213,6 +215,16 @@ public abstract class WallpaperService extends Service {
boolean mInitializing = true;
boolean mVisible;
+ /**
+ * Whether the screen is turning on.
+ * After the display is powered on, brightness is initially off. It is turned on only after
+ * all windows have been drawn, and sysui notifies that it's ready (See
+ * {@link com.android.internal.policy.IKeyguardDrawnCallback}).
+ * As some wallpapers use visibility as a signal to start animations, this makes sure
+ * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and
+ * visible (with brightness on).
+ */
+ private boolean mIsScreenTurningOn;
boolean mReportedVisible;
boolean mDestroyed;
// Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
@@ -420,6 +432,7 @@ public abstract class WallpaperService extends Service {
Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
reportDraw ? 1 : 0,
mergedConfiguration);
+ mIWallpaperEngine.mPendingResizeCount.incrementAndGet();
mCaller.sendMessage(msg);
}
@@ -499,6 +512,7 @@ public abstract class WallpaperService extends Service {
public Engine(Supplier<Long> clockFunction, Handler handler) {
mClockFunction = clockFunction;
mHandler = handler;
+ mMergedConfiguration.setOverrideConfiguration(getResources().getConfiguration());
}
/**
@@ -1018,6 +1032,7 @@ public abstract class WallpaperService extends Service {
out.print(" mDestroyed="); out.println(mDestroyed);
out.print(prefix); out.print("mVisible="); out.print(mVisible);
out.print(" mReportedVisible="); out.println(mReportedVisible);
+ out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn);
out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
out.print(prefix); out.print("mCreated="); out.print(mCreated);
out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
@@ -1039,6 +1054,10 @@ public abstract class WallpaperService extends Service {
out.print(prefix); out.print("mZoom="); out.println(mZoom);
out.print(prefix); out.print("mPreviewSurfacePosition=");
out.println(mPreviewSurfacePosition);
+ final int pendingCount = mIWallpaperEngine.mPendingResizeCount.get();
+ if (pendingCount != 0) {
+ out.print(prefix); out.print("mPendingResizeCount="); out.println(pendingCount);
+ }
synchronized (mLock) {
out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset);
out.print(" mPendingXOffset="); out.println(mPendingXOffset);
@@ -1101,10 +1120,6 @@ public abstract class WallpaperService extends Service {
}
}
- private void updateConfiguration(MergedConfiguration mergedConfiguration) {
- mMergedConfiguration.setTo(mergedConfiguration);
- }
-
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
Log.w(TAG, "Ignoring updateSurface due to destroyed");
@@ -1153,7 +1168,7 @@ public abstract class WallpaperService extends Service {
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
final Configuration config = mMergedConfiguration.getMergedConfiguration();
- final Rect maxBounds = config.windowConfiguration.getMaxBounds();
+ final Rect maxBounds = new Rect(config.windowConfiguration.getMaxBounds());
if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
&& myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
mLayout.width = myWidth;
@@ -1209,6 +1224,17 @@ public abstract class WallpaperService extends Service {
final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration,
mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
+ final Rect outMaxBounds = mMergedConfiguration.getMergedConfiguration()
+ .windowConfiguration.getMaxBounds();
+ if (!outMaxBounds.equals(maxBounds)) {
+ Log.i(TAG, "Retry updateSurface because bounds changed from relayout: "
+ + maxBounds + " -> " + outMaxBounds);
+ mSurfaceHolder.mSurfaceLock.unlock();
+ mDrawingAllowed = false;
+ mCaller.sendMessage(mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
+ redrawNeeded ? 1 : 0));
+ return;
+ }
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
@@ -1549,6 +1575,13 @@ public abstract class WallpaperService extends Service {
}
}
+ void onScreenTurningOnChanged(boolean isScreenTurningOn) {
+ if (!mDestroyed) {
+ mIsScreenTurningOn = isScreenTurningOn;
+ reportVisibility(false);
+ }
+ }
+
void doVisibilityChanged(boolean visible) {
if (!mDestroyed) {
mVisible = visible;
@@ -1565,9 +1598,10 @@ public abstract class WallpaperService extends Service {
return;
}
if (!mDestroyed) {
- mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN :
- mDisplay.getCommittedState();
- boolean visible = mVisible && mDisplayState != Display.STATE_OFF;
+ mDisplayState =
+ mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState();
+ boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn;
+ boolean visible = mVisible && displayVisible;
if (DEBUG) {
Log.v(
TAG,
@@ -2304,6 +2338,8 @@ public abstract class WallpaperService extends Service {
final IBinder mWindowToken;
final int mWindowType;
final boolean mIsPreview;
+ final AtomicInteger mPendingResizeCount = new AtomicInteger();
+ boolean mReportDraw;
boolean mShownReported;
int mReqWidth;
int mReqHeight;
@@ -2486,6 +2522,20 @@ public abstract class WallpaperService extends Service {
}
}
+ public void updateScreenTurningOn(boolean isScreenTurningOn) {
+ Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn,
+ null);
+ mCaller.sendMessage(msg);
+ }
+
+ public void onScreenTurningOn() throws RemoteException {
+ updateScreenTurningOn(true);
+ }
+
+ public void onScreenTurnedOn() throws RemoteException {
+ updateScreenTurningOn(false);
+ }
+
@Override
public void executeMessage(Message message) {
switch (message.what) {
@@ -2530,6 +2580,13 @@ public abstract class WallpaperService extends Service {
+ ": " + message.arg1);
mEngine.doVisibilityChanged(message.arg1 != 0);
break;
+ case MSG_UPDATE_SCREEN_TURNING_ON:
+ if (DEBUG) {
+ Log.v(TAG,
+ message.arg1 != 0 ? "Screen turning on" : "Screen turned on");
+ }
+ mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0);
+ break;
case MSG_WALLPAPER_OFFSETS: {
mEngine.doOffsetsChanged(true);
} break;
@@ -2538,11 +2595,7 @@ public abstract class WallpaperService extends Service {
mEngine.doCommand(cmd);
} break;
case MSG_WINDOW_RESIZED: {
- final boolean reportDraw = message.arg1 != 0;
- mEngine.updateConfiguration(((MergedConfiguration) message.obj));
- mEngine.updateSurface(true, false, reportDraw);
- mEngine.doOffsetsChanged(true);
- mEngine.scaleAndCropScreenshot();
+ handleResized((MergedConfiguration) message.obj, message.arg1 != 0);
} break;
case MSG_WINDOW_MOVED: {
// Do nothing. What does it mean for a Wallpaper to move?
@@ -2590,6 +2643,40 @@ public abstract class WallpaperService extends Service {
Log.w(TAG, "Unknown message type " + message.what);
}
}
+
+ /**
+ * In general this performs relayout for IWindow#resized. If there are several pending
+ * (in the message queue) MSG_WINDOW_RESIZED from server side, only the last one will be
+ * handled (ignore intermediate states). Note that this procedure cannot be skipped if the
+ * configuration is not changed because this is also used to dispatch insets changes.
+ */
+ private void handleResized(MergedConfiguration config, boolean reportDraw) {
+ // The config can be null when retrying for a changed config from relayout, otherwise
+ // it is from IWindow#resized which always sends non-null config.
+ final int pendingCount = config != null ? mPendingResizeCount.decrementAndGet() : -1;
+ if (reportDraw) {
+ mReportDraw = true;
+ }
+ if (pendingCount > 0) {
+ if (DEBUG) {
+ Log.d(TAG, "Skip outdated resize, bounds="
+ + config.getMergedConfiguration().windowConfiguration.getMaxBounds()
+ + " pendingCount=" + pendingCount);
+ }
+ return;
+ }
+ if (config != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Update config from resized, bounds="
+ + config.getMergedConfiguration().windowConfiguration.getMaxBounds());
+ }
+ mEngine.mMergedConfiguration.setTo(config);
+ }
+ mEngine.updateSurface(true /* forceRelayout */, false /* forceReport */, mReportDraw);
+ mReportDraw = false;
+ mEngine.doOffsetsChanged(true);
+ mEngine.scaleAndCropScreenshot();
+ }
}
/**
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index c3295088ef11..d87198a0dc85 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -128,6 +128,12 @@ public class FeatureFlagUtils {
*/
public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2";
+ /**
+ * Enable the SPA metrics writing.
+ * @hide
+ */
+ public static final String SETTINGS_ENABLE_SPA_METRICS = "settings_enable_spa_metrics";
+
/** Flag to enable/disable adb log metrics
* @hide
*/
@@ -226,6 +232,7 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false");
DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true");
DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 9a93e1b9e1f9..d06b0ce1a2d8 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -354,6 +354,15 @@ public class TimeUtils {
}
/** @hide Just for debugging; not internationalized. */
+ public static void formatDuration(long time, long now, StringBuilder sb) {
+ if (time == 0) {
+ sb.append("--");
+ return;
+ }
+ formatDuration(time-now, sb, 0);
+ }
+
+ /** @hide Just for debugging; not internationalized. */
public static void formatDuration(long time, long now, PrintWriter pw) {
if (time == 0) {
pw.print("--");
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index c92b1b8c120d..edc59931d4f8 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -785,12 +785,13 @@ public final class Choreographer {
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
+ boolean resynced = false;
try {
+ FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW,
- "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId);
+ Trace.traceBegin(
+ Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId);
}
- mFrameData.update(frameTimeNanos, vsyncEventData);
synchronized (mLock) {
if (!mFrameScheduled) {
traceMessage("Frame not scheduled");
@@ -828,7 +829,9 @@ public final class Choreographer {
+ " ms in the past.");
}
}
- mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+ timeline = mFrameData.update(
+ frameTimeNanos, mDisplayEventReceiver, jitterNanos);
+ resynced = true;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
@@ -860,6 +863,12 @@ public final class Choreographer {
mLastVsyncEventData.copyFrom(vsyncEventData);
}
+ if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
+ String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms",
+ timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f);
+ Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
+ }
+
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
@@ -875,6 +884,9 @@ public final class Choreographer {
doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
} finally {
AnimationUtils.unlockAnimationClock();
+ if (resynced) {
+ Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+ }
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
@@ -1149,7 +1161,8 @@ public final class Choreographer {
* Update the frame data with a {@code DisplayEventReceiver.VsyncEventData} received from
* native.
*/
- void update(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
+ FrameTimeline update(
+ long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) {
if (vsyncEventData.frameTimelines.length != mFrameTimelines.length) {
throw new IllegalStateException(
"Length of native frame timelines received does not match Java. Did "
@@ -1164,6 +1177,7 @@ public final class Choreographer {
mFrameTimelines[i].update(frameTimeline.vsyncId,
frameTimeline.expectedPresentationTime, frameTimeline.deadline);
}
+ return mFrameTimelines[mPreferredFrameTimelineIndex];
}
/**
@@ -1171,7 +1185,7 @@ public final class Choreographer {
*
* @param jitterNanos currentTime - frameTime
*/
- void update(
+ FrameTimeline update(
long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) {
int newPreferredIndex = 0;
final long minimumDeadline =
@@ -1192,6 +1206,7 @@ public final class Choreographer {
} else {
update(frameTimeNanos, newPreferredIndex);
}
+ return mFrameTimelines[mPreferredFrameTimelineIndex];
}
void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) {
diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java
index 0b4adaeb9890..a8e68b71f5cc 100644
--- a/core/java/android/view/InputEvent.java
+++ b/core/java/android/view/InputEvent.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -100,6 +101,7 @@ public abstract class InputEvent implements Parcelable {
* @return The display id associated with the event.
* @hide
*/
+ @TestApi
public abstract int getDisplayId();
/**
@@ -107,6 +109,7 @@ public abstract class InputEvent implements Parcelable {
* @param displayId
* @hide
*/
+ @TestApi
public abstract void setDisplayId(int displayId);
/**
* Copies the event.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index d35aff9a72b7..3812d37a5fed 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -215,6 +215,7 @@ public final class InputWindowHandle {
.append(", scaleFactor=").append(scaleFactor)
.append(", transform=").append(transform)
.append(", windowToken=").append(windowToken)
+ .append(", displayId=").append(displayId)
.append(", isClone=").append((inputConfig & InputConfig.CLONE) != 0)
.toString();
diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java
index b6d9400fad5c..858da554c670 100644
--- a/core/java/android/view/KeyEvent.java
+++ b/core/java/android/view/KeyEvent.java
@@ -2100,6 +2100,7 @@ public class KeyEvent extends InputEvent implements Parcelable {
}
/** @hide */
+ @TestApi
@Override
public final int getDisplayId() {
return mDisplayId;
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index ac50d09cc091..bd6224b07b16 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -99,9 +99,16 @@ public class SurfaceControlViewHost {
@Override
public ISurfaceSyncGroup getSurfaceSyncGroup() {
CompletableFuture<ISurfaceSyncGroup> surfaceSyncGroup = new CompletableFuture<>();
- mViewRoot.mHandler.post(
- () -> surfaceSyncGroup.complete(
- mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
+ // If the call came from in process and it's already running on the UI thread, return
+ // results immediately instead of posting to the main thread. If we post to the main
+ // thread, it will block itself and the return value will always be null.
+ if (Thread.currentThread() == mViewRoot.mThread) {
+ return mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup;
+ } else {
+ mViewRoot.mHandler.post(
+ () -> surfaceSyncGroup.complete(
+ mViewRoot.getOrCreateSurfaceSyncGroup().mISurfaceSyncGroup));
+ }
try {
return surfaceSyncGroup.get(1, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
@@ -113,6 +120,8 @@ public class SurfaceControlViewHost {
private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl();
+ private ViewRootImpl.ConfigChangedCallback mConfigChangedCallback;
+
/**
* Package encapsulating a Surface hierarchy which contains interactive view
* elements. It's expected to get this object from
@@ -296,10 +305,11 @@ public class SurfaceControlViewHost {
/** @hide */
public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
@NonNull WindowlessWindowManager wwm, @NonNull String callsite) {
+ mSurfaceControl = wwm.mRootSurface;
mWm = wwm;
mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout());
mCloseGuard.openWithCallSite("release", callsite);
- addConfigCallback(c, d);
+ setConfigCallback(c, d);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
@@ -349,21 +359,23 @@ public class SurfaceControlViewHost {
mViewRoot = new ViewRootImpl(context, display, mWm, new WindowlessWindowLayout());
mCloseGuard.openWithCallSite("release", callsite);
- addConfigCallback(context, display);
+ setConfigCallback(context, display);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection();
}
- private void addConfigCallback(Context c, Display d) {
+ private void setConfigCallback(Context c, Display d) {
final IBinder token = c.getWindowContextToken();
- mViewRoot.addConfigCallback((conf) -> {
+ mConfigChangedCallback = conf -> {
if (token instanceof WindowTokenClient) {
final WindowTokenClient w = (WindowTokenClient) token;
w.onConfigurationChanged(conf, d.getDisplayId(), true);
}
- });
+ };
+
+ ViewRootImpl.addConfigCallback(mConfigChangedCallback);
}
/**
@@ -378,8 +390,7 @@ public class SurfaceControlViewHost {
mCloseGuard.warnIfOpen();
}
// We aren't on the UI thread here so we need to pass false to doDie
- mViewRoot.die(false /* immediate */);
- WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
+ doRelease(false /* immediate */);
}
/**
@@ -392,8 +403,7 @@ public class SurfaceControlViewHost {
public @Nullable SurfacePackage getSurfacePackage() {
if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) {
return new SurfacePackage(new SurfaceControl(mSurfaceControl, "getSurfacePackage"),
- mAccessibilityEmbeddedConnection,
- mWm.getFocusGrantToken(), mRemoteInterface);
+ mAccessibilityEmbeddedConnection, getFocusGrantToken(), mRemoteInterface);
} else {
return null;
}
@@ -489,7 +499,16 @@ public class SurfaceControlViewHost {
*/
public void release() {
// ViewRoot will release mSurfaceControl for us.
- mViewRoot.die(true /* immediate */);
+ doRelease(true /* immediate */);
+ }
+
+ private void doRelease(boolean immediate) {
+ if (mConfigChangedCallback != null) {
+ ViewRootImpl.removeConfigCallback(mConfigChangedCallback);
+ mConfigChangedCallback = null;
+ }
+
+ mViewRoot.die(immediate);
WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot);
mReleased = true;
mCloseGuard.close();
@@ -499,7 +518,7 @@ public class SurfaceControlViewHost {
* @hide
*/
public IBinder getFocusGrantToken() {
- return mWm.getFocusGrantToken();
+ return mWm.getFocusGrantToken(getWindowToken().asBinder());
}
private void addWindowToken(WindowManager.LayoutParams attrs) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2f5cd5434b89..055b5cb70562 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6946,6 +6946,7 @@ public final class ViewRootImpl implements ViewParent,
return;
}
final boolean needsStylusPointerIcon = event.isStylusPointer()
+ && event.isHoverEvent()
&& mInputManager.isStylusPointerIconEnabled();
if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index 98681446446b..96bfb2d9e1e6 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -106,8 +106,22 @@ public class WindowlessWindowManager implements IWindowSession {
mConfiguration.setTo(configuration);
}
- IBinder getFocusGrantToken() {
- return mFocusGrantToken;
+ IBinder getFocusGrantToken(IBinder window) {
+ synchronized (this) {
+ // This can only happen if someone requested the focusGrantToken before setView was
+ // called for the SCVH. In that case, use the root focusGrantToken since this will be
+ // the same token sent to WMS for the root window once setView is called.
+ if (mStateForWindow.isEmpty()) {
+ return mFocusGrantToken;
+ }
+ State state = mStateForWindow.get(window);
+ if (state != null) {
+ return state.mFocusGrantToken;
+ }
+ }
+
+ Log.w(TAG, "Failed to get focusGrantToken. Returning null token");
+ return null;
}
/**
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1fac142df481..390503b80bad 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -62,7 +62,7 @@ interface IAccessibilityManager {
in IAccessibilityInteractionConnection connection);
void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client,
- in AccessibilityServiceInfo info, int flags);
+ in AccessibilityServiceInfo info, int userId, int flags);
void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java
index 70db6e513c88..50249da08c3c 100644
--- a/core/java/android/view/translation/Translator.java
+++ b/core/java/android/view/translation/Translator.java
@@ -18,7 +18,6 @@ package android.view.translation;
import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL;
import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS;
-import static android.view.translation.UiTranslationController.DEBUG;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
@@ -402,7 +401,7 @@ public class Translator {
@Override
public void onTranslationResponse(TranslationResponse response) throws RemoteException {
- if (DEBUG) {
+ if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
Log.i(TAG, "onTranslationResponse called.");
}
final Runnable runnable =
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 514df59f1989..140e3f1b6fdc 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -122,8 +122,9 @@ public class UiTranslationController implements Dumpable {
Log.i(TAG, "Cannot update " + stateToString(state) + " for destroyed " + mActivity);
return;
}
+ boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
Log.i(TAG, "updateUiTranslationState state: " + stateToString(state)
- + (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
+ + (isLoggable ? (", views: " + views + ", spec: " + uiTranslationSpec) : ""));
synchronized (mLock) {
mCurrentState = state;
if (views != null) {
@@ -237,7 +238,7 @@ public class UiTranslationController implements Dumpable {
}
pw.print(outerPrefix); pw.print("padded views: "); pw.println(mViewsToPadContent);
}
- if (DEBUG) {
+ if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
dumpViewByTraversal(outerPrefix, pw);
}
}
@@ -345,6 +346,7 @@ public class UiTranslationController implements Dumpable {
*/
private void onVirtualViewTranslationCompleted(
SparseArray<LongSparseArray<ViewTranslationResponse>> translatedResult) {
+ boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
if (mActivity.isDestroyed()) {
Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
return;
@@ -369,7 +371,7 @@ public class UiTranslationController implements Dumpable {
}
final LongSparseArray<ViewTranslationResponse> virtualChildResponse =
translatedResult.valueAt(i);
- if (DEBUG) {
+ if (isLoggable) {
Log.v(TAG, "onVirtualViewTranslationCompleted: received response for "
+ "AutofillId " + autofillId);
}
@@ -379,7 +381,7 @@ public class UiTranslationController implements Dumpable {
}
mActivity.runOnUiThread(() -> {
if (view.getViewTranslationCallback() == null) {
- if (DEBUG) {
+ if (isLoggable) {
Log.d(TAG, view + " doesn't support showing translation because of "
+ "null ViewTranslationCallback.");
}
@@ -397,12 +399,13 @@ public class UiTranslationController implements Dumpable {
* The method is used to handle the translation result for non-vertual views.
*/
private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) {
+ boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
if (mActivity.isDestroyed()) {
Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed.");
return;
}
final int resultCount = translatedResult.size();
- if (DEBUG) {
+ if (isLoggable) {
Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses.");
}
synchronized (mLock) {
@@ -413,7 +416,7 @@ public class UiTranslationController implements Dumpable {
}
for (int i = 0; i < resultCount; i++) {
final ViewTranslationResponse response = translatedResult.valueAt(i);
- if (DEBUG) {
+ if (isLoggable) {
Log.v(TAG, "onTranslationCompleted: "
+ sanitizedViewTranslationResponse(response));
}
@@ -443,7 +446,7 @@ public class UiTranslationController implements Dumpable {
(TextViewTranslationCallback) callback;
if (textViewCallback.isShowingTranslation()
|| textViewCallback.isAnimationRunning()) {
- if (DEBUG) {
+ if (isLoggable) {
Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+ ". Ignoring.");
}
@@ -458,7 +461,7 @@ public class UiTranslationController implements Dumpable {
callback = new TextViewTranslationCallback();
view.setViewTranslationCallback(callback);
} else {
- if (DEBUG) {
+ if (isLoggable) {
Log.d(TAG, view + " doesn't support showing translation because of "
+ "null ViewTranslationCallback.");
}
@@ -506,7 +509,7 @@ public class UiTranslationController implements Dumpable {
final TranslationRequest request = new TranslationRequest.Builder()
.setViewTranslationRequests(requests)
.build();
- if (DEBUG) {
+ if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) {
StringBuilder msg = new StringBuilder("sendTranslationRequest:{requests=[");
for (ViewTranslationRequest viewRequest: requests) {
msg.append("{request=")
@@ -636,6 +639,7 @@ public class UiTranslationController implements Dumpable {
private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) {
synchronized (mLock) {
+ boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG);
final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews);
if (views.size() == 0) {
Log.w(TAG, "No views can be excuted for runForEachView.");
@@ -644,12 +648,12 @@ public class UiTranslationController implements Dumpable {
final int viewCounts = views.size();
for (int i = 0; i < viewCounts; i++) {
final View view = views.valueAt(i).get();
- if (DEBUG) {
+ if (isLoggable) {
Log.d(TAG, "runForEachView for autofillId = " + (view != null
? view.getAutofillId() : " null"));
}
if (view == null || view.getViewTranslationCallback() == null) {
- if (DEBUG) {
+ if (isLoggable) {
Log.d(TAG, "View was gone or ViewTranslationCallback for autofillId "
+ "= " + views.keyAt(i));
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 088065d2f77d..7931d1a06d39 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -8095,12 +8095,14 @@ public class Editor {
private boolean mIsInsertModeActive;
private InsertModeTransformationMethod mInsertModeTransformationMethod;
private final Paint mHighlightPaint;
+ private final Path mHighlightPath;
InsertModeController(@NonNull TextView textView) {
mTextView = Objects.requireNonNull(textView);
mIsInsertModeActive = false;
mInsertModeTransformationMethod = null;
mHighlightPaint = new Paint();
+ mHighlightPath = new Path();
// The highlight color is supposed to be 12% of the color primary40. We can't
// directly access Material 3 theme. But because Material 3 sets the colorPrimary to
@@ -8168,10 +8170,8 @@ public class Editor {
((InsertModeTransformationMethod.TransformedText) transformedText);
final int highlightStart = insertModeTransformedText.getHighlightStart();
final int highlightEnd = insertModeTransformedText.getHighlightEnd();
- final Layout.SelectionRectangleConsumer consumer =
- (left, top, right, bottom, textSelectionLayout) ->
- canvas.drawRect(left, top, right, bottom, mHighlightPaint);
- layout.getSelection(highlightStart, highlightEnd, consumer);
+ layout.getSelectionPath(highlightStart, highlightEnd, mHighlightPath);
+ canvas.drawPath(mHighlightPath, mHighlightPaint);
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 18874f768929..3165654d806d 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1780,6 +1780,21 @@ public class RemoteViews implements Parcelable, Filter {
Object value = getParameterValue(view);
try {
MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
+ // Upload the bitmap to GPU if the parameter is of type Bitmap or Icon.
+ // Since bitmaps in framework are seldomly modified, this is supposed to accelerate
+ // the operations.
+ if (value instanceof Bitmap bitmap) {
+ bitmap.prepareToDraw();
+ }
+
+ if (value instanceof Icon icon
+ && (icon.getType() == Icon.TYPE_BITMAP
+ || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) {
+ Bitmap bitmap = icon.getBitmap();
+ if (bitmap != null) {
+ bitmap.prepareToDraw();
+ }
+ }
if (method != null) {
Runnable endAction = (Runnable) method.invoke(view, value);
diff --git a/core/java/android/window/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java
index 8012a1c26bac..c47572313eeb 100644
--- a/core/java/android/window/BackMotionEvent.java
+++ b/core/java/android/window/BackMotionEvent.java
@@ -34,6 +34,8 @@ public final class BackMotionEvent implements Parcelable {
private final float mTouchX;
private final float mTouchY;
private final float mProgress;
+ private final float mVelocityX;
+ private final float mVelocityY;
@BackEvent.SwipeEdge
private final int mSwipeEdge;
@@ -43,19 +45,32 @@ public final class BackMotionEvent implements Parcelable {
/**
* Creates a new {@link BackMotionEvent} instance.
*
+ * <p>Note: Velocity is only computed for last event, for performance reasons.</p>
+ *
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
+ * @param velocityX X velocity computed from the touch point of this event.
+ * Value in pixels/second. {@link Float#NaN} if was not computed.
+ * @param velocityY Y velocity computed from the touch point of this event.
+ * Value in pixels/second. {@link Float#NaN} if was not computed.
* @param swipeEdge Indicates which edge the swipe starts from.
* @param departingAnimationTarget The remote animation target of the departing
* application window.
*/
- public BackMotionEvent(float touchX, float touchY, float progress,
+ public BackMotionEvent(
+ float touchX,
+ float touchY,
+ float progress,
+ float velocityX,
+ float velocityY,
@BackEvent.SwipeEdge int swipeEdge,
@Nullable RemoteAnimationTarget departingAnimationTarget) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
+ mVelocityX = velocityX;
+ mVelocityY = velocityY;
mSwipeEdge = swipeEdge;
mDepartingAnimationTarget = departingAnimationTarget;
}
@@ -64,6 +79,8 @@ public final class BackMotionEvent implements Parcelable {
mTouchX = in.readFloat();
mTouchY = in.readFloat();
mProgress = in.readFloat();
+ mVelocityX = in.readFloat();
+ mVelocityY = in.readFloat();
mSwipeEdge = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
@@ -91,11 +108,27 @@ public final class BackMotionEvent implements Parcelable {
dest.writeFloat(mTouchX);
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
+ dest.writeFloat(mVelocityX);
+ dest.writeFloat(mVelocityY);
dest.writeInt(mSwipeEdge);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
+ * Returns the absolute X location of the touch point.
+ */
+ public float getTouchX() {
+ return mTouchX;
+ }
+
+ /**
+ * Returns the absolute Y location of the touch point.
+ */
+ public float getTouchY() {
+ return mTouchY;
+ }
+
+ /**
* Returns the progress of a {@link BackEvent}.
*
* @see BackEvent#getProgress()
@@ -106,17 +139,21 @@ public final class BackMotionEvent implements Parcelable {
}
/**
- * Returns the absolute X location of the touch point.
+ * Returns the X velocity computed from the touch point.
+ *
+ * @return value in pixels/second or {@link Float#NaN} if was not computed.
*/
- public float getTouchX() {
- return mTouchX;
+ public float getVelocityX() {
+ return mVelocityX;
}
/**
- * Returns the absolute Y location of the touch point.
+ * Returns the Y velocity computed from the touch point.
+ *
+ * @return value in pixels/second or {@link Float#NaN} if was not computed.
*/
- public float getTouchY() {
- return mTouchY;
+ public float getVelocityY() {
+ return mVelocityY;
}
/**
@@ -143,6 +180,8 @@ public final class BackMotionEvent implements Parcelable {
+ "mTouchX=" + mTouchX
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ + ", mVelocityX=" + mVelocityX
+ + ", mVelocityY=" + mVelocityY
+ ", mSwipeEdge" + mSwipeEdge
+ ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
index ad0d1a401991..380118846dc7 100644
--- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
+++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl
@@ -20,6 +20,7 @@ import android.hardware.soundtrigger.SoundTrigger;
import android.service.voice.HotwordDetectedResult;
import android.service.voice.HotwordDetectionServiceFailure;
import android.service.voice.HotwordRejectedResult;
+import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionServiceFailure;
/**
@@ -57,13 +58,6 @@ oneway interface IHotwordRecognitionStatusCallback {
void onRejected(in HotwordRejectedResult result);
/**
- * Called when the detection fails due to an error.
- *
- * @param status The error code that was seen.
- */
- void onError(int status);
-
- /**
* Called when the detection fails due to an error occurs in the
* {@link HotwordDetectionService}.
*
@@ -84,6 +78,15 @@ oneway interface IHotwordRecognitionStatusCallback {
in VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure);
/**
+ * Called when the detection fails due to an error occurs in the
+ * {@link com.android.server.soundtrigger.SoundTriggerService}.
+ *
+ * @param soundTriggerFailure It provides the error code, error message and
+ * suggested action.
+ */
+ void onSoundTriggerFailure(in SoundTriggerFailure soundTriggerFailure);
+
+ /**
* Called when the detection fails due to an unknown error occurs.
*
* @param errorMessage It provides the error message.
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index ab7f602e2dfc..ed751cb481c5 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -16,8 +16,9 @@
package com.android.internal.app;
-import android.media.permission.Identity;
import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
import com.android.internal.app.ISoundTriggerSession;
/**
@@ -74,4 +75,8 @@ interface ISoundTriggerService {
*/
List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity);
+ /**
+ * Attach an HAL injection interface.
+ */
+ void attachInjection(ISoundTriggerInjection injection);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 6b40d9873fbb..24d5afc42d8f 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -96,6 +96,21 @@ interface IVoiceInteractionManagerService {
* @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES
*/
int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
+
+ /**
+ * Override the persistent enrolled model database with an in-memory
+ * fake for testing purposes.
+ *
+ * @param enabled - {@code true} to enable the test database. {@code false} to enable
+ * the real, persistent database.
+ * @param token - IBinder used to register a death listener to clean-up the override
+ * if tests do not clean up gracefully.
+ */
+ @EnforcePermission("MANAGE_VOICE_KEYPHRASES")
+ @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" +
+ "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)")
+ void setModelDatabaseForTestEnabled(boolean enabled, IBinder token);
+
/**
* Indicates if there's a keyphrase sound model available for the given keyphrase ID and the
* user ID of the caller.
@@ -106,6 +121,7 @@ interface IVoiceInteractionManagerService {
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
*/
boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale);
+
/**
* Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale,
* and the user ID of the caller.
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index e47c335f9c7e..73914a2a99f6 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -16,7 +16,6 @@
package com.android.internal.app;
-import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.UNLAUNCHABLE_APP_WORK_PAUSED_TITLE;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -87,17 +86,13 @@ public class UnlaunchableAppActivity extends Activity
mTelecomManager.getDefaultDialerPackage(UserHandle.of(mUserId))));
final AlertDialog.Builder builder;
- final String dialogMessage;
if (showEmergencyCallButton) {
builder = new AlertDialog.Builder(this, R.style.AlertDialogWithEmergencyButton);
- dialogMessage = getDialogMessage(R.string.work_mode_dialer_off_message);
builder.setNeutralButton(R.string.work_mode_emergency_call_button, this);
} else {
builder = new AlertDialog.Builder(this);
- dialogMessage = getDialogMessage(R.string.work_mode_off_message);
}
builder.setTitle(getDialogTitle())
- .setMessage(dialogMessage)
.setOnDismissListener(this)
.setPositiveButton(R.string.work_mode_turn_on, this)
.setNegativeButton(R.string.cancel, null);
@@ -120,12 +115,6 @@ public class UnlaunchableAppActivity extends Activity
UNLAUNCHABLE_APP_WORK_PAUSED_TITLE, () -> getString(R.string.work_mode_off_title));
}
- private String getDialogMessage(int dialogMessageString) {
- return getSystemService(DevicePolicyManager.class).getResources().getString(
- UNLAUNCHABLE_APP_WORK_PAUSED_MESSAGE,
- () -> getString(dialogMessageString));
- }
-
@Override
public void onDismiss(DialogInterface dialog) {
finish();
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
deleted file mode 100644
index 4a46d91efbf0..000000000000
--- a/core/java/com/android/internal/expresslog/Counter.java
+++ /dev/null
@@ -1,71 +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.internal.expresslog;
-
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-/** Counter encapsulates StatsD write API calls */
-public final class Counter {
-
- // Not instantiable.
- private Counter() {}
-
- /**
- * Increments Telemetry Express Counter metric by 1
- * @param metricId to log, no-op if metricId is not defined in the TeX catalog
- * @hide
- */
- public static void logIncrement(@NonNull String metricId) {
- logIncrement(metricId, 1);
- }
-
- /**
- * Increments Telemetry Express Counter metric by 1
- * @param metricId to log, no-op if metricId is not defined in the TeX catalog
- * @param uid used as a dimension for the count metric
- * @hide
- */
- public static void logIncrementWithUid(@NonNull String metricId, int uid) {
- logIncrementWithUid(metricId, uid, 1);
- }
-
- /**
- * Increments Telemetry Express Counter metric by arbitrary value
- * @param metricId to log, no-op if metricId is not defined in the TeX catalog
- * @param amount to increment counter
- * @hide
- */
- public static void logIncrement(@NonNull String metricId, long amount) {
- final long metricIdHash = Utils.hashString(metricId);
- FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
- }
-
- /**
- * Increments Telemetry Express Counter metric by arbitrary value
- * @param metricId to log, no-op if metricId is not defined in the TeX catalog
- * @param uid used as a dimension for the count metric
- * @param amount to increment counter
- * @hide
- */
- public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) {
- final long metricIdHash = Utils.hashString(metricId);
- FrameworkStatsLog.write(
- FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid);
- }
-}
diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java
deleted file mode 100644
index 2fe784a5a855..000000000000
--- a/core/java/com/android/internal/expresslog/Histogram.java
+++ /dev/null
@@ -1,234 +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.internal.expresslog;
-
-import android.annotation.FloatRange;
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.Arrays;
-
-/** Histogram encapsulates StatsD write API calls */
-public final class Histogram {
-
- private final long mMetricIdHash;
- private final BinOptions mBinOptions;
-
- /**
- * Creates Histogram metric logging wrapper
- *
- * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog
- * @param binOptions to calculate bin index for samples
- * @hide
- */
- public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) {
- mMetricIdHash = Utils.hashString(metricId);
- mBinOptions = binOptions;
- }
-
- /**
- * Logs increment sample count for automatically calculated bin
- *
- * @param sample value
- * @hide
- */
- public void logSample(float sample) {
- final int binIndex = mBinOptions.getBinForSample(sample);
- FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash,
- /*count*/ 1, binIndex);
- }
-
- /**
- * Logs increment sample count for automatically calculated bin
- *
- * @param uid used as a dimension for the count metric
- * @param sample value
- * @hide
- */
- public void logSampleWithUid(int uid, float sample) {
- final int binIndex = mBinOptions.getBinForSample(sample);
- FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED,
- mMetricIdHash, /*count*/ 1, binIndex, uid);
- }
-
- /** Used by Histogram to map data sample to corresponding bin */
- public interface BinOptions {
- /**
- * Returns bins count to be used by a histogram
- *
- * @return bins count used to initialize Options, including overflow & underflow bins
- * @hide
- */
- int getBinsCount();
-
- /**
- * Returns bin index for the input sample value
- * index == 0 stands for underflow
- * index == getBinsCount() - 1 stands for overflow
- *
- * @return zero based index
- * @hide
- */
- int getBinForSample(float sample);
- }
-
- /** Used by Histogram to map data sample to corresponding bin for uniform bins */
- public static final class UniformOptions implements BinOptions {
-
- private final int mBinCount;
- private final float mMinValue;
- private final float mExclusiveMaxValue;
- private final float mBinSize;
-
- /**
- * Creates options for uniform (linear) sized bins
- *
- * @param binCount amount of histogram bins. 2 bin indexes will be calculated
- * automatically to represent underflow & overflow bins
- * @param minValue is included in the first bin, values less than minValue
- * go to underflow bin
- * @param exclusiveMaxValue is included in the overflow bucket. For accurate
- * measure up to kMax, then exclusiveMaxValue
- * should be set to kMax + 1
- * @hide
- */
- public UniformOptions(@IntRange(from = 1) int binCount, float minValue,
- float exclusiveMaxValue) {
- if (binCount < 1) {
- throw new IllegalArgumentException("Bin count should be positive number");
- }
-
- if (exclusiveMaxValue <= minValue) {
- throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)");
- }
-
- mMinValue = minValue;
- mExclusiveMaxValue = exclusiveMaxValue;
- mBinSize = (mExclusiveMaxValue - minValue) / binCount;
-
- // Implicitly add 2 for the extra underflow & overflow bins
- mBinCount = binCount + 2;
- }
-
- @Override
- public int getBinsCount() {
- return mBinCount;
- }
-
- @Override
- public int getBinForSample(float sample) {
- if (sample < mMinValue) {
- // goes to underflow
- return 0;
- } else if (sample >= mExclusiveMaxValue) {
- // goes to overflow
- return mBinCount - 1;
- }
- return (int) ((sample - mMinValue) / mBinSize + 1);
- }
- }
-
- /** Used by Histogram to map data sample to corresponding bin for scaled bins */
- public static final class ScaledRangeOptions implements BinOptions {
- // store minimum value per bin
- final long[] mBins;
-
- /**
- * Creates options for scaled range bins
- *
- * @param binCount amount of histogram bins. 2 bin indexes will be calculated
- * automatically to represent underflow & overflow bins
- * @param minValue is included in the first bin, values less than minValue
- * go to underflow bin
- * @param firstBinWidth used to represent first bin width and as a reference to calculate
- * width for consecutive bins
- * @param scaleFactor used to calculate width for consecutive bins
- * @hide
- */
- public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue,
- @FloatRange(from = 1.f) float firstBinWidth,
- @FloatRange(from = 1.f) float scaleFactor) {
- if (binCount < 1) {
- throw new IllegalArgumentException("Bin count should be positive number");
- }
-
- if (firstBinWidth < 1.f) {
- throw new IllegalArgumentException(
- "First bin width invalid (should be 1.f at minimum)");
- }
-
- if (scaleFactor < 1.f) {
- throw new IllegalArgumentException(
- "Scaled factor invalid (should be 1.f at minimum)");
- }
-
- // precalculating bins ranges (no need to create a bin for underflow reference value)
- mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor);
- }
-
- @Override
- public int getBinsCount() {
- return mBins.length + 1;
- }
-
- @Override
- public int getBinForSample(float sample) {
- if (sample < mBins[0]) {
- // goes to underflow
- return 0;
- } else if (sample >= mBins[mBins.length - 1]) {
- // goes to overflow
- return mBins.length;
- }
-
- return lower_bound(mBins, (long) sample) + 1;
- }
-
- // To find lower bound using binary search implementation of Arrays utility class
- private static int lower_bound(long[] array, long sample) {
- int index = Arrays.binarySearch(array, sample);
- // If key is not present in the array
- if (index < 0) {
- // Index specify the position of the key when inserted in the sorted array
- // so the element currently present at this position will be the lower bound
- return Math.abs(index) - 2;
- }
- return index;
- }
-
- private static long[] initBins(int count, int minValue, float firstBinWidth,
- float scaleFactor) {
- long[] bins = new long[count];
- bins[0] = minValue;
- double lastWidth = firstBinWidth;
- for (int i = 1; i < count; i++) {
- // current bin minValue = previous bin width * scaleFactor
- double currentBinMinValue = bins[i - 1] + lastWidth;
- if (currentBinMinValue > Integer.MAX_VALUE) {
- throw new IllegalArgumentException(
- "Attempted to create a bucket larger than maxint");
- }
-
- bins[i] = (long) currentBinMinValue;
- lastWidth *= scaleFactor;
- }
- return bins;
- }
- }
-}
diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS
deleted file mode 100644
index ee865b1e4ec8..000000000000
--- a/core/java/com/android/internal/expresslog/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/stats/OWNERS
diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf80a1e6..000000000000
--- a/core/java/com/android/internal/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presubmit": [
- {
- "name": "ExpressLogTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ]
-} \ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
index e2c096c11fee..aa6b1c00d186 100644
--- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
+++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java
@@ -20,7 +20,7 @@ import android.annotation.Nullable;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
import java.io.IOException;
import java.util.Arrays;
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
index f6d80a572c75..8fe2b9cdf1e5 100644
--- a/core/java/com/android/internal/util/DumpUtils.java
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -25,6 +25,7 @@ import android.os.Binder;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -312,5 +313,85 @@ public final class DumpUtils {
|| cn.flattenToString().toLowerCase().contains(filterString.toLowerCase());
};
}
-}
+ /**
+ * Lambda used to dump a key (and its index) while iterating though a collection.
+ */
+ public interface KeyDumper {
+
+ /** Dumps the index and key.*/
+ void dump(int index, int key);
+ }
+
+ /**
+ * Lambda used to dump a value while iterating though a collection.
+ *
+ * @param <T> type of the value.
+ */
+ public interface ValueDumper<T> {
+
+ /** Dumps the value.*/
+ void dump(T value);
+ }
+
+ /**
+ * Dumps a sparse array.
+ */
+ public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array,
+ String name) {
+ dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null);
+ }
+
+ /**
+ * Dumps the values of a sparse array.
+ */
+ public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix,
+ SparseArray<T> array, String name) {
+ dumpSparseArray(pw, prefix, array, name, (i, k) -> {
+ pw.printf("%s%s", prefix, prefix);
+ }, /* valueDumper= */ null);
+ }
+
+ /**
+ * Dumps a sparse array, customizing each line.
+ */
+ public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array,
+ String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) {
+ int size = array.size();
+ if (size == 0) {
+ pw.print(prefix);
+ pw.print("No ");
+ pw.print(name);
+ pw.println("s");
+ return;
+ }
+ pw.print(prefix);
+ pw.print(size);
+ pw.print(' ');
+ pw.print(name);
+ pw.println("(s):");
+
+ String prefix2 = prefix + prefix;
+ for (int i = 0; i < size; i++) {
+ int key = array.keyAt(i);
+ T value = array.valueAt(i);
+ if (keyDumper != null) {
+ keyDumper.dump(i, key);
+ } else {
+ pw.print(prefix2);
+ pw.print(i);
+ pw.print(": ");
+ pw.print(key);
+ pw.print("->");
+ }
+ if (value == null) {
+ pw.print("(null)");
+ } else if (valueDumper != null) {
+ valueDumper.dump(value);
+ } else {
+ pw.print(value);
+ }
+ pw.println();
+ }
+ }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index fc26766d7090..6bec6bc236bd 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -225,7 +225,6 @@ cc_library_shared {
"android_security_Scrypt.cpp",
"com_android_internal_content_om_OverlayConfig.cpp",
"com_android_internal_content_om_OverlayManagerImpl.cpp",
- "com_android_internal_expresslog_Utils.cpp",
"com_android_internal_net_NetworkUtilsInternal.cpp",
"com_android_internal_os_ClassLoaderFactory.cpp",
"com_android_internal_os_FuseAppLoop.cpp",
@@ -262,6 +261,7 @@ cc_library_shared {
"libstatssocket_lazy",
"libskia",
"libtextclassifier_hash_static",
+ "libexpresslog_jni",
],
shared_libs: [
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b550f28e0934..21bdf099f18d 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -200,7 +200,7 @@ extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env);
-extern int register_com_android_internal_expresslog_Utils(JNIEnv* env);
+extern int register_com_android_modules_expresslog_Utils(JNIEnv* env);
extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1586,7 +1586,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_os_incremental_IncrementalManager),
REG_JNI(register_com_android_internal_content_om_OverlayConfig),
REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl),
- REG_JNI(register_com_android_internal_expresslog_Utils),
+ REG_JNI(register_com_android_modules_expresslog_Utils),
REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index bce533281186..4e4abec88040 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -104,6 +104,3 @@ per-file com_android_internal_os_*MultiStateCounter* = file:/BATTERY_STATS_OWNER
# PM
per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS
-
-# Stats/expresslog
-per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 8ba4eed8b34d..e1be0cd80bb6 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2481,36 +2481,31 @@ static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject th
if (jSurroundFormats == nullptr) {
ALOGE("jSurroundFormats is NULL");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
}
if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) {
ALOGE("getSurroundFormats not a map");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
}
jint jStatus;
unsigned int numSurroundFormats = 0;
- audio_format_t *surroundFormats = nullptr;
- bool *surroundFormatsEnabled = nullptr;
- status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
- surroundFormatsEnabled);
+ status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr);
if (status != NO_ERROR) {
ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
- jStatus = nativeToJavaStatus(status);
- goto exit;
+ return nativeToJavaStatus(status);
}
if (numSurroundFormats == 0) {
- jStatus = (jint)AUDIO_JAVA_SUCCESS;
- goto exit;
+ return static_cast<jint>(AUDIO_JAVA_SUCCESS);
}
- surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
- surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool));
- status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats,
- surroundFormatsEnabled);
+ auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+ auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats);
+ status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0],
+ &surroundFormatsEnabled[0]);
jStatus = nativeToJavaStatus(status);
if (status != NO_ERROR) {
ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status);
- goto exit;
+ return jStatus;
}
for (size_t i = 0; i < numSurroundFormats; i++) {
int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2526,9 +2521,6 @@ static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject th
env->DeleteLocalRef(enabled);
}
-exit:
- free(surroundFormats);
- free(surroundFormatsEnabled);
return jStatus;
}
@@ -2538,31 +2530,28 @@ static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jo
if (jSurroundFormats == nullptr) {
ALOGE("jSurroundFormats is NULL");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
}
if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) {
ALOGE("jSurroundFormats not an arraylist");
- return (jint)AUDIO_JAVA_BAD_VALUE;
+ return static_cast<jint>(AUDIO_JAVA_BAD_VALUE);
}
jint jStatus;
unsigned int numSurroundFormats = 0;
- audio_format_t *surroundFormats = nullptr;
- status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+ status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr);
if (status != NO_ERROR) {
ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
- jStatus = nativeToJavaStatus(status);
- goto exit;
+ return nativeToJavaStatus(status);
}
if (numSurroundFormats == 0) {
- jStatus = (jint)AUDIO_JAVA_SUCCESS;
- goto exit;
+ return static_cast<jint>(AUDIO_JAVA_SUCCESS);
}
- surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t));
- status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats);
+ auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats);
+ status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]);
jStatus = nativeToJavaStatus(status);
if (status != NO_ERROR) {
ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status);
- goto exit;
+ return jStatus;
}
for (size_t i = 0; i < numSurroundFormats; i++) {
int audioFormat = audioFormatFromNative(surroundFormats[i]);
@@ -2576,8 +2565,6 @@ static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jo
env->DeleteLocalRef(surroundFormat);
}
-exit:
- free(surroundFormats);
return jStatus;
}
diff --git a/core/jni/com_android_internal_expresslog_Utils.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp
deleted file mode 100644
index d33a7bda27f7..000000000000
--- a/core/jni/com_android_internal_expresslog_Utils.cpp
+++ /dev/null
@@ -1,57 +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.
- */
-
-#include <nativehelper/JNIHelp.h>
-#include <utils/hash/farmhash.h>
-
-#include "core_jni_helpers.h"
-
-// ----------------------------------------------------------------------------
-// JNI Glue
-// ----------------------------------------------------------------------------
-
-static jclass g_stringClass = nullptr;
-
-/**
- * Class: com_android_internal_expresslog_Utils
- * Method: hashString
- * Signature: (Ljava/lang/String;)J
- */
-static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
- ScopedUtfChars name(env, metricNameObj);
- if (name.c_str() == nullptr) {
- return 0;
- }
-
- return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
-}
-
-static const JNINativeMethod g_methods[] = {
- {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
-};
-
-static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils";
-
-namespace android {
-
-int register_com_android_internal_expresslog_Utils(JNIEnv* env) {
- jclass stringClass = FindClassOrDie(env, "java/lang/String");
- g_stringClass = MakeGlobalRefOrDie(env, stringClass);
-
- return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods));
-}
-
-} // namespace android
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index ed612a05e6b5..025a57d0e334 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -1035,6 +1035,7 @@ message AppsExitInfoProto {
optional int32 uid = 1;
repeated .android.app.ApplicationExitInfoProto app_exit_info = 2;
+ repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3;
}
repeated User users = 2;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78d39236b392..05b38a562e29 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1746,37 +1746,6 @@
android:protectionLevel="dangerous"
android:permissionFlags="hardRestricted" />
- <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
- <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
- <p>Protection level: dangerous
- @hide
- -->
- <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_bodySensorsWristTemperature"
- android:description="@string/permdesc_bodySensorsWristTemperature"
- android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
- android:protectionLevel="dangerous" />
-
- <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors.
- If you're requesting this permission, you must also request
- {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't
- give you wrist temperature body sensors access.
- <p class="note"><strong>Note: </strong> This permission is for Wear OS only.
- <p>Protection level: dangerous
-
- <p> This is a hard restricted permission which cannot be held by an app until
- the installer on record allowlists the permission. For more details see
- {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
- @hide
- -->
- <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"
- android:permissionGroup="android.permission-group.UNDEFINED"
- android:label="@string/permlab_bodySensors_wristTemperature_background"
- android:description="@string/permdesc_bodySensors_wristTemperature_background"
- android:protectionLevel="dangerous"
- android:permissionFlags="hardRestricted" />
-
<!-- Allows an app to use fingerprint hardware.
<p>Protection level: normal
@deprecated Applications should request {@link
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3ff63519572f..5544701d9325 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -534,8 +534,10 @@
<!-- If this is true, long press on power button will be available from the non-interactive state -->
<bool name="config_supportLongPressPowerWhenNonInteractive">false</bool>
- <!-- If this is true, then keep dreaming when undocking. -->
- <bool name="config_keepDreamingWhenUndocking">false</bool>
+ <!-- If this is true, then keep dreaming when unplugging.
+ This config was formerly known as config_keepDreamingWhenUndocking.
+ It has been updated to affect other plug types. -->
+ <bool name="config_keepDreamingWhenUnplugging">false</bool>
<!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if
it becomes disconnected -->
@@ -1373,6 +1375,9 @@
<!-- Number of notifications to keep in the notification service historical archive -->
<integer name="config_notificationServiceArchiveSize">100</integer>
+ <!-- List of packages that will be able to use full screen intent in notifications by default -->
+ <string-array name="config_useFullScreenIntentPackages" translatable="false" />
+
<!-- Allow the menu hard key to be disabled in LockScreen on some devices -->
<bool name="config_disableMenuKeyInLockScreen">false</bool>
@@ -4301,8 +4306,12 @@
This package must be trusted, as it has the permissions to control other applications
on the device.
Example: "com.android.wellbeing"
+
+ Note: This config is deprecated, please use config_systemWellbeing instead.
-->
- <string name="config_defaultWellbeingPackage" translatable="false"></string>
+ <string name="config_defaultWellbeingPackage" translatable="false">
+ @string/config_systemWellbeing
+ </string>
<!-- The component name for the default system attention service.
This service must be trusted, as it can be activated without explicit consent of the user.
@@ -6067,6 +6076,12 @@
<!-- Wear OS: the name of the main activity of the device's sysui. -->
<string name="config_wearSysUiMainActivity" translatable="false"/>
+ <!-- Wear OS: the name of the package containing the Media Controls Activity. -->
+ <string name="config_wearMediaControlsPackage" translatable="false"/>
+
+ <!-- Wear OS: the name of the package containing the Media Sessions APK. -->
+ <string name="config_wearMediaSessionsPackage" translatable="false"/>
+
<bool name="config_secondaryBuiltInDisplayIsRound">@bool/config_windowIsRound</bool>
<!-- The display round config for each display in a multi-display device. -->
diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml
index 85325fec7277..daa0f553f47a 100644
--- a/core/res/res/values/public-final.xml
+++ b/core/res/res/values/public-final.xml
@@ -3402,4 +3402,343 @@
<!-- @hide @SystemApi -->
<public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" />
+ <!-- ===============================================================
+ Resources added in version U of the platform
+
+ NOTE: After this version of the platform is forked, changes cannot be made to the root
+ branch's groups for that release. Only merge changes to the forked platform branch.
+ =============================================================== -->
+ <eat-comment/>
+
+ <staging-public-group-final type="attr" first-id="0x01ce0000">
+ <public name="handwritingBoundsOffsetLeft" />
+ <public name="handwritingBoundsOffsetTop" />
+ <public name="handwritingBoundsOffsetRight" />
+ <public name="handwritingBoundsOffsetBottom" />
+ <public name="accessibilityDataSensitive" />
+ <public name="enableTextStylingShortcuts" />
+ <public name="requiredDisplayCategory"/>
+ <public name="removed_maxConcurrentSessionsCount" />
+ <public name="visualQueryDetectionService" />
+ <public name="physicalKeyboardHintLanguageTag" />
+ <public name="physicalKeyboardHintLayoutType" />
+ <public name="allowSharedIsolatedProcess" />
+ <public name="keyboardLocale" />
+ <public name="keyboardLayoutType" />
+ <public name="allowUpdateOwnership" />
+ <public name="isCredential"/>
+ <public name="searchResultHighlightColor" />
+ <public name="focusedSearchResultHighlightColor" />
+ <public name="stylusHandwritingSettingsActivity" />
+ <public name="windowNoMoveAnimation" />
+ <public name="settingsSubtitle" />
+ <public name="capability" />
+ </staging-public-group-final>
+
+ <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" />
+ <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" />
+ <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" />
+ <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" />
+ <public type="attr" name="accessibilityDataSensitive" id="0x01010677" />
+ <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" />
+ <public type="attr" name="requiredDisplayCategory" id="0x01010679" />
+ <public type="attr" name="visualQueryDetectionService" id="0x0101067a" />
+ <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" />
+ <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" />
+ <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" />
+ <public type="attr" name="keyboardLocale" id="0x0101067e" />
+ <public type="attr" name="keyboardLayoutType" id="0x0101067f" />
+ <public type="attr" name="allowUpdateOwnership" id="0x01010680" />
+ <public type="attr" name="isCredential" id="0x01010681" />
+ <public type="attr" name="searchResultHighlightColor" id="0x01010682" />
+ <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" />
+ <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" />
+ <public type="attr" name="windowNoMoveAnimation" id="0x01010685" />
+ <public type="attr" name="settingsSubtitle" id="0x01010686" />
+ <public type="attr" name="capability" id="0x01010687" />
+
+ <staging-public-group-final type="id" first-id="0x01cd0000">
+ <public name="bold" />
+ <public name="italic" />
+ <public name="underline" />
+ <public name="accessibilityActionScrollInDirection" />
+ </staging-public-group-final>
+
+ <public type="id" name="bold" id="0x0102005b" />
+ <public type="id" name="italic" id="0x0102005c" />
+ <public type="id" name="underline" id="0x0102005d" />
+ <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" />
+
+ <staging-public-group-final type="string" first-id="0x01cb0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_systemWearHealthService" />
+ <!-- @hide @SystemApi -->
+ <public name="config_defaultNotes" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemFinancedDeviceController" />
+ <!-- @hide @SystemApi -->
+ <public name="config_systemCallStreaming" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_systemWearHealthService" id="0x01040044" />
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_defaultNotes" id="0x01040045" />
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" />
+ <!-- @hide @SystemApi -->
+ <public type="string" name="config_systemCallStreaming" id="0x01040047" />
+
+ <staging-public-group-final type="dimen" first-id="0x01ca0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi -->
+ <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" />
+
+ <staging-public-group-final type="color" first-id="0x01c90000">
+ <public name="system_primary_container_light" />
+ <public name="system_on_primary_container_light" />
+ <public name="system_primary_light" />
+ <public name="system_on_primary_light" />
+ <public name="system_secondary_container_light" />
+ <public name="system_on_secondary_container_light" />
+ <public name="system_secondary_light" />
+ <public name="system_on_secondary_light" />
+ <public name="system_tertiary_container_light" />
+ <public name="system_on_tertiary_container_light" />
+ <public name="system_tertiary_light" />
+ <public name="system_on_tertiary_light" />
+ <public name="system_background_light" />
+ <public name="system_on_background_light" />
+ <public name="system_surface_light" />
+ <public name="system_on_surface_light" />
+ <public name="system_surface_container_low_light" />
+ <public name="system_surface_container_lowest_light" />
+ <public name="system_surface_container_light" />
+ <public name="system_surface_container_high_light" />
+ <public name="system_surface_container_highest_light" />
+ <public name="system_surface_bright_light" />
+ <public name="system_surface_dim_light" />
+ <public name="system_surface_variant_light" />
+ <public name="system_on_surface_variant_light" />
+ <public name="system_outline_light" />
+ <public name="system_error_light" />
+ <public name="system_on_error_light" />
+ <public name="system_error_container_light" />
+ <public name="system_on_error_container_light" />
+ <public name="removed_system_primary_fixed_light" />
+ <public name="removed_system_primary_fixed_dim_light" />
+ <public name="removed_system_on_primary_fixed_light" />
+ <public name="removed_system_on_primary_fixed_variant_light" />
+ <public name="removed_system_secondary_fixed_light" />
+ <public name="removed_system_secondary_fixed_dim_light" />
+ <public name="removed_system_on_secondary_fixed_light" />
+ <public name="removed_system_on_secondary_fixed_variant_light" />
+ <public name="removed_system_tertiary_fixed_light" />
+ <public name="removed_system_tertiary_fixed_dim_light" />
+ <public name="removed_system_on_tertiary_fixed_light" />
+ <public name="removed_system_on_tertiary_fixed_variant_light" />
+ <public name="system_control_activated_light" />
+ <public name="system_control_normal_light" />
+ <public name="system_control_highlight_light" />
+ <public name="system_text_primary_inverse_light" />
+ <public name="system_text_secondary_and_tertiary_inverse_light" />
+ <public name="system_text_primary_inverse_disable_only_light" />
+ <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
+ <public name="system_text_hint_inverse_light" />
+ <public name="system_palette_key_color_primary_light" />
+ <public name="system_palette_key_color_secondary_light" />
+ <public name="system_palette_key_color_tertiary_light" />
+ <public name="system_palette_key_color_neutral_light" />
+ <public name="system_palette_key_color_neutral_variant_light" />
+ <public name="system_primary_container_dark"/>
+ <public name="system_on_primary_container_dark"/>
+ <public name="system_primary_dark"/>
+ <public name="system_on_primary_dark"/>
+ <public name="system_secondary_container_dark"/>
+ <public name="system_on_secondary_container_dark"/>
+ <public name="system_secondary_dark"/>
+ <public name="system_on_secondary_dark"/>
+ <public name="system_tertiary_container_dark"/>
+ <public name="system_on_tertiary_container_dark"/>
+ <public name="system_tertiary_dark"/>
+ <public name="system_on_tertiary_dark"/>
+ <public name="system_background_dark"/>
+ <public name="system_on_background_dark"/>
+ <public name="system_surface_dark"/>
+ <public name="system_on_surface_dark"/>
+ <public name="system_surface_container_low_dark"/>
+ <public name="system_surface_container_lowest_dark"/>
+ <public name="system_surface_container_dark"/>
+ <public name="system_surface_container_high_dark"/>
+ <public name="system_surface_container_highest_dark"/>
+ <public name="system_surface_bright_dark"/>
+ <public name="system_surface_dim_dark"/>
+ <public name="system_surface_variant_dark"/>
+ <public name="system_on_surface_variant_dark"/>
+ <public name="system_outline_dark"/>
+ <public name="system_error_dark"/>
+ <public name="system_on_error_dark"/>
+ <public name="system_error_container_dark"/>
+ <public name="system_on_error_container_dark"/>
+ <public name="removed_system_primary_fixed_dark"/>
+ <public name="removed_system_primary_fixed_dim_dark"/>
+ <public name="removed_system_on_primary_fixed_dark"/>
+ <public name="removed_system_on_primary_fixed_variant_dark"/>
+ <public name="removed_system_secondary_fixed_dark"/>
+ <public name="removed_system_secondary_fixed_dim_dark"/>
+ <public name="removed_system_on_secondary_fixed_dark"/>
+ <public name="removed_system_on_secondary_fixed_variant_dark"/>
+ <public name="removed_system_tertiary_fixed_dark"/>
+ <public name="removed_system_tertiary_fixed_dim_dark"/>
+ <public name="removed_system_on_tertiary_fixed_dark"/>
+ <public name="removed_system_on_tertiary_fixed_variant_dark"/>
+ <public name="system_control_activated_dark"/>
+ <public name="system_control_normal_dark"/>
+ <public name="system_control_highlight_dark"/>
+ <public name="system_text_primary_inverse_dark"/>
+ <public name="system_text_secondary_and_tertiary_inverse_dark"/>
+ <public name="system_text_primary_inverse_disable_only_dark"/>
+ <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
+ <public name="system_text_hint_inverse_dark"/>
+ <public name="system_palette_key_color_primary_dark"/>
+ <public name="system_palette_key_color_secondary_dark"/>
+ <public name="system_palette_key_color_tertiary_dark"/>
+ <public name="system_palette_key_color_neutral_dark"/>
+ <public name="system_palette_key_color_neutral_variant_dark"/>
+ <public name="system_primary_fixed" />
+ <public name="system_primary_fixed_dim" />
+ <public name="system_on_primary_fixed" />
+ <public name="system_on_primary_fixed_variant" />
+ <public name="system_secondary_fixed" />
+ <public name="system_secondary_fixed_dim" />
+ <public name="system_on_secondary_fixed" />
+ <public name="system_on_secondary_fixed_variant" />
+ <public name="system_tertiary_fixed" />
+ <public name="system_tertiary_fixed_dim" />
+ <public name="system_on_tertiary_fixed" />
+ <public name="system_on_tertiary_fixed_variant" />
+ <public name="system_outline_variant_light" />
+ <public name="system_outline_variant_dark" />
+ </staging-public-group-final>
+
+ <public type="color" name="system_primary_container_light" id="0x0106005e" />
+ <public type="color" name="system_on_primary_container_light" id="0x0106005f" />
+ <public type="color" name="system_primary_light" id="0x01060060" />
+ <public type="color" name="system_on_primary_light" id="0x01060061" />
+ <public type="color" name="system_secondary_container_light" id="0x01060062" />
+ <public type="color" name="system_on_secondary_container_light" id="0x01060063" />
+ <public type="color" name="system_secondary_light" id="0x01060064" />
+ <public type="color" name="system_on_secondary_light" id="0x01060065" />
+ <public type="color" name="system_tertiary_container_light" id="0x01060066" />
+ <public type="color" name="system_on_tertiary_container_light" id="0x01060067" />
+ <public type="color" name="system_tertiary_light" id="0x01060068" />
+ <public type="color" name="system_on_tertiary_light" id="0x01060069" />
+ <public type="color" name="system_background_light" id="0x0106006a" />
+ <public type="color" name="system_on_background_light" id="0x0106006b" />
+ <public type="color" name="system_surface_light" id="0x0106006c" />
+ <public type="color" name="system_on_surface_light" id="0x0106006d" />
+ <public type="color" name="system_surface_container_low_light" id="0x0106006e" />
+ <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" />
+ <public type="color" name="system_surface_container_light" id="0x01060070" />
+ <public type="color" name="system_surface_container_high_light" id="0x01060071" />
+ <public type="color" name="system_surface_container_highest_light" id="0x01060072" />
+ <public type="color" name="system_surface_bright_light" id="0x01060073" />
+ <public type="color" name="system_surface_dim_light" id="0x01060074" />
+ <public type="color" name="system_surface_variant_light" id="0x01060075" />
+ <public type="color" name="system_on_surface_variant_light" id="0x01060076" />
+ <public type="color" name="system_outline_light" id="0x01060077" />
+ <public type="color" name="system_error_light" id="0x01060078" />
+ <public type="color" name="system_on_error_light" id="0x01060079" />
+ <public type="color" name="system_error_container_light" id="0x0106007a" />
+ <public type="color" name="system_on_error_container_light" id="0x0106007b" />
+ <public type="color" name="system_control_activated_light" id="0x0106007c" />
+ <public type="color" name="system_control_normal_light" id="0x0106007d" />
+ <public type="color" name="system_control_highlight_light" id="0x0106007e" />
+ <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" />
+ <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" />
+ <public type="color" name="system_text_hint_inverse_light" id="0x01060083" />
+ <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" />
+ <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" />
+ <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" />
+ <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" />
+ <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" />
+ <public type="color" name="system_primary_container_dark" id="0x01060089" />
+ <public type="color" name="system_on_primary_container_dark" id="0x0106008a" />
+ <public type="color" name="system_primary_dark" id="0x0106008b" />
+ <public type="color" name="system_on_primary_dark" id="0x0106008c" />
+ <public type="color" name="system_secondary_container_dark" id="0x0106008d" />
+ <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" />
+ <public type="color" name="system_secondary_dark" id="0x0106008f" />
+ <public type="color" name="system_on_secondary_dark" id="0x01060090" />
+ <public type="color" name="system_tertiary_container_dark" id="0x01060091" />
+ <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" />
+ <public type="color" name="system_tertiary_dark" id="0x01060093" />
+ <public type="color" name="system_on_tertiary_dark" id="0x01060094" />
+ <public type="color" name="system_background_dark" id="0x01060095" />
+ <public type="color" name="system_on_background_dark" id="0x01060096" />
+ <public type="color" name="system_surface_dark" id="0x01060097" />
+ <public type="color" name="system_on_surface_dark" id="0x01060098" />
+ <public type="color" name="system_surface_container_low_dark" id="0x01060099" />
+ <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" />
+ <public type="color" name="system_surface_container_dark" id="0x0106009b" />
+ <public type="color" name="system_surface_container_high_dark" id="0x0106009c" />
+ <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" />
+ <public type="color" name="system_surface_bright_dark" id="0x0106009e" />
+ <public type="color" name="system_surface_dim_dark" id="0x0106009f" />
+ <public type="color" name="system_surface_variant_dark" id="0x010600a0" />
+ <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" />
+ <public type="color" name="system_outline_dark" id="0x010600a2" />
+ <public type="color" name="system_error_dark" id="0x010600a3" />
+ <public type="color" name="system_on_error_dark" id="0x010600a4" />
+ <public type="color" name="system_error_container_dark" id="0x010600a5" />
+ <public type="color" name="system_on_error_container_dark" id="0x010600a6" />
+ <public type="color" name="system_control_activated_dark" id="0x010600a7" />
+ <public type="color" name="system_control_normal_dark" id="0x010600a8" />
+ <public type="color" name="system_control_highlight_dark" id="0x010600a9" />
+ <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" />
+ <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" />
+ <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" />
+ <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" />
+ <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" />
+ <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" />
+ <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" />
+ <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" />
+ <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" />
+ <public type="color" name="system_primary_fixed" id="0x010600b4" />
+ <public type="color" name="system_primary_fixed_dim" id="0x010600b5" />
+ <public type="color" name="system_on_primary_fixed" id="0x010600b6" />
+ <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" />
+ <public type="color" name="system_secondary_fixed" id="0x010600b8" />
+ <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" />
+ <public type="color" name="system_on_secondary_fixed" id="0x010600ba" />
+ <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" />
+ <public type="color" name="system_tertiary_fixed" id="0x010600bc" />
+ <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" />
+ <public type="color" name="system_on_tertiary_fixed" id="0x010600be" />
+ <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" />
+ <public type="color" name="system_outline_variant_light" id="0x010600c0" />
+ <public type="color" name="system_outline_variant_dark" id="0x010600c1" />
+
+ <staging-public-group-final type="bool" first-id="0x01be0000">
+ <!-- @hide @SystemApi -->
+ <public name="config_safetyProtectionEnabled" />
+ <!-- @hide @SystemApi -->
+ <public name="config_enableDefaultNotes" />
+ <!-- @hide @SystemApi -->
+ <public name="config_enableDefaultNotesForWorkProfile" />
+ </staging-public-group-final>
+
+ <!-- @hide @SystemApi -->
+ <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" />
+ <!-- @hide @SystemApi -->
+ <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" />
+ <!-- @hide @SystemApi -->
+ <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" />
+
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 9cbf3b679851..49a19407e59f 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -102,231 +102,65 @@
<resources>
<!-- ===============================================================
- Resources added in version U of the platform
+ Resources added in version NEXT of the platform
NOTE: After this version of the platform is forked, changes cannot be made to the root
branch's groups for that release. Only merge changes to the forked platform branch.
=============================================================== -->
<eat-comment/>
- <staging-public-group type="attr" first-id="0x01ce0000">
- <public name="handwritingBoundsOffsetLeft" />
- <public name="handwritingBoundsOffsetTop" />
- <public name="handwritingBoundsOffsetRight" />
- <public name="handwritingBoundsOffsetBottom" />
- <public name="accessibilityDataSensitive" />
- <public name="enableTextStylingShortcuts" />
- <public name="requiredDisplayCategory"/>
- <public name="removed_maxConcurrentSessionsCount" />
- <public name="visualQueryDetectionService" />
- <public name="physicalKeyboardHintLanguageTag" />
- <public name="physicalKeyboardHintLayoutType" />
- <public name="allowSharedIsolatedProcess" />
- <public name="keyboardLocale" />
- <public name="keyboardLayoutType" />
- <public name="allowUpdateOwnership" />
- <public name="isCredential"/>
- <public name="searchResultHighlightColor" />
- <public name="focusedSearchResultHighlightColor" />
- <public name="stylusHandwritingSettingsActivity" />
- <public name="windowNoMoveAnimation" />
- <public name="settingsSubtitle" />
- <public name="capability" />
+ <staging-public-group type="attr" first-id="0x01bd0000">
</staging-public-group>
- <staging-public-group type="id" first-id="0x01cd0000">
- <public name="bold" />
- <public name="italic" />
- <public name="underline" />
- <public name="accessibilityActionScrollInDirection" />
+ <staging-public-group type="id" first-id="0x01bc0000">
</staging-public-group>
- <staging-public-group type="style" first-id="0x01cc0000">
+ <staging-public-group type="style" first-id="0x01bb0000">
</staging-public-group>
- <staging-public-group type="string" first-id="0x01cb0000">
- <!-- @hide @SystemApi -->
- <public name="config_systemWearHealthService" />
- <!-- @hide @SystemApi -->
- <public name="config_defaultNotes" />
- <!-- @hide @SystemApi -->
- <public name="config_systemFinancedDeviceController" />
- <!-- @hide @SystemApi -->
- <public name="config_systemCallStreaming" />
+ <staging-public-group type="string" first-id="0x01ba0000">
</staging-public-group>
- <staging-public-group type="dimen" first-id="0x01ca0000">
- <!-- @hide @SystemApi -->
- <public name="config_viewConfigurationHandwritingGestureLineMargin" />
+ <staging-public-group type="dimen" first-id="0x01b90000">
</staging-public-group>
- <staging-public-group type="color" first-id="0x01c90000">
- <public name="system_primary_container_light" />
- <public name="system_on_primary_container_light" />
- <public name="system_primary_light" />
- <public name="system_on_primary_light" />
- <public name="system_secondary_container_light" />
- <public name="system_on_secondary_container_light" />
- <public name="system_secondary_light" />
- <public name="system_on_secondary_light" />
- <public name="system_tertiary_container_light" />
- <public name="system_on_tertiary_container_light" />
- <public name="system_tertiary_light" />
- <public name="system_on_tertiary_light" />
- <public name="system_background_light" />
- <public name="system_on_background_light" />
- <public name="system_surface_light" />
- <public name="system_on_surface_light" />
- <public name="system_surface_container_low_light" />
- <public name="system_surface_container_lowest_light" />
- <public name="system_surface_container_light" />
- <public name="system_surface_container_high_light" />
- <public name="system_surface_container_highest_light" />
- <public name="system_surface_bright_light" />
- <public name="system_surface_dim_light" />
- <public name="system_surface_variant_light" />
- <public name="system_on_surface_variant_light" />
- <public name="system_outline_light" />
- <public name="system_error_light" />
- <public name="system_on_error_light" />
- <public name="system_error_container_light" />
- <public name="system_on_error_container_light" />
- <public name="removed_system_primary_fixed_light" />
- <public name="removed_system_primary_fixed_dim_light" />
- <public name="removed_system_on_primary_fixed_light" />
- <public name="removed_system_on_primary_fixed_variant_light" />
- <public name="removed_system_secondary_fixed_light" />
- <public name="removed_system_secondary_fixed_dim_light" />
- <public name="removed_system_on_secondary_fixed_light" />
- <public name="removed_system_on_secondary_fixed_variant_light" />
- <public name="removed_system_tertiary_fixed_light" />
- <public name="removed_system_tertiary_fixed_dim_light" />
- <public name="removed_system_on_tertiary_fixed_light" />
- <public name="removed_system_on_tertiary_fixed_variant_light" />
- <public name="system_control_activated_light" />
- <public name="system_control_normal_light" />
- <public name="system_control_highlight_light" />
- <public name="system_text_primary_inverse_light" />
- <public name="system_text_secondary_and_tertiary_inverse_light" />
- <public name="system_text_primary_inverse_disable_only_light" />
- <public name="system_text_secondary_and_tertiary_inverse_disabled_light" />
- <public name="system_text_hint_inverse_light" />
- <public name="system_palette_key_color_primary_light" />
- <public name="system_palette_key_color_secondary_light" />
- <public name="system_palette_key_color_tertiary_light" />
- <public name="system_palette_key_color_neutral_light" />
- <public name="system_palette_key_color_neutral_variant_light" />
- <public name="system_primary_container_dark"/>
- <public name="system_on_primary_container_dark"/>
- <public name="system_primary_dark"/>
- <public name="system_on_primary_dark"/>
- <public name="system_secondary_container_dark"/>
- <public name="system_on_secondary_container_dark"/>
- <public name="system_secondary_dark"/>
- <public name="system_on_secondary_dark"/>
- <public name="system_tertiary_container_dark"/>
- <public name="system_on_tertiary_container_dark"/>
- <public name="system_tertiary_dark"/>
- <public name="system_on_tertiary_dark"/>
- <public name="system_background_dark"/>
- <public name="system_on_background_dark"/>
- <public name="system_surface_dark"/>
- <public name="system_on_surface_dark"/>
- <public name="system_surface_container_low_dark"/>
- <public name="system_surface_container_lowest_dark"/>
- <public name="system_surface_container_dark"/>
- <public name="system_surface_container_high_dark"/>
- <public name="system_surface_container_highest_dark"/>
- <public name="system_surface_bright_dark"/>
- <public name="system_surface_dim_dark"/>
- <public name="system_surface_variant_dark"/>
- <public name="system_on_surface_variant_dark"/>
- <public name="system_outline_dark"/>
- <public name="system_error_dark"/>
- <public name="system_on_error_dark"/>
- <public name="system_error_container_dark"/>
- <public name="system_on_error_container_dark"/>
- <public name="removed_system_primary_fixed_dark"/>
- <public name="removed_system_primary_fixed_dim_dark"/>
- <public name="removed_system_on_primary_fixed_dark"/>
- <public name="removed_system_on_primary_fixed_variant_dark"/>
- <public name="removed_system_secondary_fixed_dark"/>
- <public name="removed_system_secondary_fixed_dim_dark"/>
- <public name="removed_system_on_secondary_fixed_dark"/>
- <public name="removed_system_on_secondary_fixed_variant_dark"/>
- <public name="removed_system_tertiary_fixed_dark"/>
- <public name="removed_system_tertiary_fixed_dim_dark"/>
- <public name="removed_system_on_tertiary_fixed_dark"/>
- <public name="removed_system_on_tertiary_fixed_variant_dark"/>
- <public name="system_control_activated_dark"/>
- <public name="system_control_normal_dark"/>
- <public name="system_control_highlight_dark"/>
- <public name="system_text_primary_inverse_dark"/>
- <public name="system_text_secondary_and_tertiary_inverse_dark"/>
- <public name="system_text_primary_inverse_disable_only_dark"/>
- <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/>
- <public name="system_text_hint_inverse_dark"/>
- <public name="system_palette_key_color_primary_dark"/>
- <public name="system_palette_key_color_secondary_dark"/>
- <public name="system_palette_key_color_tertiary_dark"/>
- <public name="system_palette_key_color_neutral_dark"/>
- <public name="system_palette_key_color_neutral_variant_dark"/>
- <public name="system_primary_fixed" />
- <public name="system_primary_fixed_dim" />
- <public name="system_on_primary_fixed" />
- <public name="system_on_primary_fixed_variant" />
- <public name="system_secondary_fixed" />
- <public name="system_secondary_fixed_dim" />
- <public name="system_on_secondary_fixed" />
- <public name="system_on_secondary_fixed_variant" />
- <public name="system_tertiary_fixed" />
- <public name="system_tertiary_fixed_dim" />
- <public name="system_on_tertiary_fixed" />
- <public name="system_on_tertiary_fixed_variant" />
- <public name="system_outline_variant_light" />
- <public name="system_outline_variant_dark" />
+ <staging-public-group type="color" first-id="0x01b80000">
</staging-public-group>
- <staging-public-group type="array" first-id="0x01c80000">
+ <staging-public-group type="array" first-id="0x01b70000">
</staging-public-group>
- <staging-public-group type="drawable" first-id="0x01c70000">
+ <staging-public-group type="drawable" first-id="0x01b60000">
</staging-public-group>
- <staging-public-group type="layout" first-id="0x01c60000">
+ <staging-public-group type="layout" first-id="0x01b50000">
</staging-public-group>
- <staging-public-group type="anim" first-id="0x01c50000">
+ <staging-public-group type="anim" first-id="0x01b40000">
</staging-public-group>
- <staging-public-group type="animator" first-id="0x01c40000">
+ <staging-public-group type="animator" first-id="0x01b30000">
</staging-public-group>
- <staging-public-group type="interpolator" first-id="0x01c30000">
+ <staging-public-group type="interpolator" first-id="0x01b20000">
</staging-public-group>
- <staging-public-group type="mipmap" first-id="0x01c20000">
+ <staging-public-group type="mipmap" first-id="0x01b10000">
</staging-public-group>
- <staging-public-group type="integer" first-id="0x01c10000">
+ <staging-public-group type="integer" first-id="0x01b00000">
</staging-public-group>
- <staging-public-group type="transition" first-id="0x01c00000">
+ <staging-public-group type="transition" first-id="0x01af0000">
</staging-public-group>
- <staging-public-group type="raw" first-id="0x01bf0000">
+ <staging-public-group type="raw" first-id="0x01ae0000">
</staging-public-group>
- <staging-public-group type="bool" first-id="0x01be0000">
- <!-- @hide @SystemApi -->
- <public name="config_safetyProtectionEnabled" />
- <!-- @hide @SystemApi -->
- <public name="config_enableDefaultNotes" />
- <!-- @hide @SystemApi -->
- <public name="config_enableDefaultNotesForWorkProfile" />
+ <staging-public-group type="bool" first-id="0x01ad0000">
</staging-public-group>
- <staging-public-group type="fraction" first-id="0x01bd0000">
+ <staging-public-group type="fraction" first-id="0x01ac0000">
</staging-public-group>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3ee8af2842bf..947dc2de9841 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1347,16 +1347,6 @@
<!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] -->
<string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string>
- <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
- <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string>
- <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
- <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string>
-
- <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] -->
- <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string>
- <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] -->
- <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string>
-
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCalendar">Read calendar events and details</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -5369,15 +5359,11 @@
<string name="app_suspended_unsuspend_message">Unpause app</string>
<!-- Title of a dialog. This text is confirming that the user wants to turn on access to their work apps, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=30] -->
- <string name="work_mode_off_title">Turn on work apps?</string>
- <!-- Text in a dialog. This text is confirming that the user wants to turn on access to their work apps and notifications, which the user had previously paused. "Work" is an adjective. [CHAR LIMIT=NONE] -->
- <string name="work_mode_off_message">Get access to your work apps and notifications</string>
- <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
- <string name="work_mode_turn_on">Turn on</string>
+ <string name="work_mode_off_title">Unpause work apps?</string>
+ <!-- Title for button to unpause on work profile. [CHAR LIMIT=NONE] -->
+ <string name="work_mode_turn_on">Unpause</string>
<!-- Title for button to launch the personal safety app to make an emergency call -->
<string name="work_mode_emergency_call_button">Emergency</string>
- <!-- Text shown in a dialog when the user tries to launch a disabled work profile app when work apps are paused-->
- <string name="work_mode_dialer_off_message">Get access to your work apps and calls</string>
<!-- Title of the dialog that is shown when the user tries to launch a blocked application [CHAR LIMIT=50] -->
<string name="app_blocked_title">App is not available</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 7b582da836aa..cea603265930 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1944,7 +1944,7 @@
<java-symbol type="bool" name="config_allowTheaterModeWakeFromLidSwitch" />
<java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" />
<java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" />
- <java-symbol type="bool" name="config_keepDreamingWhenUndocking" />
+ <java-symbol type="bool" name="config_keepDreamingWhenUnplugging" />
<java-symbol type="integer" name="config_keyguardDrawnTimeout" />
<java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" />
<java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" />
@@ -2017,6 +2017,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+ <java-symbol type="array" name="config_useFullScreenIntentPackages" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
@@ -3138,7 +3139,6 @@
<!-- Work profile unlaunchable app alert dialog-->
<java-symbol type="style" name="AlertDialogWithEmergencyButton"/>
- <java-symbol type="string" name="work_mode_dialer_off_message" />
<java-symbol type="string" name="work_mode_emergency_call_button" />
<java-symbol type="string" name="work_mode_off_title" />
<java-symbol type="string" name="work_mode_off_message" />
@@ -4865,6 +4865,8 @@
<java-symbol type="string" name="config_wearSysUiPackage"/>
<java-symbol type="string" name="config_wearSysUiMainActivity"/>
+ <java-symbol type="string" name="config_wearMediaControlsPackage"/>
+ <java-symbol type="string" name="config_wearMediaSessionsPackage"/>
<java-symbol type="string" name="config_defaultQrCodeComponent"/>
<java-symbol type="dimen" name="secondary_rounded_corner_radius" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index e59b2597722f..706845360bf4 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1665,47 +1665,47 @@ easier.
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
<!-- DeviceDefault style for input methods, which is used by the
@@ -1758,47 +1758,47 @@ easier.
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
<style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
@@ -4043,16 +4043,16 @@ easier.
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
<item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
<item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
<item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
<item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
<item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4123,16 +4123,16 @@ easier.
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
<item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
<item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
<item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
<item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
<item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4195,16 +4195,16 @@ easier.
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
<item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
<item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
<item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
<item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
<item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4366,16 +4366,16 @@ easier.
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
<item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
<item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
<item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
<item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
<item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
@@ -4475,47 +4475,47 @@ easier.
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert">
@@ -4570,47 +4570,47 @@ easier.
<item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
<item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
- <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item>
+ <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item>
<item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item>
- <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item>
- <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item>
- <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item>
- <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item>
+ <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item>
+ <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item>
+ <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item>
+ <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item>
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
- <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
+ <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
- <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
- <item name="materialColorErrorContainer">@color/system_error_container_dark</item>
+ <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item>
+ <item name="materialColorErrorContainer">@color/system_error_container_light</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
- <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
- <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item>
+ <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
- <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item>
- <item name="materialColorOnBackground">@color/system_on_background_dark</item>
+ <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item>
+ <item name="materialColorOnBackground">@color/system_on_background_light</item>
<item name="materialColorPrimaryFixed">@color/system_primary_fixed</item>
- <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item>
- <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item>
- <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item>
- <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item>
- <item name="materialColorSecondary">@color/system_secondary_dark</item>
- <item name="materialColorOnError">@color/system_on_error_dark</item>
- <item name="materialColorSurface">@color/system_surface_dark</item>
- <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item>
- <item name="materialColorTertiary">@color/system_tertiary_dark</item>
- <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item>
- <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item>
- <item name="materialColorOutline">@color/system_outline_dark</item>
- <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item>
- <item name="materialColorOnPrimary">@color/system_on_primary_dark</item>
- <item name="materialColorOnSurface">@color/system_on_surface_dark</item>
- <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item>
+ <item name="materialColorOnSecondary">@color/system_on_secondary_light</item>
+ <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item>
+ <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item>
+ <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item>
+ <item name="materialColorSecondary">@color/system_secondary_light</item>
+ <item name="materialColorOnError">@color/system_on_error_light</item>
+ <item name="materialColorSurface">@color/system_surface_light</item>
+ <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item>
+ <item name="materialColorTertiary">@color/system_tertiary_light</item>
+ <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item>
+ <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item>
+ <item name="materialColorOutline">@color/system_outline_light</item>
+ <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item>
+ <item name="materialColorOnPrimary">@color/system_on_primary_light</item>
+ <item name="materialColorOnSurface">@color/system_on_surface_light</item>
+ <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item>
</style>
<style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" />
@@ -4700,16 +4700,16 @@ easier.
<item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item>
<item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item>
<item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item>
- <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item>
+ <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item>
<item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item>
<item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item>
<item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item>
<item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item>
<item name="materialColorErrorContainer">@color/system_error_container_dark</item>
<item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item>
- <item name="materialColorPrimaryInverse">@color/system_primary_dark</item>
+ <item name="materialColorPrimaryInverse">@color/system_primary_light</item>
<item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item>
- <item name="materialColorSurfaceInverse">@color/system_surface_dark</item>
+ <item name="materialColorSurfaceInverse">@color/system_surface_light</item>
<item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item>
<item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item>
<item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item>
diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml
index 4fae8fb77687..8b3667e9f2a9 100644
--- a/core/res/res/xml/irq_device_map.xml
+++ b/core/res/res/xml/irq_device_map.xml
@@ -18,14 +18,16 @@
-->
<irq-device-map>
<!-- This file maps devices (chips) that can send interrupts to the main processor (and bring it
- out of sleep) to logical subsystems in userspace code. Since each Android device has its own
- uniquely designed chipset, this mapping is expected to be empty by default and should be
- overridden by device-specific configs.
+ out of sleep) to logical subsystems in userspace code. Since each Android device can have
+ a differently designed chipset, this mapping is expected to be empty by default and should
+ be overridden by device-specific configs.
This mapping helps the system to meaningfully attribute CPU wakeups to logical work that
- happened on the device. The devices are referred to by their names as defined in the kernel.
- Currently, defined subsystems are:
- - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API.
- - Wifi: Use this to denote network traffic that uses the wifi transport.
+ happened on the device and the app activity that caused it. The devices are referred to by
+ their names as defined in the kernel. Currently, defined subsystems are:
+ - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API.
+ - Wifi: Use this to denote network traffic that uses the wifi transport.
+ - Sound_trigger: Use this to denote sound phrase detection, like the ones supported by
+ SoundTriggerManager.
The overlay should use tags <device> and <subsystem> to describe this mapping in the
following way:
diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS
index b3f399363aef..8d9461d8035d 100644
--- a/core/tests/coretests/src/android/app/OWNERS
+++ b/core/tests/coretests/src/android/app/OWNERS
@@ -4,3 +4,6 @@ per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS
per-file *Notification* = file:/packages/SystemUI/OWNERS
per-file *Zen* = file:/packages/SystemUI/OWNERS
per-file *StatusBar* = file:/packages/SystemUI/OWNERS
+
+# A11Y and related
+per-file *UiAutomation* = file:/services/accessibility/OWNERS
diff --git a/core/tests/coretests/src/android/app/UiAutomationTest.java b/core/tests/coretests/src/android/app/UiAutomationTest.java
new file mode 100644
index 000000000000..3ac5057bcf24
--- /dev/null
+++ b/core/tests/coretests/src/android/app/UiAutomationTest.java
@@ -0,0 +1,144 @@
+/*
+ * 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.app;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.UserManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class UiAutomationTest {
+
+ private static final int SECONDARY_DISPLAY_ID = 42;
+ private static final int DISPLAY_ID_ASSIGNED_TO_USER = 108;
+
+ private final Looper mLooper = Looper.getMainLooper();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private IUiAutomationConnection mConnection;
+
+ @Before
+ public void setFixtures() {
+ when(mContext.getMainLooper()).thenReturn(mLooper);
+ mockSystemService(UserManager.class, mUserManager);
+
+ // Set default expectations
+ mockVisibleBackgroundUsersSupported(/* supported= */ false);
+ mockUserVisibility(/* visible= */ true);
+ // make sure it's not used, unless explicitly mocked
+ mockDisplayAssignedToUser(INVALID_DISPLAY);
+ mockContextDisplay(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testContextConstructor_nullContext() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new UiAutomation((Context) null, mConnection));
+ }
+
+ @Test
+ public void testContextConstructor_nullConnection() {
+ assertThrows(IllegalArgumentException.class,
+ () -> new UiAutomation(mContext, (IUiAutomationConnection) null));
+ }
+
+ @Test
+ public void testGetDisplay_contextWithSecondaryDisplayId() {
+ mockContextDisplay(SECONDARY_DISPLAY_ID);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ // It's always DEFAULT_DISPLAY regardless, unless the device supports visible bg users
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testGetDisplay_contextWithInvalidDisplayId() {
+ mockContextDisplay(INVALID_DISPLAY);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
+ public void testGetDisplay_visibleBgUsers() {
+ mockVisibleBackgroundUsersSupported(/* supported= */ true);
+ mockContextDisplay(SECONDARY_DISPLAY_ID);
+ // Should be using display from context, not from user
+ mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(SECONDARY_DISPLAY_ID);
+ }
+
+ @Test
+ public void testGetDisplay_visibleBgUsers_contextWithInvalidDisplayId() {
+ mockVisibleBackgroundUsersSupported(/* supported= */ true);
+ mockContextDisplay(INVALID_DISPLAY);
+ mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER);
+
+ UiAutomation uiAutomation = new UiAutomation(mContext, mConnection);
+
+ assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId())
+ .isEqualTo(DISPLAY_ID_ASSIGNED_TO_USER);
+ }
+
+ private <T> void mockSystemService(Class<T> svcClass, T svc) {
+ String svcName = svcClass.getName();
+ when(mContext.getSystemServiceName(svcClass)).thenReturn(svcName);
+ when(mContext.getSystemService(svcName)).thenReturn(svc);
+ }
+
+ private void mockVisibleBackgroundUsersSupported(boolean supported) {
+ when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ private void mockContextDisplay(int displayId) {
+ when(mContext.getDisplayId()).thenReturn(displayId);
+ }
+
+ private void mockDisplayAssignedToUser(int displayId) {
+ when(mUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId);
+ }
+
+ private void mockUserVisibility(boolean visible) {
+ when(mUserManager.isUserVisible()).thenReturn(visible);
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
index 980211fe4cc8..c6bb07b17fd4 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.app.Activity;
import android.compat.testing.PlatformCompatChangeRule;
import android.os.Bundle;
+import android.platform.test.annotations.IwTest;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.util.PollingCheck;
@@ -84,6 +85,7 @@ public class FontScaleConverterActivityTest {
}
}
+ @IwTest(focusArea = "accessibility")
@Test
public void testFontsScaleNonLinearly() {
final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -114,6 +116,7 @@ public class FontScaleConverterActivityTest {
)));
}
+ @IwTest(focusArea = "accessibility")
@Test
public void testOnConfigurationChanged_doesNotCrash() {
final ActivityScenario<TestActivity> scenario = rule.getScenario();
@@ -127,6 +130,7 @@ public class FontScaleConverterActivityTest {
});
}
+ @IwTest(focusArea = "accessibility")
@Test
public void testUpdateConfiguration_doesNotCrash() {
final ActivityScenario<TestActivity> scenario = rule.getScenario();
diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING
index 4ea6e40a7225..ab14950891c3 100644
--- a/core/tests/coretests/src/android/content/res/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING
@@ -39,5 +39,18 @@
}
]
}
+ ],
+ "ironwood-postsubmit": [
+ {
+ "name": "FrameworksCoreTests",
+ "options":[
+ {
+ "include-annotation": "android.platform.test.annotations.IwTest"
+ },
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ }
+ ]
+ }
]
}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
new file mode 100644
index 000000000000..66f3bca72aeb
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.hardware.biometrics;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoRule;
+
+import java.util.concurrent.Executor;
+
+
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class BiometricPromptTest {
+ @Rule
+ public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private IAuthService mService;
+ private BiometricPrompt mBiometricPrompt;
+
+ private CancellationSignal mCancellationSignal;
+
+ private final TestLooper mLooper = new TestLooper();
+ private final Handler mHandler = new Handler(mLooper.getLooper());
+ private final Executor mExecutor = mHandler::post;
+
+ @Before
+ public void setUp() throws RemoteException {
+ mBiometricPrompt = new BiometricPrompt.Builder(mContext)
+ .setUseDefaultSubtitle()
+ .setUseDefaultTitle()
+ .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG
+ | BiometricManager.Authenticators.DEVICE_CREDENTIAL)
+ .setService(mService)
+ .build();
+
+ mCancellationSignal = new CancellationSignal();
+ when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any()))
+ .thenReturn(0L);
+ when(mContext.getPackageName()).thenReturn("BiometricPromptTest");
+ }
+
+ @Test
+ public void testCancellationAfterAuthenticationFailed() throws RemoteException {
+ ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor =
+ ArgumentCaptor.forClass(IBiometricServiceReceiver.class);
+ BiometricPrompt.AuthenticationCallback callback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }};
+ mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback);
+ mLooper.dispatchAll();
+
+ verify(mService).authenticate(any(), anyLong(), anyInt(),
+ biometricServiceReceiverCaptor.capture(), anyString(), any());
+
+ biometricServiceReceiverCaptor.getValue().onAuthenticationFailed();
+ mLooper.dispatchAll();
+ mCancellationSignal.cancel();
+
+ verify(mService).cancelAuthentication(any(), anyString(), anyLong());
+ }
+}
diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
new file mode 100644
index 000000000000..6a2192a2c7fb
--- /dev/null
+++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/biometrics/OWNERS
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 3b6e8eaafc22..cde100cc20aa 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -70,7 +70,13 @@ public class WindowOnBackInvokedDispatcherTest {
private ApplicationInfo mApplicationInfo;
private final BackMotionEvent mBackEvent = new BackMotionEvent(
- 0, 0, 0, BackEvent.EDGE_LEFT, null);
+ /* touchX = */ 0,
+ /* touchY = */ 0,
+ /* progress = */ 0,
+ /* velocityX = */ 0,
+ /* velocityY = */ 0,
+ /* swipeEdge = */ BackEvent.EDGE_LEFT,
+ /* departingAnimationTarget = */ null);
@Before
public void setUp() throws Exception {
diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
index 4716312c59a8..36c2a62ae6ed 100644
--- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java
@@ -23,16 +23,25 @@ import static com.android.internal.util.DumpUtils.isPlatformCriticalPackage;
import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage;
import static com.android.internal.util.DumpUtils.isPlatformPackage;
+import static com.google.common.truth.Truth.assertWithMessage;
+
import android.content.ComponentName;
+import android.util.SparseArray;
import junit.framework.TestCase;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
/**
* Run with:
atest FrameworksCoreTests:DumpUtilsTest
*/
public class DumpUtilsTest extends TestCase {
+ private final StringWriter mStringWriter = new StringWriter();
+ private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter);
+
private static ComponentName cn(String componentName) {
if (componentName == null) {
return null;
@@ -168,4 +177,144 @@ public class DumpUtilsTest extends TestCase {
Integer.toHexString(System.identityHashCode(component))).test(
wcn("com.google/.abc")));
}
+
+ public void testDumpSparseArray_empty() {
+ SparseArray<String> array = new SparseArray<>();
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n");
+ }
+
+ public void testDumpSparseArray_oneElement() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".1 number(s):\n"
+ + "..0: 1->uno\n");
+ }
+
+ public void testDumpSparseArray_oneNullElement() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".1 NULL(s):\n"
+ + "..0: 1->(null)\n");
+ }
+
+ public void testDumpSparseArray_multipleElements() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "..0: 1->uno\n"
+ + "..1: 2->duo\n"
+ + "..2: 42->(null)\n");
+ }
+
+ public void testDumpSparseArray_keyDumperOnly() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ (i, k) -> {
+ mPrintWriter.printf("_%d=%d_", i, k);
+ }, /* valueDumper= */ null);
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "_0=1_uno\n"
+ + "_1=2_duo\n"
+ + "_2=42_(null)\n");
+ }
+
+ public void testDumpSparseArray_valueDumperOnly() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ /* keyDumper= */ null,
+ s -> {
+ mPrintWriter.print(s.toUpperCase());
+ });
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "..0: 1->UNO\n"
+ + "..1: 2->DUO\n"
+ + "..2: 42->(null)\n");
+ }
+
+ public void testDumpSparseArray_keyAndValueDumpers() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number",
+ (i, k) -> {
+ mPrintWriter.printf("_%d=%d_", i, k);
+ },
+ s -> {
+ mPrintWriter.print(s.toUpperCase());
+ });
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 number(s):\n"
+ + "_0=1_UNO\n"
+ + "_1=2_DUO\n"
+ + "_2=42_(null)\n");
+ }
+
+ public void testDumpSparseArrayValues() {
+ SparseArray<String> array = new SparseArray<>();
+ array.put(1, "uno");
+ array.put(2, "duo");
+ array.put(42, null);
+
+ DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number");
+
+ String output = flushPrintWriter();
+
+ assertWithMessage("dump of %s", array).that(output).isEqualTo(""
+ + ".3 numbers:\n"
+ + "..uno\n"
+ + "..duo\n"
+ + "..(null)\n");
+ }
+
+ private String flushPrintWriter() {
+ mPrintWriter.flush();
+
+ return mStringWriter.toString();
+ }
}
diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS
deleted file mode 100644
index 3dc958b07f9c..000000000000
--- a/core/tests/expresslog/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 719316
-# Stats/expresslog
-file:/services/core/java/com/android/server/stats/OWNERS
diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING
deleted file mode 100644
index c9b0cf80a1e6..000000000000
--- a/core/tests/expresslog/TEST_MAPPING
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "presubmit": [
- {
- "name": "ExpressLogTests",
- "options": [
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
- }
- ]
-} \ No newline at end of file
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
deleted file mode 100644
index ee62d7528818..000000000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java
+++ /dev/null
@@ -1,167 +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.internal.expresslog;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class ScaledRangeOptionsTest {
- private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName();
-
- @Test
- public void testGetBinsCount() {
- Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2);
- assertEquals(3, options1.getBinsCount());
-
- Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2);
- assertEquals(12, options10.getBinsCount());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructZeroBinsCount() {
- new Histogram.ScaledRangeOptions(0, 100, 100, 2);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructNegativeBinsCount() {
- new Histogram.ScaledRangeOptions(-1, 100, 100, 2);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructNegativeFirstBinWidth() {
- new Histogram.ScaledRangeOptions(10, 100, -100, 2);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructTooSmallFirstBinWidth() {
- new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructNegativeScaleFactor() {
- new Histogram.ScaledRangeOptions(10, 100, 100, -2);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructTooSmallScaleFactor() {
- new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructTooBigScaleFactor() {
- new Histogram.ScaledRangeOptions(10, 100, 100, 500.f);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructTooBigBinRange() {
- new Histogram.ScaledRangeOptions(100, 100, 100, 10.f);
- }
-
- @Test
- public void testBinIndexForRangeEqual1() {
- Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1);
- assertEquals(12, options.getBinsCount());
-
- assertEquals(11, options.getBinForSample(11));
-
- for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
- assertEquals(i, options.getBinForSample(i));
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual2() {
- // this should produce bin otpions similar to linear histogram with bin width 2
- Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1);
- assertEquals(12, options.getBinsCount());
-
- for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
- assertEquals(i, options.getBinForSample(i * 2));
- assertEquals(i, options.getBinForSample(i * 2 - 1));
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual5() {
- Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1);
- assertEquals(4, options.getBinsCount());
- for (int i = 0; i < 2; i++) {
- for (int sample = 0; sample < 5; sample++) {
- assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
- }
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual10() {
- Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1);
- assertEquals(0, options.getBinForSample(0));
- assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
- assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
- final float binSize = (101 - 1) / 10f;
- for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
- assertEquals(i, options.getBinForSample(i * binSize));
- }
- }
-
- @Test
- public void testBinIndexForScaleFactor2() {
- final int binsCount = 10;
- final int minValue = 10;
- final int firstBinWidth = 5;
- final int scaledFactor = 2;
-
- Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(
- binsCount, minValue, firstBinWidth, scaledFactor);
- assertEquals(binsCount + 2, options.getBinsCount());
- long[] binCounts = new long[10];
-
- // precalculate max valid value - start value for the overflow bin
- int lastBinStartValue = minValue; //firstBinMin value
- int lastBinWidth = firstBinWidth;
- for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) {
- lastBinStartValue = lastBinStartValue + lastBinWidth;
- lastBinWidth *= scaledFactor;
- }
-
- // underflow bin
- for (int i = 1; i < minValue; i++) {
- assertEquals(0, options.getBinForSample(i));
- }
-
- for (int i = 10; i < lastBinStartValue; i++) {
- assertTrue(options.getBinForSample(i) > 0);
- assertTrue(options.getBinForSample(i) <= binsCount);
- binCounts[options.getBinForSample(i) - 1]++;
- }
-
- // overflow bin
- assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue));
-
- for (int i = 1; i < binsCount; i++) {
- assertEquals(binCounts[i], binCounts[i - 1] * 2L);
- }
- }
-}
diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
deleted file mode 100644
index 037dbb32c2f8..000000000000
--- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java
+++ /dev/null
@@ -1,125 +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.internal.expresslog;
-
-import androidx.test.filters.SmallTest;
-
-import static org.junit.Assert.assertEquals;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-@SmallTest
-public class UniformOptionsTest {
- private static final String TAG = UniformOptionsTest.class.getSimpleName();
-
- @Test
- public void testGetBinsCount() {
- Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000);
- assertEquals(3, options1.getBinsCount());
-
- Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000);
- assertEquals(12, options10.getBinsCount());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructZeroBinsCount() {
- new Histogram.UniformOptions(0, 100, 1000);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructNegativeBinsCount() {
- new Histogram.UniformOptions(-1, 100, 1000);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testConstructMaxValueLessThanMinValue() {
- new Histogram.UniformOptions(10, 1000, 100);
- }
-
- @Test
- public void testBinIndexForRangeEqual1() {
- Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11);
- for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
- assertEquals(i, options.getBinForSample(i));
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual2() {
- Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21);
- for (int i = 0, bins = options.getBinsCount(); i < bins; i++) {
- assertEquals(i, options.getBinForSample(i * 2));
- assertEquals(i, options.getBinForSample(i * 2 - 1));
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual5() {
- Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10);
- assertEquals(4, options.getBinsCount());
- for (int i = 0; i < 2; i++) {
- for (int sample = 0; sample < 5; sample++) {
- assertEquals(i + 1, options.getBinForSample(i * 5 + sample));
- }
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual10() {
- Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101);
- assertEquals(0, options.getBinForSample(0));
- assertEquals(options.getBinsCount() - 2, options.getBinForSample(100));
- assertEquals(options.getBinsCount() - 1, options.getBinForSample(101));
-
- final float binSize = (101 - 1) / 10f;
- for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) {
- assertEquals(i, options.getBinForSample(i * binSize));
- }
- }
-
- @Test
- public void testBinIndexForRangeEqual90() {
- final int binCount = 10;
- final int minValue = 100;
- final int maxValue = 100000;
-
- Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue,
- maxValue);
-
- // logging underflow sample
- assertEquals(0, options.getBinForSample(minValue - 1));
-
- // logging overflow sample
- assertEquals(binCount + 1, options.getBinForSample(maxValue));
- assertEquals(binCount + 1, options.getBinForSample(maxValue + 1));
-
- // logging min edge sample
- assertEquals(1, options.getBinForSample(minValue));
-
- // logging max edge sample
- assertEquals(binCount, options.getBinForSample(maxValue - 1));
-
- // logging single valid sample per bin
- final int binSize = (maxValue - minValue) / binCount;
-
- for (int i = 0; i < binCount; i++) {
- assertEquals(i + 1, options.getBinForSample(minValue + binSize * i));
- }
- }
-}
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index 99594336999b..cea535e8f480 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -56,6 +56,10 @@
<install-in-user-type package="com.android.internal.systemui.navbar.transparent">
<install-in user-type="FULL" />
</install-in-user-type>
+ <install-in-user-type package="com.android.role.notes.enabled">
+ <install-in user-type="FULL" />
+ <install-in user-type="PROFILE" />
+ </install-in-user-type>
<install-in-user-type package="com.android.theme.color.amethyst">
<install-in user-type="FULL" />
<install-in user-type="PROFILE" />
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 2afd54b984a4..7434cb02cc7c 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -175,12 +175,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1941440781": {
- "message": "Creating Pending Move-to-back: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Task.java"
- },
"-1939861963": {
"message": "Create root task displayId=%d winMode=%d",
"level": "VERBOSE",
@@ -475,6 +469,18 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/RecentTasks.java"
},
+ "-1643780158": {
+ "message": "Saving original orientation before camera compat, last orientation is %d",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
+ "-1639406696": {
+ "message": "NOSENSOR override detected",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+ },
"-1638958146": {
"message": "Removing activity %s from task=%s adding to task=%s Callers=%s",
"level": "INFO",
@@ -619,12 +625,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
- "-1484988952": {
- "message": "Creating Pending Multiwindow Fullscreen Request: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityClientController.java"
- },
"-1483435730": {
"message": "InsetsSource setWin %s for type %s",
"level": "DEBUG",
@@ -751,6 +751,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/ImeInsetsSourceProvider.java"
},
+ "-1397175017": {
+ "message": "Other orientation overrides are in place: not reverting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+ },
"-1394745488": {
"message": "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
"level": "INFO",
@@ -883,6 +889,12 @@
"group": "WM_SHOW_TRANSACTIONS",
"at": "com\/android\/server\/wm\/WindowSurfaceController.java"
},
+ "-1258739769": {
+ "message": "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_BACK_PREVIEW",
+ "at": "com\/android\/server\/wm\/BackNavigationController.java"
+ },
"-1257821162": {
"message": "OUT SURFACE %s: copied",
"level": "INFO",
@@ -1297,6 +1309,12 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-874087484": {
+ "message": "SyncGroup %d: Set ready %b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
+ },
"-869242375": {
"message": "Content Recording: Unable to start recording due to invalid region for display %d",
"level": "VERBOSE",
@@ -1699,6 +1717,12 @@
"group": "WM_DEBUG_WINDOW_TRANSITIONS",
"at": "com\/android\/server\/wm\/Transition.java"
},
+ "-529187878": {
+ "message": "Reverting orientation after camera compat force rotation",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+ },
"-521613870": {
"message": "App died during pause, not stopping: %s",
"level": "VERBOSE",
@@ -2371,6 +2395,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "138097009": {
+ "message": "NOSENSOR override is absent: reverting",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/DisplayRotationReversionController.java"
+ },
"140319294": {
"message": "IME target changed within ActivityRecord",
"level": "DEBUG",
@@ -2563,12 +2593,6 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowState.java"
},
- "286170861": {
- "message": "Creating Pending Transition for TaskFragment: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
- },
"288485303": {
"message": "Attempted to set remove mode to a display that does not exist: %d",
"level": "WARN",
@@ -3151,12 +3175,6 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/AppTransitionController.java"
},
- "800698875": {
- "message": "SyncGroup %d: Started when there is other active SyncGroup",
- "level": "WARN",
- "group": "WM_DEBUG_SYNC_ENGINE",
- "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
- },
"801521566": {
"message": "Content Recording: Attempting to mirror %d from %d but no DisplayContent associated. Changing to mirror default display.",
"level": "WARN",
@@ -3217,12 +3235,6 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
- "898260097": {
- "message": "Creating Pending Pip-Enter: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
- },
"898863925": {
"message": "Attempted to add QS dialog window with unknown token %s. Aborting.",
"level": "WARN",
@@ -3967,12 +3979,6 @@
"group": "WM_DEBUG_CONTENT_RECORDING",
"at": "com\/android\/server\/wm\/ContentRecorder.java"
},
- "1667162379": {
- "message": "Creating Pending Transition: %s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
- },
"1670933628": {
"message": " Setting allReady override",
"level": "VERBOSE",
@@ -4003,12 +4009,6 @@
"group": "WM_DEBUG_CONFIGURATION",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1689989893": {
- "message": "SyncGroup %d: Set ready",
- "level": "VERBOSE",
- "group": "WM_DEBUG_SYNC_ENGINE",
- "at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
- },
"1699269281": {
"message": "Don't organize or trigger events for untrusted displayId=%d",
"level": "WARN",
@@ -4039,6 +4039,12 @@
"group": "WM_DEBUG_SYNC_ENGINE",
"at": "com\/android\/server\/wm\/BLASTSyncEngine.java"
},
+ "1735199721": {
+ "message": "Queueing transition: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN",
+ "at": "com\/android\/server\/wm\/TransitionController.java"
+ },
"1739298851": {
"message": "removeWindowToken: Attempted to remove token: %s for non-exiting displayId=%d",
"level": "WARN",
diff --git a/data/keyboards/GoogleTV-Remote.idc b/data/keyboards/GoogleTV-Remote.idc
new file mode 100644
index 000000000000..14fb4e29c33c
--- /dev/null
+++ b/data/keyboards/GoogleTV-Remote.idc
@@ -0,0 +1,25 @@
+# Copyright 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.
+
+#
+# Input Device Configuration file for Google Reference Remote Control Unit (RCU).
+#
+#
+
+# Basic Parameters
+# Depending on the FLASH configurations, RCUs may have PID 0006 instead
+# of 0001.
+keyboard.layout = Vendor_0957_Product_0001
+keyboard.doNotWakeByDefault = 1
+audio.mic = 1
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index 8dd23b70ae61..2307d6080f9f 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -401,8 +401,9 @@ public final class Bitmap implements Parcelable {
/**
* This is called by methods that want to throw an exception if the bitmap
* has already been recycled.
+ * @hide
*/
- private void checkRecycled(String errorMessage) {
+ void checkRecycled(String errorMessage) {
if (mRecycled) {
throw new IllegalStateException(errorMessage);
}
@@ -1921,6 +1922,7 @@ public final class Bitmap implements Parcelable {
*/
public void setGainmap(@Nullable Gainmap gainmap) {
checkRecycled("Bitmap is recycled");
+ mGainmap = null;
nativeSetGainmap(mNativePtr, gainmap == null ? 0 : gainmap.mNativePtr);
}
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 701e20c499da..1da8e189d768 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -482,7 +482,9 @@ public class BitmapFactory {
if (opts == null || opts.inBitmap == null) {
return 0;
}
-
+ // Clear out the gainmap since we don't attempt to reuse it and don't want to
+ // accidentally keep it on the re-used bitmap
+ opts.inBitmap.setGainmap(null);
return opts.inBitmap.getNativeInstance();
}
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 2f6dd468511b..5c065775eea2 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -120,6 +120,7 @@ public class BitmapShader extends Shader {
if (bitmap == null) {
throw new IllegalArgumentException("Bitmap must be non-null");
}
+ bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap");
mBitmap = bitmap;
mTileX = tileX;
mTileY = tileY;
@@ -188,6 +189,8 @@ public class BitmapShader extends Shader {
/** @hide */
@Override
protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) {
+ mBitmap.checkRecycled("BitmapShader's bitmap has been recycled");
+
boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR;
if (mFilterMode == FILTER_MODE_DEFAULT) {
mFilterFromPaint = filterFromPaint;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 0b29973507d2..56c3068fe5e9 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -2083,32 +2083,29 @@ public final class ImageDecoder implements AutoCloseable {
}
sIsP010SupportedForAV1Initialized = true;
-
- if (hasHardwareDecoder("video/av01")) {
- sIsP010SupportedForAV1 = true;
- return true;
- }
-
- sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT
- >= Build.VERSION_CODES.S;
- return sIsP010SupportedForAV1;
+ return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01");
}
}
/**
- * Checks if the device has hardware decoder for the target mime type.
- */
- private static boolean hasHardwareDecoder(String mime) {
- final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- for (MediaCodecInfo info : sMCL.getCodecInfos()) {
- if (info.isEncoder() == false && info.isHardwareAccelerated()) {
- try {
- if (info.getCapabilitiesForType(mime) != null) {
- return true;
- }
- } catch (IllegalArgumentException e) {
- // mime is not supported
- return false;
+ * Checks if the device supports decoding 10-bit for the given mime type.
+ */
+ private static boolean isP010SupportedforMime(String mime) {
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+ for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) {
+ if (mediaCodecInfo.isEncoder()) {
+ continue;
+ }
+ for (String mediaType : mediaCodecInfo.getSupportedTypes()) {
+ if (mediaType.equalsIgnoreCase(mime)) {
+ MediaCodecInfo.CodecCapabilities codecCapabilities =
+ mediaCodecInfo.getCapabilitiesForType(mediaType);
+ for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) {
+ if (codecCapabilities.colorFormats[i]
+ == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) {
+ return true;
+ }
+ }
}
}
}
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index d785c3c895b8..f26b50ed4e2a 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -21,10 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageManager;
-import android.os.RemoteException;
import android.os.ServiceManager;
-import android.security.GenerateRkpKey;
-import android.security.keymaster.KeymasterDefs;
class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
@@ -125,18 +122,7 @@ class CredstoreIdentityCredentialStore extends IdentityCredentialStore {
@NonNull String docType) throws AlreadyPersonalizedException,
DocTypeNotSupportedException {
try {
- IWritableCredential wc;
- wc = mStore.createCredential(credentialName, docType);
- try {
- GenerateRkpKey keyGen = new GenerateRkpKey(mContext);
- // We don't know what the security level is for the backing keymint, so go ahead and
- // poke the provisioner for both TEE and SB.
- keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT);
- keyGen.notifyKeyGenerated(KeymasterDefs.KM_SECURITY_LEVEL_STRONGBOX);
- } catch (RemoteException e) {
- // Not really an error state. Does not apply at all if RKP is unsupported or
- // disabled on a given device.
- }
+ IWritableCredential wc = mStore.createCredential(credentialName, docType);
return new CredstoreWritableIdentityCredential(mContext, credentialName, docType, wc);
} catch (android.os.RemoteException e) {
throw new RuntimeException("Unexpected RemoteException ", e);
diff --git a/keystore/java/android/security/GenerateRkpKey.java b/keystore/java/android/security/GenerateRkpKey.java
deleted file mode 100644
index 698133287f63..000000000000
--- a/keystore/java/android/security/GenerateRkpKey.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-import android.annotation.CheckResult;
-import android.annotation.IntDef;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * GenerateKey is a helper class to handle interactions between Keystore and the RemoteProvisioner
- * app. There are two cases where Keystore should use this class.
- *
- * (1) : An app generates a new attested key pair, so Keystore calls notifyKeyGenerated to let the
- * RemoteProvisioner app check if the state of the attestation key pool is getting low enough
- * to warrant provisioning more attestation certificates early.
- *
- * (2) : An app attempts to generate a new key pair, but the keystore service discovers it is out of
- * attestation key pairs and cannot provide one for the given application. Keystore can then
- * make a blocking call on notifyEmpty to allow the RemoteProvisioner app to get another
- * attestation certificate chain provisioned.
- *
- * In most cases, the proper usage of (1) should preclude the need for (2).
- *
- * @hide
- */
-public class GenerateRkpKey {
- private static final String TAG = "GenerateRkpKey";
-
- private static final int NOTIFY_EMPTY = 0;
- private static final int NOTIFY_KEY_GENERATED = 1;
- private static final int TIMEOUT_MS = 1000;
-
- private IGenerateRkpKeyService mBinder;
- private Context mContext;
- private CountDownLatch mCountDownLatch;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {
- IGenerateRkpKeyService.Status.OK,
- IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY,
- IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR,
- IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED,
- IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR,
- IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR,
- IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR,
- IGenerateRkpKeyService.Status.INTERNAL_ERROR,
- })
- public @interface Status {
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName className, IBinder service) {
- mBinder = IGenerateRkpKeyService.Stub.asInterface(service);
- mCountDownLatch.countDown();
- }
-
- @Override public void onBindingDied(ComponentName className) {
- mCountDownLatch.countDown();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName className) {
- mBinder = null;
- }
- };
-
- /**
- * Constructor which takes a Context object.
- */
- public GenerateRkpKey(Context context) {
- mContext = context;
- }
-
- @Status
- private int bindAndSendCommand(int command, int securityLevel) throws RemoteException {
- Intent intent = new Intent(IGenerateRkpKeyService.class.getName());
- ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
- int returnCode = IGenerateRkpKeyService.Status.OK;
- if (comp == null) {
- // On a system that does not use RKP, the RemoteProvisioner app won't be installed.
- return returnCode;
- }
- intent.setComponent(comp);
- mCountDownLatch = new CountDownLatch(1);
- Executor executor = Executors.newCachedThreadPool();
- if (!mContext.bindService(intent, Context.BIND_AUTO_CREATE, executor, mConnection)) {
- throw new RemoteException("Failed to bind to GenerateRkpKeyService");
- }
- try {
- mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- Log.e(TAG, "Interrupted: ", e);
- }
- if (mBinder != null) {
- switch (command) {
- case NOTIFY_EMPTY:
- returnCode = mBinder.generateKey(securityLevel);
- break;
- case NOTIFY_KEY_GENERATED:
- mBinder.notifyKeyGenerated(securityLevel);
- break;
- default:
- Log.e(TAG, "Invalid case for command");
- }
- } else {
- Log.e(TAG, "Binder object is null; failed to bind to GenerateRkpKeyService.");
- returnCode = IGenerateRkpKeyService.Status.INTERNAL_ERROR;
- }
- mContext.unbindService(mConnection);
- return returnCode;
- }
-
- /**
- * Fulfills the use case of (2) described in the class documentation. Blocks until the
- * RemoteProvisioner application can get new attestation keys signed by the server.
- * @return the status of the key generation
- */
- @CheckResult
- @Status
- public int notifyEmpty(int securityLevel) throws RemoteException {
- return bindAndSendCommand(NOTIFY_EMPTY, securityLevel);
- }
-
- /**
- * Fulfills the use case of (1) described in the class documentation. Non blocking call.
- */
- public void notifyKeyGenerated(int securityLevel) throws RemoteException {
- bindAndSendCommand(NOTIFY_KEY_GENERATED, securityLevel);
- }
-}
diff --git a/keystore/java/android/security/IGenerateRkpKeyService.aidl b/keystore/java/android/security/IGenerateRkpKeyService.aidl
deleted file mode 100644
index eeaeb27a7c77..000000000000
--- a/keystore/java/android/security/IGenerateRkpKeyService.aidl
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security;
-
-/**
- * Interface to allow the framework to notify the RemoteProvisioner app when keys are empty. This
- * will be used if Keystore replies with an error code NO_KEYS_AVAILABLE in response to an
- * attestation request. The framework can then synchronously call generateKey() to get more
- * attestation keys generated and signed. Upon return, the caller can be certain an attestation key
- * is available.
- *
- * @hide
- */
-interface IGenerateRkpKeyService {
- @JavaDerive(toString=true)
- @Backing(type="int")
- enum Status {
- /** No error(s) occurred */
- OK = 0,
- /** Unable to provision keys due to a lack of internet connectivity. */
- NO_NETWORK_CONNECTIVITY = 1,
- /** An error occurred while communicating with the RKP server. */
- NETWORK_COMMUNICATION_ERROR = 2,
- /** The given device was not registered with the RKP backend. */
- DEVICE_NOT_REGISTERED = 4,
- /** The RKP server returned an HTTP client error, indicating a misbehaving client. */
- HTTP_CLIENT_ERROR = 5,
- /** The RKP server returned an HTTP server error, indicating something went wrong on the server. */
- HTTP_SERVER_ERROR = 6,
- /** The RKP server returned an HTTP status that is unknown. This should never happen. */
- HTTP_UNKNOWN_ERROR = 7,
- /** An unexpected internal error occurred. This should never happen. */
- INTERNAL_ERROR = 8,
- }
-
- /**
- * Ping the provisioner service to let it know an app generated a key. This may or may not have
- * consumed a remotely provisioned attestation key, so the RemoteProvisioner app should check.
- */
- oneway void notifyKeyGenerated(in int securityLevel);
-
- /**
- * Ping the provisioner service to indicate there are no remaining attestation keys left.
- */
- Status generateKey(in int securityLevel);
-}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index c3b0f9bc16d3..474b7ea56be9 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,7 +20,6 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityThread;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
@@ -28,9 +27,6 @@ import android.hardware.security.keymint.KeyPurpose;
import android.hardware.security.keymint.SecurityLevel;
import android.hardware.security.keymint.Tag;
import android.os.Build;
-import android.os.RemoteException;
-import android.security.GenerateRkpKey;
-import android.security.IGenerateRkpKeyService;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore2;
import android.security.KeyStoreException;
@@ -621,45 +617,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
@Override
public KeyPair generateKeyPair() {
- GenerateKeyPairHelperResult result = new GenerateKeyPairHelperResult(0, null);
- for (int i = 0; i < 2; i++) {
- /**
- * NOTE: There is no need to delay between re-tries because the call to
- * GenerateRkpKey.notifyEmpty() will delay for a while before returning.
- */
- result = generateKeyPairHelper();
- if (result.rkpStatus == KeyStoreException.RKP_SUCCESS && result.keyPair != null) {
- return result.keyPair;
- }
- }
-
- // RKP failure
- if (result.rkpStatus != KeyStoreException.RKP_SUCCESS) {
- KeyStoreException ksException = new KeyStoreException(ResponseCode.OUT_OF_KEYS,
- "Could not get RKP keys", result.rkpStatus);
- throw new ProviderException("Failed to provision new attestation keys.", ksException);
- }
-
- return result.keyPair;
- }
-
- private static class GenerateKeyPairHelperResult {
- // Zero indicates success, non-zero indicates failure. Values should be
- // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
- // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
- // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
- // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
- public final int rkpStatus;
- @Nullable
- public final KeyPair keyPair;
-
- private GenerateKeyPairHelperResult(int rkpStatus, KeyPair keyPair) {
- this.rkpStatus = rkpStatus;
- this.keyPair = keyPair;
- }
- }
-
- private GenerateKeyPairHelperResult generateKeyPairHelper() {
if (mKeyStore == null || mSpec == null) {
throw new IllegalStateException("Not initialized");
}
@@ -697,26 +654,12 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
AndroidKeyStorePublicKey publicKey =
AndroidKeyStoreProvider.makeAndroidKeyStorePublicKeyFromKeyEntryResponse(
descriptor, metadata, iSecurityLevel, mKeymasterAlgorithm);
- GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
- .currentApplication());
- try {
- if (mSpec.getAttestationChallenge() != null) {
- keyGen.notifyKeyGenerated(securityLevel);
- }
- } catch (RemoteException e) {
- // This is not really an error state, and necessarily does not apply to non RKP
- // systems or hybrid systems where RKP is not currently turned on.
- Log.d(TAG, "Couldn't connect to the RemoteProvisioner backend.", e);
- }
success = true;
- KeyPair kp = new KeyPair(publicKey, publicKey.getPrivateKey());
- return new GenerateKeyPairHelperResult(0, kp);
+ return new KeyPair(publicKey, publicKey.getPrivateKey());
} catch (KeyStoreException e) {
switch (e.getErrorCode()) {
case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:
throw new StrongBoxUnavailableException("Failed to generated key pair.", e);
- case ResponseCode.OUT_OF_KEYS:
- return checkIfRetryableOrThrow(e, securityLevel);
default:
ProviderException p = new ProviderException("Failed to generate key pair.", e);
if ((mSpec.getPurposes() & KeyProperties.PURPOSE_WRAP_KEY) != 0) {
@@ -742,55 +685,6 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
- // In case keystore reports OUT_OF_KEYS, call this handler in an attempt to remotely provision
- // some keys.
- GenerateKeyPairHelperResult checkIfRetryableOrThrow(KeyStoreException e, int securityLevel) {
- GenerateRkpKey keyGen = new GenerateRkpKey(ActivityThread
- .currentApplication());
- KeyStoreException ksException;
- try {
- final int keyGenStatus = keyGen.notifyEmpty(securityLevel);
- // Default stance: temporary error. This is a hint to the caller to try again with
- // exponential back-off.
- int rkpStatus;
- switch (keyGenStatus) {
- case IGenerateRkpKeyService.Status.NO_NETWORK_CONNECTIVITY:
- rkpStatus = KeyStoreException.RKP_FETCHING_PENDING_CONNECTIVITY;
- break;
- case IGenerateRkpKeyService.Status.DEVICE_NOT_REGISTERED:
- rkpStatus = KeyStoreException.RKP_SERVER_REFUSED_ISSUANCE;
- break;
- case IGenerateRkpKeyService.Status.OK:
- // Explicitly return not-OK here so we retry in generateKeyPair. All other cases
- // should throw because a retry doesn't make sense if we didn't actually
- // provision fresh keys.
- return new GenerateKeyPairHelperResult(
- KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE, null);
- case IGenerateRkpKeyService.Status.NETWORK_COMMUNICATION_ERROR:
- case IGenerateRkpKeyService.Status.HTTP_CLIENT_ERROR:
- case IGenerateRkpKeyService.Status.HTTP_SERVER_ERROR:
- case IGenerateRkpKeyService.Status.HTTP_UNKNOWN_ERROR:
- case IGenerateRkpKeyService.Status.INTERNAL_ERROR:
- default:
- // These errors really should never happen. The best we can do is assume they
- // are transient and hint to the caller to retry with back-off.
- rkpStatus = KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE;
- break;
- }
- ksException = new KeyStoreException(
- ResponseCode.OUT_OF_KEYS,
- "Out of RKP keys due to IGenerateRkpKeyService status: " + keyGenStatus,
- rkpStatus);
- } catch (RemoteException f) {
- ksException = new KeyStoreException(
- ResponseCode.OUT_OF_KEYS,
- "Remote exception: " + f.getMessage(),
- KeyStoreException.RKP_TEMPORARILY_UNAVAILABLE);
- }
- ksException.initCause(e);
- throw new ProviderException("Failed to provision new attestation keys.", ksException);
- }
-
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index a5b192cd7ceb..abe8f859f2fe 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -55,20 +55,6 @@ prebuilt_etc {
}
// Extensions
-// NOTE: This module is still under active development and must not
-// be used in production. Use 'androidx.window.sidecar' instead.
-android_library_import {
- name: "window-extensions",
- aars: ["window-extensions-release.aar"],
- sdk_version: "current",
-}
-
-android_library_import {
- name: "window-extensions-core",
- aars: ["window-extensions-core-release.aar"],
- sdk_version: "current",
-}
-
java_library {
name: "androidx.window.extensions",
srcs: [
@@ -77,8 +63,8 @@ java_library {
"src/androidx/window/common/**/*.java",
],
static_libs: [
- "window-extensions",
- "window-extensions-core",
+ "androidx.window.extensions_extensions-nodeps",
+ "androidx.window.extensions.core_core-nodeps",
],
installable: true,
sdk_version: "core_platform",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 8386131b177d..a7a6b3c92157 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -122,16 +122,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
addWindowLayoutInfoListener(activity, extConsumer);
}
- @Override
- public void addWindowLayoutInfoListener(@NonNull @UiContext Context context,
- @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
- final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
- synchronized (mLock) {
- mJavaToExtConsumers.put(consumer, extConsumer);
- }
- addWindowLayoutInfoListener(context, extConsumer);
- }
-
/**
* Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but
* takes a UI Context as a parameter.
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
deleted file mode 100644
index 96ff840b984b..000000000000
--- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
deleted file mode 100644
index c3b6916121d0..000000000000
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 521a65cc4df6..bfbddbbe4aa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -22,6 +22,7 @@ import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
import static java.util.Objects.requireNonNull;
import android.content.Context;
+import android.graphics.Rect;
import android.os.IBinder;
import android.util.ArrayMap;
import android.view.SurfaceControl;
@@ -35,6 +36,9 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.util.TransitionUtil;
+
+import java.util.List;
/**
* Responsible for handling ActivityEmbedding related transitions.
@@ -86,12 +90,13 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
boolean containsEmbeddingSplit = false;
- for (TransitionInfo.Change change : info.getChanges()) {
+ boolean containsNonEmbeddedChange = false;
+ final List<TransitionInfo.Change> changes = info.getChanges();
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
- // Only animate the transition if all changes are in a Task with ActivityEmbedding.
- return false;
- }
- if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) {
+ containsNonEmbeddedChange = true;
+ } else if (!change.hasFlags(FLAG_FILLS_TASK)) {
// Whether the Task contains any ActivityEmbedding split before or after the
// transition.
containsEmbeddingSplit = true;
@@ -103,6 +108,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
// such as the device is in a folded state.
return false;
}
+ if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) {
+ return false;
+ }
// Start ActivityEmbedding animation.
mTransitionCallbacks.put(transition, finishCallback);
@@ -110,6 +118,37 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle
return true;
}
+ private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) {
+ final Rect nonClosingEmbeddedArea = new Rect();
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ if (!TransitionUtil.isClosingType(change.getMode())) {
+ if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ nonClosingEmbeddedArea.union(change.getEndAbsBounds());
+ continue;
+ }
+ // Not able to handle non-embedded container if it is not closing.
+ return false;
+ }
+ }
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)
+ && !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) {
+ // Unknown to animate containers outside the area of embedded activities.
+ return false;
+ }
+ }
+ // Drop the non-embedded closing change because it is occluded by embedded activities.
+ for (int i = changes.size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = changes.get(i);
+ if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) {
+ changes.remove(i);
+ }
+ }
+ return true;
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index e84a78f42616..133fd87a2f63 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -33,12 +33,19 @@ public interface BackAnimation {
*
* @param touchX the X touch position of the {@link MotionEvent}.
* @param touchY the Y touch position of the {@link MotionEvent}.
+ * @param velocityX the X velocity computed from the {@link MotionEvent}.
+ * @param velocityY the Y velocity computed from the {@link MotionEvent}.
* @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
* the process. This is forwarded separately because the input pipeline may mutate
* the {#event} action state later.
* @param swipeEdge the edge from which the swipe begins.
*/
- void onBackMotion(float touchX, float touchY, int keyAction,
+ void onBackMotion(
+ float touchX,
+ float touchY,
+ float velocityX,
+ float velocityY,
+ int keyAction,
@BackEvent.SwipeEdge int swipeEdge);
/**
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 210c9aab14d6..47d3a5c52074 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
@@ -256,8 +256,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private class BackAnimationImpl implements BackAnimation {
@Override
public void onBackMotion(
- float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
- mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
+ float touchX,
+ float touchY,
+ float velocityX,
+ float velocityY,
+ int keyAction,
+ @BackEvent.SwipeEdge int swipeEdge
+ ) {
+ mShellExecutor.execute(() -> onMotionEvent(
+ /* touchX = */ touchX,
+ /* touchY = */ touchY,
+ /* velocityX = */ velocityX,
+ /* velocityY = */ velocityY,
+ /* keyAction = */ keyAction,
+ /* swipeEdge = */ swipeEdge));
}
@Override
@@ -332,13 +344,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
- public void onMotionEvent(float touchX, float touchY, int keyAction,
+ public void onMotionEvent(
+ float touchX,
+ float touchY,
+ float velocityX,
+ float velocityY,
+ int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
if (mPostCommitAnimationInProgress) {
return;
}
- mTouchTracker.update(touchX, touchY);
+ mTouchTracker.update(touchX, touchY, velocityX, velocityY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -561,6 +578,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
if (runner.isWaitingAnimation()) {
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
+ // Supposed it is in post commit animation state, and start the timeout to watch
+ // if the animation is ready.
+ mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
return;
} else if (runner.isAnimationCancelled()) {
invokeOrCancelBack();
@@ -577,6 +597,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mPostCommitAnimationInProgress) {
return;
}
+
+ mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()");
mPostCommitAnimationInProgress = true;
mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION);
@@ -595,9 +617,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
@VisibleForTesting
void onBackAnimationFinished() {
- if (!mPostCommitAnimationInProgress) {
- return;
- }
// Stop timeout runner.
mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable);
mPostCommitAnimationInProgress = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
index 695ef4e66302..904574b08562 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java
@@ -42,11 +42,13 @@ class TouchTracker {
*/
private float mInitTouchX;
private float mInitTouchY;
+ private float mLatestVelocityX;
+ private float mLatestVelocityY;
private float mStartThresholdX;
private int mSwipeEdge;
private boolean mCancelled;
- void update(float touchX, float touchY) {
+ void update(float touchX, float touchY, float velocityX, float velocityY) {
/**
* If back was previously cancelled but the user has started swiping in the forward
* direction again, restart back.
@@ -58,6 +60,8 @@ class TouchTracker {
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
+ mLatestVelocityX = velocityX;
+ mLatestVelocityY = velocityY;
}
void setTriggerBack(boolean triggerBack) {
@@ -84,7 +88,14 @@ class TouchTracker {
}
BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
- return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
+ return new BackMotionEvent(
+ /* touchX = */ mInitTouchX,
+ /* touchY = */ mInitTouchY,
+ /* progress = */ 0,
+ /* velocityX = */ 0,
+ /* velocityY = */ 0,
+ /* swipeEdge = */ mSwipeEdge,
+ /* departingAnimationTarget = */ target);
}
BackMotionEvent createProgressEvent() {
@@ -111,7 +122,14 @@ class TouchTracker {
}
BackMotionEvent createProgressEvent(float progress) {
- return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
+ return new BackMotionEvent(
+ /* touchX = */ mLatestTouchX,
+ /* touchY = */ mLatestTouchY,
+ /* progress = */ progress,
+ /* velocityX = */ mLatestVelocityX,
+ /* velocityY = */ mLatestVelocityY,
+ /* swipeEdge = */ mSwipeEdge,
+ /* departingAnimationTarget = */ null);
}
public void setProgressThreshold(float progressThreshold) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 670b24c176b5..0400963a47e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.WindowingMode
import android.content.Context
+import android.graphics.Point
import android.graphics.Rect
import android.os.IBinder
import android.os.SystemProperties
@@ -193,6 +194,21 @@ class DesktopTasksController(
}
}
+
+ /**
+ * Move a task to fullscreen after being dragged from fullscreen and released back into
+ * status bar area
+ */
+ fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) {
+ val wct = WindowContainerTransaction()
+ addMoveToFullscreenChanges(wct, task.token)
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition)
+ } else {
+ shellTaskOrganizer.applyTransaction(wct)
+ }
+ }
+
fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
val wct = WindowContainerTransaction()
addMoveToFullscreenChanges(wct, task.token)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
index 3df2340d4524..27eda16f4171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java
@@ -17,11 +17,13 @@
package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.ActivityManager;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;
@@ -55,6 +57,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
+ private Point mStartPosition;
public EnterDesktopTaskTransitionHandler(
Transitions transitions) {
@@ -79,6 +82,17 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
mPendingTransitionTokens.add(token);
}
+ /**
+ * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+ * @param wct WindowContainerTransaction for transition
+ * @param startPosition Position of task when transition is triggered
+ */
+ public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct,
+ Point startPosition) {
+ mStartPosition = startPosition;
+ startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct);
+ }
+
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startT,
@@ -173,6 +187,37 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
return true;
}
+ if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && mStartPosition != null) {
+ // This Transition animates a task to fullscreen after being dragged from the status
+ // bar and then released back into the status bar area
+ final SurfaceControl sc = change.getLeash();
+ startT.setWindowCrop(sc, null);
+ startT.apply();
+
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f);
+ animator.setDuration(FREEFORM_ANIMATION_DURATION);
+ final SurfaceControl.Transaction t = mTransactionSupplier.get();
+ animator.addUpdateListener(animation -> {
+ final float scale = animation.getAnimatedFraction();
+ t.setPosition(sc, mStartPosition.x * (1 - scale),
+ mStartPosition.y * (1 - scale));
+ t.setScale(sc, scale, scale);
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTransitions.getMainExecutor().execute(
+ () -> finishCallback.onTransitionFinished(null, null));
+ }
+ });
+ animator.start();
+ return true;
+ }
+
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f70df833cf4f..1d7e64988359 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -30,14 +30,17 @@ import android.app.TaskInfo;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
+import android.os.SystemClock;
import android.view.Surface;
import android.view.SurfaceControl;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions;
import java.lang.annotation.Retention;
@@ -61,6 +64,14 @@ public class PipAnimationController {
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {}
+ /**
+ * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
+ * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
+ * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong
+ * animation style to an unrelated task.
+ */
+ private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800;
+
public static final int TRANSITION_DIRECTION_NONE = 0;
public static final int TRANSITION_DIRECTION_SAME = 1;
public static final int TRANSITION_DIRECTION_TO_PIP = 2;
@@ -109,6 +120,9 @@ public class PipAnimationController {
});
private PipTransitionAnimator mCurrentAnimator;
+ @AnimationType
+ private int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private long mLastOneShotAlphaAnimationTime;
public PipAnimationController(PipSurfaceTransactionHelper helper) {
mSurfaceTransactionHelper = helper;
@@ -222,6 +236,37 @@ public class PipAnimationController {
}
/**
+ * Sets the preferred enter animation type for one time. This is typically used to set the
+ * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}.
+ * <p>
+ * For example, gesture navigation would first fade out the PiP activity, and the transition
+ * should be responsible to animate in (such as fade in) the PiP.
+ */
+ public void setOneShotEnterAnimationType(@AnimationType int animationType) {
+ mOneShotAnimationType = animationType;
+ if (animationType == ANIM_TYPE_ALPHA) {
+ mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
+ }
+ }
+
+ /** Returns the preferred animation type and consumes the one-shot type if needed. */
+ @AnimationType
+ public int takeOneShotEnterAnimationType() {
+ final int type = mOneShotAnimationType;
+ if (type == ANIM_TYPE_ALPHA) {
+ // Restore to default type.
+ mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
+ > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "Alpha animation is expired. Use bounds animation.");
+ return ANIM_TYPE_BOUNDS;
+ }
+ }
+ return type;
+ }
+
+ /**
* Additional callback interface for PiP animation
*/
public static class PipAnimationCallback {
@@ -255,7 +300,7 @@ public class PipAnimationController {
* @return true if handled by the handler, false otherwise.
*/
public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
- Rect destinationBounds) {
+ Rect destinationBounds, float alpha) {
return false;
}
}
@@ -356,9 +401,10 @@ public class PipAnimationController {
}
boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx,
- Rect destinationBounds) {
+ Rect destinationBounds, float alpha) {
if (mPipTransactionHandler != null) {
- return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds);
+ return mPipTransactionHandler.handlePipTransaction(
+ leash, tx, destinationBounds, alpha);
}
return false;
}
@@ -503,7 +549,9 @@ public class PipAnimationController {
getSurfaceTransactionHelper().alpha(tx, leash, alpha)
.round(tx, leash, shouldApplyCornerRadius())
.shadow(tx, leash, shouldApplyShadowRadius());
- tx.apply();
+ if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) {
+ tx.apply();
+ }
}
@Override
@@ -618,7 +666,7 @@ public class PipAnimationController {
.shadow(tx, leash, shouldApplyShadowRadius());
}
}
- if (!handlePipTransaction(leash, tx, bounds)) {
+ if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) {
tx.apply();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 000624499f79..0775f5279e31 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -45,6 +45,13 @@ public interface PipMenuController {
String MENU_WINDOW_TITLE = "PipMenuView";
/**
+ * Used with
+ * {@link PipMenuController#movePipMenu(SurfaceControl, SurfaceControl.Transaction, Rect,
+ * float)} to indicate that we don't want to affect the alpha value of the menu surfaces.
+ */
+ float ALPHA_NO_CHANGE = -1f;
+
+ /**
* Called when
* {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)}
* is called.
@@ -85,8 +92,8 @@ public interface PipMenuController {
* need to synchronize the movements on the same frame as PiP.
*/
default void movePipMenu(@Nullable SurfaceControl pipLeash,
- @Nullable SurfaceControl.Transaction t,
- Rect destinationBounds) {}
+ @Nullable SurfaceControl.Transaction t, Rect destinationBounds, float alpha) {
+ }
/**
* Update the PiP menu with the given bounds for re-layout purposes.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index a0bd064149d2..23706a5e35a4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,7 +62,6 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
import android.view.Choreographer;
@@ -111,12 +110,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = PipTaskOrganizer.class.getSimpleName();
private static final boolean DEBUG = false;
- /**
- * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
- * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
- * navigation, then the alpha type is unexpected.
- */
- private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
/**
* The fixed start delay in ms when fading out the content overlay from bounds animation.
@@ -256,7 +249,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}, null);
}
- private boolean shouldSyncPipTransactionWithMenu() {
+ protected boolean shouldSyncPipTransactionWithMenu() {
return mPipMenuController.isMenuVisible();
}
@@ -284,9 +277,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
new PipAnimationController.PipTransactionHandler() {
@Override
public boolean handlePipTransaction(SurfaceControl leash,
- SurfaceControl.Transaction tx, Rect destinationBounds) {
+ SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
if (shouldSyncPipTransactionWithMenu()) {
- mPipMenuController.movePipMenu(leash, tx, destinationBounds);
+ mPipMenuController.movePipMenu(leash, tx, destinationBounds, alpha);
return true;
}
return false;
@@ -301,8 +294,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
private WindowContainerToken mToken;
private SurfaceControl mLeash;
protected PipTransitionState mPipTransitionState;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- private long mLastOneShotAlphaAnimationTime;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
protected PictureInPictureParams mPictureInPictureParams;
@@ -390,6 +381,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mPipTransitionController;
}
+ PipAnimationController.PipTransactionHandler getPipTransactionHandler() {
+ return mPipTransactionHandler;
+ }
+
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
@@ -422,18 +417,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
- * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
- */
- public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- if (animationType == ANIM_TYPE_ALPHA) {
- mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
- }
- }
-
- /**
* Override if the PiP should always use a fade-in animation during PiP entry.
*
* @return true if the mOneShotAnimationType should always be
@@ -733,26 +716,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return;
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA
- && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
- > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
- ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: Alpha animation is expired. Use bounds animation.", TAG);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
- }
-
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
// For Shell transition, we will animate the window in PipTransition#startAnimation
// instead of #onTaskAppeared.
return;
}
- if (shouldAlwaysFadeIn()) {
- mOneShotAnimationType = ANIM_TYPE_ALPHA;
- }
-
+ final int animationType = shouldAlwaysFadeIn()
+ ? ANIM_TYPE_ALPHA
+ : mPipAnimationController.takeOneShotEnterAnimationType();
if (mWaitForFixedRotation) {
- onTaskAppearedWithFixedRotation();
+ onTaskAppearedWithFixedRotation(animationType);
return;
}
@@ -763,7 +737,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
Objects.requireNonNull(destinationBounds, "Missing destination bounds");
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ if (animationType == ANIM_TYPE_BOUNDS) {
if (!shouldAttachMenuEarly()) {
mPipMenuController.attach(mLeash);
}
@@ -773,16 +747,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
null /* updateBoundsCallback */);
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ } else if (animationType == ANIM_TYPE_ALPHA) {
enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
- throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
+ throw new RuntimeException("Unrecognized animation type: " + animationType);
}
}
- private void onTaskAppearedWithFixedRotation() {
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ private void onTaskAppearedWithFixedRotation(int animationType) {
+ if (animationType == ANIM_TYPE_ALPHA) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
// If deferred, hside the surface till fixed rotation is completed.
@@ -791,7 +764,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
tx.setAlpha(mLeash, 0f);
tx.show(mLeash);
tx.apply();
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
return;
}
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
@@ -1417,7 +1389,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
.scale(tx, mLeash, startBounds, toBounds, degrees)
.round(tx, mLeash, startBounds, toBounds);
if (shouldSyncPipTransactionWithMenu()) {
- mPipMenuController.movePipMenu(mLeash, tx, toBounds);
+ mPipMenuController.movePipMenu(mLeash, tx, toBounds, PipMenuController.ALPHA_NO_CHANGE);
} else {
tx.apply();
}
@@ -1583,7 +1555,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (!isInPip()) {
return;
}
- mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.movePipMenu(null, null, destinationBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
mPipMenuController.updateMenuBounds(destinationBounds);
}
@@ -1895,7 +1868,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
+ " binder=" + (mToken != null ? mToken.asBinder() : null));
pw.println(innerPrefix + "mLeash=" + mLeash);
pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
- pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 4a76a502462c..c5e92294794e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -87,7 +87,7 @@ public class PipTransition extends PipTransitionController {
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
+ private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
private Transitions.TransitionFinishCallback mFinishCallback;
private SurfaceControl.Transaction mFinishTransaction;
private final Rect mExitDestinationBounds = new Rect();
@@ -133,20 +133,6 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void setIsFullAnimation(boolean isFullAnimation) {
- setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA);
- }
-
- /**
- * Sets the preferred animation type for one time.
- * This is typically used to set the animation type to
- * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
- */
- private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
- mOneShotAnimationType = animationType;
- }
-
- @Override
public void startExitTransition(int type, WindowContainerTransaction out,
@Nullable Rect destinationBounds) {
if (destinationBounds != null) {
@@ -288,7 +274,10 @@ public class PipTransition extends PipTransitionController {
if (!requestHasPipEnter(request)) {
throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
}
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn()
+ ? ANIM_TYPE_ALPHA
+ : mPipAnimationController.takeOneShotEnterAnimationType();
+ if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
mRequestedEnterTransition = transition;
mRequestedEnterTask = request.getTriggerTask().token;
outWCT.setActivityWindowingMode(request.getTriggerTask().token,
@@ -308,7 +297,7 @@ public class PipTransition extends PipTransitionController {
@Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
- if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
// A fade-in was requested but not-yet started. In this case, just recalculate the
// initial state under the new rotation.
int rotationDelta = deltaRotation(startRotation, endRotation);
@@ -760,7 +749,6 @@ public class PipTransition extends PipTransitionController {
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
&& mPipTransitionState.getInSwipePipToHomeTransition()) {
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9])
.setPosition(leash, destinationBounds.left, destinationBounds.top)
@@ -796,17 +784,16 @@ public class PipTransition extends PipTransitionController {
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
- if (mPipOrganizer.shouldAlwaysFadeIn()) {
- mOneShotAnimationType = ANIM_TYPE_ALPHA;
- }
-
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ final int enterAnimationType = mEnterAnimationType;
+ if (enterAnimationType == ANIM_TYPE_ALPHA) {
+ // Restore to default type.
+ mEnterAnimationType = ANIM_TYPE_BOUNDS;
startTransaction.setAlpha(leash, 0f);
}
startTransaction.apply();
PipAnimationController.PipTransitionAnimator animator;
- if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
+ if (enterAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
0 /* startingAngle */, rotationDelta);
@@ -829,15 +816,14 @@ public class PipTransition extends PipTransitionController {
animator.setColorContentOverlay(mContext);
}
}
- } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
- mOneShotAnimationType = ANIM_TYPE_BOUNDS;
} else {
- throw new RuntimeException("Unrecognized animation type: "
- + mOneShotAnimationType);
+ throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
}
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
+ .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler())
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
@@ -897,7 +883,7 @@ public class PipTransition extends PipTransitionController {
.setWindowCrop(leash, endBounds.width(), endBounds.height());
}
}
- mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction);
+ mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction);
startTransaction.apply();
mPipOrganizer.onExitPipFinished(taskInfo);
@@ -964,7 +950,8 @@ public class PipTransition extends PipTransitionController {
}
private void finishResizeForMenu(Rect destinationBounds) {
- mPipMenuController.movePipMenu(null, null, destinationBounds);
+ mPipMenuController.movePipMenu(null, null, destinationBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
mPipMenuController.updateMenuBounds(destinationBounds);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index f51e247fe112..7979ce7a80c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -105,15 +105,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
}
/**
- * Called to inform the transition that the animation should start with the assumption that
- * PiP is not animating from its original bounds, but rather a continuation of another
- * animation. For example, gesture navigation would first fade out the PiP activity, and the
- * transition should be responsible to animate in (such as fade in) the PiP.
- */
- public void setIsFullAnimation(boolean isFullAnimation) {
- }
-
- /**
* Called when the Shell wants to start an exit Pip transition/animation.
*/
public void startExitTransition(int type, WindowContainerTransaction out,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index 94e593b106a5..e7a1395f541c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -298,7 +298,8 @@ public class PhonePipMenuController implements PipMenuController {
}
// Sync the menu bounds before showing it in case it is out of sync.
- movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds);
+ movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds,
+ PipMenuController.ALPHA_NO_CHANGE);
updateMenuBounds(stackBounds);
mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay,
@@ -311,7 +312,7 @@ public class PhonePipMenuController implements PipMenuController {
@Override
public void movePipMenu(@Nullable SurfaceControl pipLeash,
@Nullable SurfaceControl.Transaction t,
- Rect destinationBounds) {
+ Rect destinationBounds, float alpha) {
if (destinationBounds.isEmpty()) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 463ad77d828f..b0bb14b49db6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -968,12 +968,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
}
- private void setPinnedStackAnimationType(int animationType) {
- mPipTaskOrganizer.setOneShotAnimationType(animationType);
- mPipTransitionController.setIsFullAnimation(
- animationType == PipAnimationController.ANIM_TYPE_BOUNDS);
- }
-
@VisibleForTesting
void setPinnedStackAnimationListener(PipAnimationListener callback) {
mPinnedStackAnimationRecentsCallback = callback;
@@ -1337,7 +1331,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb
@Override
public void setPipAnimationTypeToAlpha() {
executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha",
- (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA));
+ (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType(
+ ANIM_TYPE_ALPHA));
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
index a7171fd5b220..82fe38ccc7b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
@@ -21,6 +21,7 @@ import static com.android.wm.shell.pip.PipUtils.dpToPx;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
@@ -366,11 +367,8 @@ public class PipSizeSpecHandler {
mContext = context;
mPipDisplayLayoutState = pipDisplayLayoutState;
- boolean enablePipSizeLargeScreen = SystemProperties
- .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true);
-
// choose between two implementations of size spec logic
- if (enablePipSizeLargeScreen) {
+ if (supportsPipSizeLargeScreen()) {
mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
} else {
mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
@@ -515,6 +513,18 @@ public class PipSizeSpecHandler {
}
}
+ @VisibleForTesting
+ boolean supportsPipSizeLargeScreen() {
+ // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
+ // can be injected
+ return SystemProperties
+ .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
+ }
+
+ private boolean isTv() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ }
+
/** Dumps internal state. */
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index b18e21c03c63..b2a189b45d6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -30,6 +30,7 @@ import android.os.Handler;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.SurfaceSyncGroup;
@@ -202,8 +203,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
}
private void addPipMenuViewToSystemWindows(View v, String title) {
- mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */,
- 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
+ final WindowManager.LayoutParams layoutParams =
+ getPipMenuLayoutParams(mContext, title, 0 /* width */, 0 /* height */);
+ layoutParams.alpha = 0f;
+ mSystemWindows.addView(v, layoutParams, 0 /* displayId */, SHELL_ROOT_LAYER_PIP);
}
void onPipTransitionFinished(boolean enterTransition) {
@@ -309,9 +312,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
@Override
public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx,
- Rect pipBounds) {
+ Rect pipBounds, float alpha) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
- "%s: movePipMenu: %s", TAG, pipBounds.toShortString());
+ "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha);
if (pipBounds.isEmpty()) {
if (pipTx == null) {
@@ -333,6 +336,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis
pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top);
pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top);
+ if (alpha != ALPHA_NO_CHANGE) {
+ pipTx.setAlpha(frontSurface, alpha);
+ pipTx.setAlpha(backSurface, alpha);
+ }
+
// Synchronize drawing the content in the front and back surfaces together with the pip
// transaction and the position change for the front and back surfaces
final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip");
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 0940490e9944..4819f665d6d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -98,4 +98,11 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer {
protected boolean shouldAlwaysFadeIn() {
return true;
}
+
+ @Override
+ protected boolean shouldSyncPipTransactionWithMenu() {
+ // We always have a menu visible and want to sync the pip transaction with the menu, even
+ // when the menu alpha is 0 (e.g. when a fade-in animation starts).
+ return true;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 2cd16be9590c..498f95c8595e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -541,6 +541,34 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
instanceId);
}
+ void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ if (options1 == null) options1 = new Bundle();
+ final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
+ final String packageName1 = shortcutInfo.getPackage();
+ // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
+ // recents that hasn't launched and is not being organized
+ final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
+ if (samePackage(packageName1, packageName2)) {
+ if (supportMultiInstancesSplit(packageName1)) {
+ activityOptions.setApplyMultipleTaskFlagForShortcut(true);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else {
+ if (mRecentTasksOptional.isPresent()) {
+ mRecentTasksOptional.get().removeSplitPair(taskId);
+ }
+ taskId = INVALID_TASK_ID;
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Cancel entering split as not supporting multi-instances");
+ Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
+ splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
/**
* See {@link #startIntent(PendingIntent, Intent, int, Bundle)}
* @param instanceId to be used by {@link SplitscreenEventLogger}
@@ -580,6 +608,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
Intent fillInIntent = null;
final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent);
+ // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in
+ // recents that hasn't launched and is not being organized
final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer);
if (samePackage(packageName1, packageName2)) {
if (supportMultiInstancesSplit(packageName1)) {
@@ -587,6 +617,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
} else {
+ if (mRecentTasksOptional.isPresent()) {
+ mRecentTasksOptional.get().removeSplitPair(taskId);
+ }
+ taskId = INVALID_TASK_ID;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
"Cancel entering split as not supporting multi-instances");
Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
@@ -1075,9 +1109,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
float splitRatio, @Nullable RemoteTransition remoteTransition,
InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask",
- (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo,
- options1, taskId, options2, splitPosition, splitRatio, remoteTransition,
- instanceId));
+ (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 22800ad8e8a8..2c08cd44becc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
@@ -155,8 +156,10 @@ class SplitScreenTransitions {
}
boolean isRootOrSplitSideRoot = change.getParent() == null
|| topRoot.equals(change.getParent());
- // For enter or exit, we only want to animate the side roots but not the top-root.
- if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
+ boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR;
+ // For enter or exit, we only want to animate side roots and the divider but not the
+ // top-root.
+ if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) {
continue;
}
@@ -165,6 +168,10 @@ class SplitScreenTransitions {
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
change.getEndAbsBounds().height());
+ } else if (isDivider) {
+ t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
+ t.setLayer(leash, Integer.MAX_VALUE);
+ t.show(leash);
}
boolean isOpening = isOpeningTransition(info);
if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
@@ -282,6 +289,12 @@ class SplitScreenTransitions {
return null;
}
+ void startFullscreenTransition(WindowContainerTransaction wct,
+ @Nullable RemoteTransition handler) {
+ mTransitions.startTransition(TRANSIT_OPEN, wct,
+ new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
IBinder startEnterTransition(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index f00fdba50748..e4f27240b2cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -612,6 +612,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (taskId2 == INVALID_TASK_ID) {
+ if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
+ }
+ if (mRecentTasks.isPresent()) {
+ mRecentTasks.get().removeSplitPair(taskId1);
+ }
+ options1 = options1 != null ? options1 : new Bundle();
+ wct.startTask(taskId1, options1);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
@@ -627,6 +640,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (taskId == INVALID_TASK_ID) {
+ options1 = options1 != null ? options1 : new Bundle();
+ wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
@@ -641,6 +661,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (taskId == INVALID_TASK_ID) {
+ options1 = options1 != null ? options1 : new Bundle();
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
prepareEvictChildTasksIfSplitActive(wct);
setSideStagePosition(splitPosition, wct);
options1 = options1 != null ? options1 : new Bundle();
@@ -689,6 +716,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@SplitPosition int splitPosition, float splitRatio,
@Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (pendingIntent2 == null) {
+ options1 = options1 != null ? options1 : new Bundle();
+ if (shortcutInfo1 != null) {
+ wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
+ } else {
+ wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1);
+ }
+ mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
+ return;
+ }
+
if (!mMainStage.isActive()) {
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
@@ -730,6 +768,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (options1 == null) options1 = new Bundle();
if (taskId2 == INVALID_TASK_ID) {
// Launching a solo task.
+ // Exit split first if this task under split roots.
+ if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) {
+ exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT);
+ }
ActivityOptions activityOptions = ActivityOptions.fromBundle(options1);
activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter));
options1 = activityOptions.toBundle();
@@ -931,10 +973,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct);
}
- mSyncQueue.runInSync(t -> {
- setDividerVisibility(true, t);
- });
-
setEnterInstanceId(instanceId);
}
@@ -973,6 +1011,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
onRemoteAnimationFinishedOrCancelled(evictWct);
+ setDividerVisibility(true, null);
try {
adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
@@ -1013,6 +1052,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
t.setPosition(apps[i].leash, 0, 0);
}
}
+ setDividerVisibility(true, t);
t.apply();
IRemoteAnimationFinishedCallback wrapCallback =
@@ -1503,6 +1543,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
void finishEnterSplitScreen(SurfaceControl.Transaction t) {
mSplitLayout.init();
setDividerVisibility(true, t);
+ // Ensure divider surface are re-parented back into the hierarchy at the end of the
+ // transition. See Transition#buildFinishTransaction for more detail.
+ t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash);
+
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
t.show(mRootTaskLeash);
setSplitsVisible(true);
@@ -1812,6 +1856,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
setDividerVisibility(mainStageVisible, null);
}
+ // Set divider visibility flag and try to apply it, the param transaction is used to apply.
+ // See applyDividerVisibility for more detail.
private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) {
if (visible == mDividerVisible) {
return;
@@ -1838,14 +1884,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return;
}
- if (t != null) {
- applyDividerVisibility(t);
- } else {
- mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction));
- }
+ applyDividerVisibility(t);
}
- private void applyDividerVisibility(SurfaceControl.Transaction t) {
+ // Apply divider visibility by current visibility flag. If param transaction is non-null, it
+ // will apply by that transaction, if it is null and visible, it will run a fade-in animation,
+ // otherwise hide immediately.
+ private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) {
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
@@ -1862,7 +1907,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerFadeInAnimator.cancel();
}
- if (mDividerVisible) {
+ mSplitLayout.getRefDividerBounds(mTempRect1);
+ if (t != null) {
+ t.setVisibility(dividerLeash, mDividerVisible);
+ t.setLayer(dividerLeash, Integer.MAX_VALUE);
+ t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top);
+ } else if (mDividerVisible) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f);
mDividerFadeInAnimator.addUpdateListener(animation -> {
@@ -1902,7 +1952,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerFadeInAnimator.start();
} else {
- t.hide(dividerLeash);
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ transaction.hide(dividerLeash);
+ transaction.apply();
+ mTransactionPool.release(transaction);
}
}
@@ -2361,9 +2414,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- // TODO(shell-transitions): Implement a fallback behavior for now.
- throw new IllegalStateException("Somehow removed the last task in a stage"
- + " outside of a proper transition");
+ Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+ + "transition.");
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final int dismissTop = mMainStage.getChildCount() == 0
+ ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
+ prepareExitSplitScreen(dismissTop, wct);
+ mSplitTransitions.startDismissTransition(wct, this, dismissTop,
+ EXIT_REASON_UNKNOWN);
// This can happen in some pathological cases. For example:
// 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
// 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
@@ -2486,7 +2544,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
finishEnterSplitScreen(finishT);
- addDividerBarToTransition(info, finishT, true /* show */);
+ addDividerBarToTransition(info, true /* show */);
return true;
}
@@ -2629,7 +2687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
// TODO: Have a proper remote for this. Until then, though, reset state and use the
// normal animation stuff (which falls back to the normal launcher remote).
- t.hide(mSplitLayout.getDividerLeash());
+ setDividerVisibility(false, t);
mSplitLayout.release(t);
mSplitTransitions.mPendingDismiss = null;
return false;
@@ -2647,7 +2705,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
}
- addDividerBarToTransition(info, finishT, false /* show */);
+ addDividerBarToTransition(info, false /* show */);
return true;
}
@@ -2688,11 +2746,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
logExit(EXIT_REASON_UNKNOWN);
}
- private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction finishT, boolean show) {
+ private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
mSplitLayout.getRefDividerBounds(mTempRect1);
+ barChange.setParent(mRootTaskInfo.token);
barChange.setStartAbsBounds(mTempRect1);
barChange.setEndAbsBounds(mTempRect1);
barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
@@ -2700,15 +2758,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Technically this should be order-0, but this is running after layer assignment
// and it's a special case, so just add to end.
info.addChange(barChange);
-
- if (show) {
- finishT.setLayer(leash, Integer.MAX_VALUE);
- finishT.setPosition(leash, mTempRect1.left, mTempRect1.top);
- finishT.show(leash);
- // Ensure divider surface are re-parented back into the hierarchy at the end of the
- // transition. See Transition#buildFinishTransaction for more detail.
- finishT.reparent(leash, mRootTaskLeash);
- }
}
RemoteAnimationTarget getDividerBarLegacyTarget() {
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 fa4de16b37f1..bdb7d44bad32 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
@@ -143,6 +143,10 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Transition type to fullscreen from desktop mode. */
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
+ /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */
+ public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE =
+ WindowManager.TRANSIT_FIRST_CUSTOM + 13;
+
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 060dc4e05b46..dfde7e6feff5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -110,19 +110,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
mRelayoutParams.reset();
mRelayoutParams.mRunningTaskInfo = taskInfo;
mRelayoutParams.mLayoutResId = R.layout.caption_window_decor;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index f99821747fef..5226eeebcaa2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -34,6 +34,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Handler;
@@ -197,7 +198,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
- if (mTransitionPausingRelayout.equals(merged)) {
+ if (merged.equals(mTransitionPausingRelayout)) {
mTransitionPausingRelayout = playing;
}
}
@@ -312,8 +313,12 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} else if (id == R.id.back_button) {
mTaskOperations.injectBackKey();
} else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
- moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
- decoration.createHandleMenu();
+ if (!decoration.isHandleMenuActive()) {
+ moveTaskToFront(mTaskOrganizer.getRunningTaskInfo(mTaskId));
+ decoration.createHandleMenu();
+ } else {
+ decoration.closeHandleMenu();
+ }
} else if (id == R.id.desktop_button) {
mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
mDesktopTasksController.ifPresent(c -> c.moveToDesktop(mTaskId));
@@ -553,8 +558,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragToDesktopAnimationStarted = false;
return;
} else if (mDragToDesktopAnimationStarted) {
- mDesktopTasksController.ifPresent(c ->
- c.moveToFullscreen(relevantDecor.mTaskInfo));
+ Point startPosition = new Point((int) ev.getX(), (int) ev.getY());
+ mDesktopTasksController.ifPresent(
+ c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
+ startPosition));
mDragToDesktopAnimationStarted = false;
return;
}
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 efc90b5e63e1..67e99d73b811 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
@@ -42,6 +42,7 @@ import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
+import android.window.SurfaceSyncGroup;
import android.window.WindowContainerTransaction;
import com.android.launcher3.icons.IconProvider;
@@ -208,11 +209,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
final WindowContainerTransaction wct = new WindowContainerTransaction();
- final int outsetLeftId = R.dimen.freeform_resize_handle;
- final int outsetTopId = R.dimen.freeform_resize_handle;
- final int outsetRightId = R.dimen.freeform_resize_handle;
- final int outsetBottomId = R.dimen.freeform_resize_handle;
-
final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
taskInfo.getWindowingMode());
mRelayoutParams.reset();
@@ -220,9 +216,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mRelayoutParams.mLayoutResId = windowDecorLayoutId;
mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
mRelayoutParams.mShadowRadiusId = shadowRadiusID;
- if (isDragResizeable) {
- mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
- }
relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
// After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
@@ -319,51 +312,50 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
* Create and display handle menu window
*/
void createHandleMenu() {
+ final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG);
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
updateHandleMenuPillPositions();
- createAppInfoPill(t);
+ createAppInfoPill(t, ssg);
// Only show windowing buttons in proto2. Proto1 uses a system-level mode only.
final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled();
if (shouldShowWindowingPill) {
- createWindowingPill(t);
+ createWindowingPill(t, ssg);
}
- createMoreActionsPill(t);
+ createMoreActionsPill(t, ssg);
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
+ ssg.addTransaction(t);
+ ssg.markSyncReady();
setupHandleMenu(shouldShowWindowingPill);
}
- private void createAppInfoPill(SurfaceControl.Transaction t) {
+ private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
final int x = (int) mHandleMenuAppInfoPillPosition.x;
final int y = (int) mHandleMenuAppInfoPillPosition.y;
mHandleMenuAppInfoPill = addWindow(
R.layout.desktop_mode_window_decor_handle_menu_app_info_pill,
"Menu's app info pill",
- t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
+ t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius);
}
- private void createWindowingPill(SurfaceControl.Transaction t) {
+ private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
final int x = (int) mHandleMenuWindowingPillPosition.x;
final int y = (int) mHandleMenuWindowingPillPosition.y;
mHandleMenuWindowingPill = addWindow(
R.layout.desktop_mode_window_decor_handle_menu_windowing_pill,
"Menu's windowing pill",
- t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
+ t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius);
}
- private void createMoreActionsPill(SurfaceControl.Transaction t) {
+ private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) {
final int x = (int) mHandleMenuMoreActionsPillPosition.x;
final int y = (int) mHandleMenuMoreActionsPillPosition.y;
mHandleMenuMoreActionsPill = addWindow(
R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill,
"Menu's more actions pill",
- t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
+ t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius);
}
private void setupHandleMenu(boolean windowingPillShown) {
@@ -424,13 +416,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (mRelayoutParams.mLayoutResId
== R.layout.desktop_mode_app_controls_window_decor) {
// Align the handle menu to the left of the caption.
- menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop;
+ menuX = mRelayoutParams.mCaptionX + mMarginMenuStart;
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuTop;
} else {
// Position the handle menu at the center of the caption.
- menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2)
- - mResult.mDecorContainerOffsetX;
- menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart;
+ menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
+ menuY = mRelayoutParams.mCaptionY + mMarginMenuStart;
}
// App Info pill setup.
@@ -487,26 +478,30 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
if (mHandleMenuAppInfoPill.mWindowViewHost.getView().getWidth() == 0) return;
PointF inputPoint = offsetCaptionLocation(ev);
+
+ // If this is called before open_menu_button's onClick, we don't want to close
+ // the menu since it will just reopen in onClick.
+ final boolean pointInOpenMenuButton = pointInView(
+ mResult.mRootView.findViewById(R.id.open_menu_button),
+ inputPoint.x,
+ inputPoint.y);
+
final boolean pointInAppInfoPill = pointInView(
mHandleMenuAppInfoPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuAppInfoPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuAppInfoPillPosition.x,
+ inputPoint.y - mHandleMenuAppInfoPillPosition.y);
boolean pointInWindowingPill = false;
if (mHandleMenuWindowingPill != null) {
pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuWindowingPillPosition.x
- - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuWindowingPillPosition.y
- - mResult.mDecorContainerOffsetY);
+ inputPoint.x - mHandleMenuWindowingPillPosition.x,
+ inputPoint.y - mHandleMenuWindowingPillPosition.y);
}
final boolean pointInMoreActionsPill = pointInView(
mHandleMenuMoreActionsPill.mWindowViewHost.getView(),
- inputPoint.x - mHandleMenuMoreActionsPillPosition.x
- - mResult.mDecorContainerOffsetX,
- inputPoint.y - mHandleMenuMoreActionsPillPosition.y
- - mResult.mDecorContainerOffsetY);
- if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill) {
+ inputPoint.x - mHandleMenuMoreActionsPillPosition.x,
+ inputPoint.y - mHandleMenuMoreActionsPillPosition.y);
+ if (!pointInAppInfoPill && !pointInWindowingPill
+ && !pointInMoreActionsPill && !pointInOpenMenuButton) {
closeHandleMenu();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8cb575cc96e3..d5437c72acac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -64,8 +64,8 @@ class DragResizeInputListener implements AutoCloseable {
private final TaskResizeInputEventReceiver mInputEventReceiver;
private final DragPositioningCallback mCallback;
- private int mWidth;
- private int mHeight;
+ private int mTaskWidth;
+ private int mTaskHeight;
private int mResizeHandleThickness;
private int mCornerSize;
@@ -128,78 +128,84 @@ class DragResizeInputListener implements AutoCloseable {
* This is also used to update the touch regions of this handler every event dispatched here is
* a potential resize request.
*
- * @param width The width of the drag resize handler in pixels, including resize handle
- * thickness. That is task width + 2 * resize handle thickness.
- * @param height The height of the drag resize handler in pixels, including resize handle
- * thickness. That is task height + 2 * resize handle thickness.
+ * @param taskWidth The width of the task.
+ * @param taskHeight The height of the task.
* @param resizeHandleThickness The thickness of the resize handle in pixels.
* @param cornerSize The size of the resize handle centered in each corner.
* @param touchSlop The distance in pixels user has to drag with touch for it to register as
* a resize action.
*/
- void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize,
+ void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize,
int touchSlop) {
- if (mWidth == width && mHeight == height
+ if (mTaskWidth == taskWidth && mTaskHeight == taskHeight
&& mResizeHandleThickness == resizeHandleThickness
&& mCornerSize == cornerSize) {
return;
}
- mWidth = width;
- mHeight = height;
+ mTaskWidth = taskWidth;
+ mTaskHeight = taskHeight;
mResizeHandleThickness = resizeHandleThickness;
mCornerSize = cornerSize;
mDragDetector.setTouchSlop(touchSlop);
Region touchRegion = new Region();
- final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+ final Rect topInputBounds = new Rect(
+ -mResizeHandleThickness,
+ -mResizeHandleThickness,
+ mTaskWidth + mResizeHandleThickness,
+ 0);
touchRegion.union(topInputBounds);
- final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
- mResizeHandleThickness, mHeight - mResizeHandleThickness);
+ final Rect leftInputBounds = new Rect(
+ -mResizeHandleThickness,
+ 0,
+ 0,
+ mTaskHeight);
touchRegion.union(leftInputBounds);
final Rect rightInputBounds = new Rect(
- mWidth - mResizeHandleThickness, mResizeHandleThickness,
- mWidth, mHeight - mResizeHandleThickness);
+ mTaskWidth,
+ 0,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight);
touchRegion.union(rightInputBounds);
- final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
- mWidth, mHeight);
+ final Rect bottomInputBounds = new Rect(
+ -mResizeHandleThickness,
+ mTaskHeight,
+ mTaskWidth + mResizeHandleThickness,
+ mTaskHeight + mResizeHandleThickness);
touchRegion.union(bottomInputBounds);
// Set up touch areas in each corner.
int cornerRadius = mCornerSize / 2;
mLeftTopCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ -cornerRadius,
+ cornerRadius,
+ cornerRadius);
touchRegion.union(mLeftTopCornerBounds);
mRightTopCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ -cornerRadius,
+ mTaskWidth + cornerRadius,
+ cornerRadius);
touchRegion.union(mRightTopCornerBounds);
mLeftBottomCornerBounds = new Rect(
- mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ -cornerRadius,
+ mTaskHeight - cornerRadius,
+ cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mLeftBottomCornerBounds);
mRightBottomCornerBounds = new Rect(
- mWidth - mResizeHandleThickness - cornerRadius,
- mHeight - mResizeHandleThickness - cornerRadius,
- mWidth - mResizeHandleThickness + cornerRadius,
- mHeight - mResizeHandleThickness + cornerRadius
- );
+ mTaskWidth - cornerRadius,
+ mTaskHeight - cornerRadius,
+ mTaskWidth + cornerRadius,
+ mTaskHeight + cornerRadius);
touchRegion.union(mRightBottomCornerBounds);
try {
@@ -358,16 +364,16 @@ class DragResizeInputListener implements AutoCloseable {
@TaskPositioner.CtrlType
private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
- if (x < mResizeHandleThickness) {
+ if (x < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
}
- if (x > mWidth - mResizeHandleThickness) {
+ if (x > mTaskWidth) {
ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
}
- if (y < mResizeHandleThickness) {
+ if (y < 0) {
ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
}
- if (y > mHeight - mResizeHandleThickness) {
+ if (y > mTaskHeight) {
ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
}
return ctrlType;
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 4ebd09fdecee..19a31822aabb 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
@@ -34,6 +34,7 @@ import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowlessWindowManager;
+import android.window.SurfaceSyncGroup;
import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
@@ -98,7 +99,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
private final Binder mOwner = new Binder();
private final Rect mCaptionInsetsRect = new Rect();
- private final Rect mTaskSurfaceCrop = new Rect();
private final float[] mTmpColor = new float[3];
WindowDecoration(
@@ -193,13 +193,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
if (params.mLayoutResId != 0) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
- .inflate(params.mLayoutResId, null);
+ .inflate(params.mLayoutResId, null);
}
}
if (outResult.mRootView == null) {
outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
- .inflate(params.mLayoutResId , null);
+ .inflate(params.mLayoutResId, null);
}
// DecorationContainerSurface
@@ -218,21 +218,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
final Resources resources = mDecorWindowContext.getResources();
- outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
- outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
- outResult.mWidth = taskBounds.width()
- + loadDimensionPixelSize(resources, params.mOutsetRightId)
- - outResult.mDecorContainerOffsetX;
- outResult.mHeight = taskBounds.height()
- + loadDimensionPixelSize(resources, params.mOutsetBottomId)
- - outResult.mDecorContainerOffsetY;
- startT.setPosition(
- mDecorationContainerSurface,
- outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
- .setWindowCrop(mDecorationContainerSurface,
- outResult.mWidth, outResult.mHeight)
+ outResult.mWidth = taskBounds.width();
+ outResult.mHeight = taskBounds.height();
+ startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
.show(mDecorationContainerSurface);
+ // TODO(b/270202228): This surface can be removed. Instead, use
+ // |mDecorationContainerSurface| to set the background now that it no longer has outsets
+ // and its crop is set to the task bounds.
// TaskBackgroundSurface
if (mTaskBackgroundSurface == null) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
@@ -250,8 +243,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
- startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(),
- taskBounds.height())
+ startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
.setShadowRadius(mTaskBackgroundSurface, shadowRadius)
.setColor(mTaskBackgroundSurface, mTmpColor)
.show(mTaskBackgroundSurface);
@@ -269,11 +261,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
final int captionWidth = taskBounds.width();
- startT.setPosition(
- mCaptionContainerSurface,
- -outResult.mDecorContainerOffsetX + params.mCaptionX,
- -outResult.mDecorContainerOffsetY + params.mCaptionY)
- .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
+ startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
.show(mCaptionContainerSurface);
if (mCaptionWindowManager == null) {
@@ -314,14 +302,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
// Task surface itself
Point taskPosition = mTaskInfo.positionInParent;
- mTaskSurfaceCrop.set(
- outResult.mDecorContainerOffsetX,
- outResult.mDecorContainerOffsetY,
- outResult.mWidth + outResult.mDecorContainerOffsetX,
- outResult.mHeight + outResult.mDecorContainerOffsetY);
startT.show(mTaskSurface);
finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
- .setCrop(mTaskSurface, mTaskSurfaceCrop);
+ .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
}
/**
@@ -400,18 +383,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
/**
* Create a window associated with this WindowDecoration.
* Note that subclass must dispose of this when the task is hidden/closed.
- * @param layoutId layout to make the window from
- * @param t the transaction to apply
- * @param xPos x position of new window
- * @param yPos y position of new window
- * @param width width of new window
- * @param height height of new window
+ *
+ * @param layoutId layout to make the window from
+ * @param t the transaction to apply
+ * @param xPos x position of new window
+ * @param yPos y position of new window
+ * @param width width of new window
+ * @param height height of new window
* @param shadowRadius radius of the shadow of the new window
* @param cornerRadius radius of the corners of the new window
* @return the {@link AdditionalWindow} that was added.
*/
AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
- int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) {
+ SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
+ int cornerRadius) {
final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
SurfaceControl windowSurfaceControl = builder
.setName(namePrefix + " of Task=" + mTaskInfo.taskId)
@@ -435,49 +420,27 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
windowSurfaceControl, null /* hostInputToken */);
SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
.create(mDecorWindowContext, mDisplay, windowManager);
- viewHost.setView(v, lp);
+ ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
return new AdditionalWindow(windowSurfaceControl, viewHost,
mSurfaceControlTransactionSupplier);
}
- static class RelayoutParams{
+ static class RelayoutParams {
RunningTaskInfo mRunningTaskInfo;
int mLayoutResId;
int mCaptionHeightId;
int mCaptionWidthId;
int mShadowRadiusId;
- int mOutsetTopId;
- int mOutsetBottomId;
- int mOutsetLeftId;
- int mOutsetRightId;
-
int mCaptionX;
int mCaptionY;
- void setOutsets(int leftId, int topId, int rightId, int bottomId) {
- mOutsetLeftId = leftId;
- mOutsetTopId = topId;
- mOutsetRightId = rightId;
- mOutsetBottomId = bottomId;
- }
-
- void setCaptionPosition(int left, int top) {
- mCaptionX = left;
- mCaptionY = top;
- }
-
void reset() {
mLayoutResId = Resources.ID_NULL;
mCaptionHeightId = Resources.ID_NULL;
mCaptionWidthId = Resources.ID_NULL;
mShadowRadiusId = Resources.ID_NULL;
- mOutsetTopId = Resources.ID_NULL;
- mOutsetBottomId = Resources.ID_NULL;
- mOutsetLeftId = Resources.ID_NULL;
- mOutsetRightId = Resources.ID_NULL;
-
mCaptionX = 0;
mCaptionY = 0;
}
@@ -487,14 +450,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
int mWidth;
int mHeight;
T mRootView;
- int mDecorContainerOffsetX;
- int mDecorContainerOffsetY;
void reset() {
mWidth = 0;
mHeight = 0;
- mDecorContainerOffsetX = 0;
- mDecorContainerOffsetY = 0;
mRootView = null;
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index 67ca9a1a17f7..b6d92814ad43 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -29,6 +29,7 @@
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
<option name="teardown-command" value="settings delete system show_touches" />
<option name="teardown-command" value="settings delete system pointer_location" />
+ <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index e986ee127708..c416ad011c4e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -137,7 +137,7 @@ fun FlickerTest.splitAppLayerBoundsBecomesVisible(
portraitPosTop: Boolean
) {
assertLayers {
- this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
.then()
.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
.then()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index cbbb29199d75..b8f615a1855f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.activityembedding;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
@@ -82,10 +83,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
@Test
public void testStartAnimation_containsNonActivityEmbeddingChange() {
+ final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */);
+ final TransitionInfo.Change embeddedOpen = createEmbeddedChange(
+ EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS);
+ nonEmbeddedOpen.setMode(TRANSIT_OPEN);
final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
- .addChange(createEmbeddedChange(
- EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS))
- .addChange(createChange(0 /* flags */))
+ .addChange(embeddedOpen)
+ .addChange(nonEmbeddedOpen)
.build();
// No-op because it contains non-embedded change.
@@ -95,6 +99,22 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation
verifyNoMoreInteractions(mStartTransaction);
verifyNoMoreInteractions(mFinishTransaction);
verifyNoMoreInteractions(mFinishCallback);
+
+ final TransitionInfo.Change nonEmbeddedClose = createChange(0 /* flags */);
+ nonEmbeddedClose.setMode(TRANSIT_CLOSE);
+ nonEmbeddedClose.setEndAbsBounds(TASK_BOUNDS);
+ final TransitionInfo.Change embeddedOpen2 = createEmbeddedChange(
+ EMBEDDED_RIGHT_BOUNDS, EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS);
+ final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_OPEN, 0)
+ .addChange(embeddedOpen)
+ .addChange(embeddedOpen2)
+ .addChange(nonEmbeddedClose)
+ .build();
+ // Ok to animate because nonEmbeddedClose is occluded by embeddedOpen and embeddedOpen2.
+ assertTrue(mController.startAnimation(mTransition, info2, mStartTransaction,
+ mFinishTransaction, mFinishCallback));
+ // The non-embedded change is dropped to avoid affecting embedded animation.
+ assertFalse(info2.getChanges().contains(nonEmbeddedClose));
}
@Test
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 806bffebd4cb..d95c7a488ea1 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
@@ -166,6 +166,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 0);
mController.setTriggerBack(true);
+ }
+
+ private void releaseBackGesture() {
doMotionEvent(MotionEvent.ACTION_UP, 0);
}
@@ -201,6 +204,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
simulateRemoteAnimationStart(type);
+ mShellExecutor.flushAll();
+ releaseBackGesture();
simulateRemoteAnimationFinished();
mShellExecutor.flushAll();
@@ -252,6 +257,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false);
triggerBackGesture();
+ releaseBackGesture();
verify(mAppCallback, times(1)).onBackInvoked();
@@ -269,6 +275,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ releaseBackGesture();
+
// Check that back invocation is dispatched.
verify(mAnimatorCallback).onBackInvoked();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
@@ -308,6 +316,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
+ mShellExecutor.flushAll();
+
+ releaseBackGesture();
// Simulate transition timeout.
mShellExecutor.flushAll();
@@ -369,6 +380,9 @@ public class BackAnimationControllerTest extends ShellTestCase {
simulateRemoteAnimationStart(type);
mShellExecutor.flushAll();
+ releaseBackGesture();
+ mShellExecutor.flushAll();
+
assertTrue("Navigation Done callback not called for "
+ BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
assertTrue("TriggerBack should have been true", result.mTriggerBack);
@@ -395,6 +409,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
.setOnBackNavigationDone(new RemoteCallback(result)));
triggerBackGesture();
mShellExecutor.flushAll();
+ releaseBackGesture();
+ mShellExecutor.flushAll();
assertTrue("Navigation Done callback not called for "
+ BackNavigationInfo.typeToString(type), result.mBackNavigationDone);
@@ -458,9 +474,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void doMotionEvent(int actionDown, int coordinate) {
mController.onMotionEvent(
- coordinate, coordinate,
- actionDown,
- BackEvent.EDGE_LEFT);
+ /* touchX */ coordinate,
+ /* touchY */ coordinate,
+ /* velocityX = */ 0,
+ /* velocityY = */ 0,
+ /* keyAction */ actionDown,
+ /* swipeEdge */ BackEvent.EDGE_LEFT);
}
private void simulateRemoteAnimationStart(int type) throws RemoteException {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 3608474bd90e..874ef80c29f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.back;
-import static android.window.BackEvent.EDGE_LEFT;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -48,12 +46,21 @@ public class BackProgressAnimatorTest {
private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
private Handler mMainThreadHandler;
+ private BackMotionEvent backMotionEventFrom(float touchX, float progress) {
+ return new BackMotionEvent(
+ /* touchX = */ touchX,
+ /* touchY = */ 0,
+ /* progress = */ progress,
+ /* velocityX = */ 0,
+ /* velocityY = */ 0,
+ /* swipeEdge = */ BackEvent.EDGE_LEFT,
+ /* departingAnimationTarget = */ null);
+ }
+
@Before
public void setUp() throws Exception {
mMainThreadHandler = new Handler(Looper.getMainLooper());
- final BackMotionEvent backEvent = new BackMotionEvent(
- 0, 0,
- 0, EDGE_LEFT, null);
+ final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
() -> {
mProgressAnimator = new BackProgressAnimator();
@@ -63,9 +70,7 @@ public class BackProgressAnimatorTest {
@Test
public void testBackProgressed() throws InterruptedException {
- final BackMotionEvent backEvent = new BackMotionEvent(
- 100, 0,
- mTargetProgress, EDGE_LEFT, null);
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
mMainThreadHandler.post(
() -> mProgressAnimator.onBackProgressed(backEvent));
@@ -78,9 +83,7 @@ public class BackProgressAnimatorTest {
@Test
public void testBackCancelled() throws InterruptedException {
// Give the animator some progress.
- final BackMotionEvent backEvent = new BackMotionEvent(
- 100, 0,
- mTargetProgress, EDGE_LEFT, null);
+ final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
mMainThreadHandler.post(
() -> mProgressAnimator.onBackProgressed(backEvent));
mTargetProgressCalled.await(1, TimeUnit.SECONDS);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
index ba9c159bad28..d62e6601723a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java
@@ -47,43 +47,45 @@ public class TouchTrackerTest {
public void generatesProgress_leftEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
float touchX = 10;
+ float velocityX = 0;
+ float velocityY = 0;
// Pre-commit
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX += 100;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Cancel
touchX -= 10;
mTouchTracker.setTriggerBack(false);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX -= 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX += 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX += 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX += 10;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
}
@@ -91,43 +93,45 @@ public class TouchTrackerTest {
public void generatesProgress_rightEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
+ float velocityX = 0f;
+ float velocityY = 0f;
// Pre-commit
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX -= 100;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Cancel
touchX += 10;
mTouchTracker.setTriggerBack(false);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX += 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX -= 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX -= 10;
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX -= 10;
mTouchTracker.setTriggerBack(true);
- mTouchTracker.update(touchX, 0);
+ mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index 15bb10ed4f2b..842c699fa42d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -262,7 +262,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
DisplayLayout layout = new DisplayLayout(info,
mContext.getResources(), true, true);
mPipDisplayLayoutState.setDisplayLayout(layout);
- mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
+ doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController)
+ .takeOneShotEnterAnimationType();
mPipTaskOrganizer.setSurfaceControlTransactionFactory(
MockSurfaceControlHelper::createMockSurfaceControlTransaction);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
index 390c830069eb..425bbf056b85 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
@@ -18,7 +18,6 @@ package com.android.wm.shell.pip.phone;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
@@ -76,7 +75,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
@Mock private Resources mResources;
private PipDisplayLayoutState mPipDisplayLayoutState;
- private PipSizeSpecHandler mPipSizeSpecHandler;
+ private TestPipSizeSpecHandler mPipSizeSpecHandler;
/**
* Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -84,8 +83,6 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
private static void setUpStaticSystemPropertiesSession() {
sStaticMockitoSession = mockitoSession()
.mockStatic(SystemProperties.class).startMocking();
- // make sure the feature flag is on
- when(SystemProperties.getBoolean(anyString(), anyBoolean())).thenReturn(true);
when(SystemProperties.get(anyString(), anyString())).thenAnswer(invocation -> {
String property = invocation.getArgument(0);
if (property.equals("com.android.wm.shell.pip.phone.def_percentage")) {
@@ -161,7 +158,7 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
mPipDisplayLayoutState.setDisplayLayout(displayLayout);
setUpStaticSystemPropertiesSession();
- mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+ mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
// no overridden min edge size by default
mPipSizeSpecHandler.setOverrideMinSize(null);
@@ -214,4 +211,16 @@ public class PipSizeSpecHandlerTest extends ShellTestCase {
Assert.assertEquals(expectedSize, actualSize);
}
+
+ static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
+
+ TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
+ super(context, displayLayoutState);
+ }
+
+ @Override
+ boolean supportsPipSizeLargeScreen() {
+ return true;
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index eda6fdc4dbd4..e6219d1aa792 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -113,6 +113,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SurfaceControl mRootLeash;
+ private SurfaceControl mDividerLeash;
private ActivityManager.RunningTaskInfo mRootTask;
private StageCoordinator mStageCoordinator;
private Transitions mTransitions;
@@ -129,12 +130,14 @@ public class StageCoordinatorTests extends ShellTestCase {
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
mMainExecutor, Optional.empty()));
+ mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLandscape()).thenReturn(false);
when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true);
+ when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
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 dfa3c1010eed..38a519af934b 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
@@ -49,6 +49,7 @@ import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager.LayoutParams;
+import android.window.SurfaceSyncGroup;
import android.window.TaskConstants;
import android.window.WindowContainerTransaction;
@@ -100,6 +101,8 @@ public class WindowDecorationTests extends ShellTestCase {
private TestView mMockView;
@Mock
private WindowContainerTransaction mMockWindowContainerTransaction;
+ @Mock
+ private SurfaceSyncGroup mMockSurfaceSyncGroup;
private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions =
new ArrayList<>();
@@ -159,14 +162,8 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(false)
.build();
taskInfo.isFocused = false;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -213,14 +210,8 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -229,8 +220,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(decorContainerSurfaceBuilder).setParent(taskSurface);
verify(decorContainerSurfaceBuilder).setContainerLayer();
verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true);
- verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40);
- verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220);
+ verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100);
verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
verify(taskBackgroundSurfaceBuilder).setEffectLayer();
@@ -244,7 +234,6 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -268,12 +257,12 @@ public class WindowDecorationTests extends ShellTestCase {
verify(mMockSurfaceControlFinishT)
.setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
verify(mMockSurfaceControlFinishT)
- .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+ .setWindowCrop(taskSurface, 300, 100);
verify(mMockSurfaceControlStartT)
.show(taskSurface);
- assertEquals(380, mRelayoutResult.mWidth);
- assertEquals(220, mRelayoutResult.mHeight);
+ assertEquals(300, mRelayoutResult.mWidth);
+ assertEquals(100, mRelayoutResult.mHeight);
}
@Test
@@ -309,14 +298,8 @@ public class WindowDecorationTests extends ShellTestCase {
.setVisible(true)
.build();
taskInfo.isFocused = true;
- // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
- // 64px.
+ // Density is 2. Shadow radius is 10px. Caption height is 64px.
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -419,11 +402,6 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
windowDecor.relayout(taskInfo);
@@ -438,7 +416,7 @@ public class WindowDecorationTests extends ShellTestCase {
verify(additionalWindowSurfaceBuilder).setContainerLayer();
verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
verify(additionalWindowSurfaceBuilder).build();
- verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+ verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0);
final int width = WindowDecoration.loadDimensionPixelSize(
mContext.getResources(), mCaptionMenuWidthId);
final int height = WindowDecoration.loadDimensionPixelSize(
@@ -496,11 +474,6 @@ public class WindowDecorationTests extends ShellTestCase {
.build();
taskInfo.isFocused = true;
taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
- mRelayoutParams.setOutsets(
- R.dimen.test_window_decor_left_outset,
- R.dimen.test_window_decor_top_outset,
- R.dimen.test_window_decor_right_outset,
- R.dimen.test_window_decor_bottom_outset);
final SurfaceControl taskSurface = mock(SurfaceControl.class);
final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
@@ -508,7 +481,6 @@ public class WindowDecorationTests extends ShellTestCase {
verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
verify(captionContainerSurfaceBuilder).setContainerLayer();
- verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
// Width of the captionContainerSurface should match the width of TASK_BOUNDS
verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
verify(mMockSurfaceControlStartT).show(captionContainerSurface);
@@ -584,9 +556,7 @@ public class WindowDecorationTests extends ShellTestCase {
String name = "Test Window";
WindowDecoration.AdditionalWindow additionalWindow =
addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name,
- mMockSurfaceControlAddWindowT,
- x - mRelayoutResult.mDecorContainerOffsetX,
- y - mRelayoutResult.mDecorContainerOffsetY,
+ mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
width, height, shadowRadius, cornerRadius);
return additionalWindow;
}
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 0b58406516e3..924fbd659824 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -51,6 +51,9 @@
#include "include/gpu/GrDirectContext.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include "pipeline/skia/FunctorDrawable.h"
+#ifdef __ANDROID__
+#include "renderthread/CanvasContext.h"
+#endif
namespace android {
namespace uirenderer {
@@ -489,7 +492,19 @@ struct DrawPoints final : Op {
size_t count;
SkPaint paint;
void draw(SkCanvas* c, const SkMatrix&) const {
- c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ if (paint.isAntiAlias()) {
+ c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ } else {
+ c->save();
+#ifdef __ANDROID__
+ auto pixelSnap = renderthread::CanvasContext::getActiveContext()->getPixelSnapMatrix();
+ auto transform = c->getLocalToDevice();
+ transform.postConcat(pixelSnap);
+ c->setMatrix(transform);
+#endif
+ c->drawPoints(mode, count, pod<SkPoint>(this), paint);
+ c->restore();
+ }
}
};
struct DrawVertices final : Op {
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index d08bc5c583c2..8049dc946c9e 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -29,9 +29,10 @@
namespace android {
-AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed)
- : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed) {
- mTimeToShowNextSnapshot = ms2ns(mSkAnimatedImage->currentFrameDuration());
+AnimatedImageDrawable::AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
+ SkEncodedImageFormat format)
+ : mSkAnimatedImage(std::move(animatedImage)), mBytesUsed(bytesUsed), mFormat(format) {
+ mTimeToShowNextSnapshot = ms2ns(currentFrameDuration());
setStagingBounds(mSkAnimatedImage->getBounds());
}
@@ -92,7 +93,7 @@ bool AnimatedImageDrawable::isDirty(nsecs_t* outDelay) {
// directly from mSkAnimatedImage.
lock.unlock();
std::unique_lock imageLock{mImageLock};
- *outDelay = ms2ns(mSkAnimatedImage->currentFrameDuration());
+ *outDelay = ms2ns(currentFrameDuration());
return true;
} else {
// The next snapshot has not yet been decoded, but we've already passed
@@ -109,7 +110,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::decodeNextFrame() {
Snapshot snap;
{
std::unique_lock lock{mImageLock};
- snap.mDurationMS = mSkAnimatedImage->decodeNextFrame();
+ snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
}
@@ -123,7 +124,7 @@ AnimatedImageDrawable::Snapshot AnimatedImageDrawable::reset() {
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
- snap.mDurationMS = mSkAnimatedImage->currentFrameDuration();
+ snap.mDurationMS = currentFrameDuration();
}
return snap;
@@ -274,7 +275,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
{
std::unique_lock lock{mImageLock};
mSkAnimatedImage->reset();
- durationMS = mSkAnimatedImage->currentFrameDuration();
+ durationMS = currentFrameDuration();
}
{
std::unique_lock lock{mSwapLock};
@@ -306,7 +307,7 @@ int AnimatedImageDrawable::drawStaging(SkCanvas* canvas) {
{
std::unique_lock lock{mImageLock};
if (update) {
- durationMS = mSkAnimatedImage->decodeNextFrame();
+ durationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
}
canvas->drawDrawable(mSkAnimatedImage.get());
@@ -336,4 +337,20 @@ SkRect AnimatedImageDrawable::onGetBounds() {
return SkRectMakeLargest();
}
+int AnimatedImageDrawable::adjustFrameDuration(int durationMs) {
+ if (durationMs == SkAnimatedImage::kFinished) {
+ return SkAnimatedImage::kFinished;
+ }
+
+ if (mFormat == SkEncodedImageFormat::kGIF) {
+ // Match Chrome & Firefox behavior that gifs with a duration <= 10ms is bumped to 100ms
+ return durationMs <= 10 ? 100 : durationMs;
+ }
+ return durationMs;
+}
+
+int AnimatedImageDrawable::currentFrameDuration() {
+ return adjustFrameDuration(mSkAnimatedImage->currentFrameDuration());
+}
+
} // namespace android
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 8ca3c7e125f1..1e965abc82b5 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -16,16 +16,16 @@
#pragma once
-#include <cutils/compiler.h>
-#include <utils/Macros.h>
-#include <utils/RefBase.h>
-#include <utils/Timers.h>
-
#include <SkAnimatedImage.h>
#include <SkCanvas.h>
#include <SkColorFilter.h>
#include <SkDrawable.h>
+#include <SkEncodedImageFormat.h>
#include <SkPicture.h>
+#include <cutils/compiler.h>
+#include <utils/Macros.h>
+#include <utils/RefBase.h>
+#include <utils/Timers.h>
#include <future>
#include <mutex>
@@ -48,7 +48,8 @@ class AnimatedImageDrawable : public SkDrawable {
public:
// bytesUsed includes the approximate sizes of the SkAnimatedImage and the SkPictures in the
// Snapshots.
- AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed);
+ AnimatedImageDrawable(sk_sp<SkAnimatedImage> animatedImage, size_t bytesUsed,
+ SkEncodedImageFormat format);
/**
* This updates the internal time and returns true if the image needs
@@ -115,6 +116,7 @@ protected:
private:
sk_sp<SkAnimatedImage> mSkAnimatedImage;
const size_t mBytesUsed;
+ const SkEncodedImageFormat mFormat;
bool mRunning = false;
bool mStarting = false;
@@ -157,6 +159,9 @@ private:
Properties mProperties;
std::unique_ptr<OnAnimationEndListener> mEndListener;
+
+ int adjustFrameDuration(int);
+ int currentFrameDuration();
};
} // namespace android
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index 373e893b9a25..a7f5aa83e624 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -97,7 +97,7 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
bytesUsed += picture->approximateBytesUsed();
}
-
+ SkEncodedImageFormat format = imageDecoder->mCodec->getEncodedFormat();
sk_sp<SkAnimatedImage> animatedImg = SkAnimatedImage::Make(std::move(imageDecoder->mCodec),
info, subset,
std::move(picture));
@@ -108,8 +108,8 @@ static jlong AnimatedImageDrawable_nCreate(JNIEnv* env, jobject /*clazz*/,
bytesUsed += sizeof(animatedImg.get());
- sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(std::move(animatedImg),
- bytesUsed));
+ sk_sp<AnimatedImageDrawable> drawable(
+ new AnimatedImageDrawable(std::move(animatedImg), bytesUsed, format));
return reinterpret_cast<jlong>(drawable.release());
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index cc987bcd8f0e..c4d3f5cedfa8 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -53,6 +53,8 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() {
}
MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
+ bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE);
+
// In case the surface was destroyed (e.g. a previous trimMemory call) we
// need to recreate it here.
if (mHardwareBuffer) {
@@ -65,6 +67,37 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() {
if (!mEglManager.makeCurrent(mEglSurface, &error)) {
return MakeCurrentResult::AlreadyCurrent;
}
+
+ // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations
+ // disagree on the draw/read buffer state if the default framebuffer transitions from a surface
+ // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic.
+ // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534.
+ // The discussion was not resolved with a clear consensus
+ if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) {
+ GLint curReadFB = 0;
+ GLint curDrawFB = 0;
+ glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB);
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB);
+
+ GLint buffer = GL_NONE;
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glGetIntegerv(GL_DRAW_BUFFER0, &buffer);
+ if (buffer == GL_NONE) {
+ const GLenum drawBuffer = GL_BACK;
+ glDrawBuffers(1, &drawBuffer);
+ }
+
+ glGetIntegerv(GL_READ_BUFFER, &buffer);
+ if (buffer == GL_NONE) {
+ glReadBuffer(GL_BACK);
+ }
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB);
+
+ GL_CHECKPOINT(LOW);
+ }
+
return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded;
}
@@ -104,7 +137,8 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
- SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
+ SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
+ kUnknown_SkPixelGeometry);
SkASSERT(mRenderThread.getGrContext() != nullptr);
sk_sp<SkSurface> surface;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 940d6bfdb83c..f0461bef170c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -53,6 +53,14 @@ public:
bool isSurfaceReady() override;
bool isContextReady() override;
+ const SkM44& getPixelSnapMatrix() const override {
+ // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+ // desired fragment
+ static const SkScalar kOffset = 0.063f;
+ static const SkM44 sSnapMatrix = SkM44::Translate(kOffset, kOffset);
+ return sSnapMatrix;
+ }
+
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
protected:
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 1f929685b62c..b020e966e05a 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -28,7 +28,6 @@
#include <SkMultiPictureDocument.h>
#include <SkOverdrawCanvas.h>
#include <SkOverdrawColorFilter.h>
-#include <SkPaintFilterCanvas.h>
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <SkRect.h>
@@ -450,23 +449,6 @@ void SkiaPipeline::endCapture(SkSurface* surface) {
}
}
-class ForceDitherCanvas : public SkPaintFilterCanvas {
-public:
- ForceDitherCanvas(SkCanvas* canvas) : SkPaintFilterCanvas(canvas) {}
-
-protected:
- bool onFilter(SkPaint& paint) const override {
- paint.setDither(true);
- return true;
- }
-
- void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override {
- // We unroll the drawable using "this" canvas, so that draw calls contained inside will
- // get dithering applied
- drawable->draw(this, matrix);
- }
-};
-
void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
@@ -521,12 +503,6 @@ void SkiaPipeline::renderFrameImpl(const SkRect& clip,
canvas->clear(SK_ColorTRANSPARENT);
}
- std::optional<ForceDitherCanvas> forceDitherCanvas;
- if (shouldForceDither()) {
- forceDitherCanvas.emplace(canvas);
- canvas = &forceDitherCanvas.value();
- }
-
if (1 == nodes.size()) {
if (!nodes[0]->nothingToDraw()) {
RenderNodeDrawable root(nodes[0].get(), canvas);
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.h b/libs/hwui/pipeline/skia/SkiaPipeline.h
index 0763b06b53ef..befee8989383 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.h
@@ -98,8 +98,6 @@ protected:
bool isCapturingSkp() const { return mCaptureMode != CaptureMode::None; }
- virtual bool shouldForceDither() const { return mColorMode != ColorMode::Default; }
-
private:
void renderFrameImpl(const SkRect& clip,
const std::vector<sp<RenderNode>>& nodes, bool opaque,
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 6f1b99b95bbd..86096d5bd01c 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -203,11 +203,6 @@ sk_sp<Bitmap> SkiaVulkanPipeline::allocateHardwareBitmap(renderthread::RenderThr
return nullptr;
}
-bool SkiaVulkanPipeline::shouldForceDither() const {
- if (mVkSurface && mVkSurface->isBeyond8Bit()) return false;
- return SkiaPipeline::shouldForceDither();
-}
-
void SkiaVulkanPipeline::onContextDestroyed() {
if (mVkSurface) {
vulkanManager().destroySurface(mVkSurface);
@@ -215,6 +210,10 @@ void SkiaVulkanPipeline::onContextDestroyed() {
}
}
+const SkM44& SkiaVulkanPipeline::getPixelSnapMatrix() const {
+ return mVkSurface->getPixelSnapMatrix();
+}
+
} /* namespace skiapipeline */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 0713e93bccde..284cde537ec0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -53,8 +53,8 @@ public:
void onStop() override;
bool isSurfaceReady() override;
bool isContextReady() override;
- bool supportsExtendedRangeHdr() const override { return true; }
void setTargetSdrHdrRatio(float ratio) override;
+ const SkM44& getPixelSnapMatrix() const override;
static void invokeFunctor(const renderthread::RenderThread& thread, Functor* functor);
static sk_sp<Bitmap> allocateHardwareBitmap(renderthread::RenderThread& thread,
@@ -63,8 +63,6 @@ public:
protected:
void onContextDestroyed() override;
- bool shouldForceDither() const override;
-
private:
renderthread::VulkanManager& vulkanManager();
renderthread::VulkanSurface* mVkSurface = nullptr;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 23611efccd73..7e9d44fbdbd1 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -117,12 +117,8 @@ void CacheManager::trimMemory(TrimLevel mode) {
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(/*syncCpu=*/true);
- if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
- mode = TrimLevel::COMPLETE;
- }
-
switch (mode) {
- case TrimLevel::COMPLETE:
+ case TrimLevel::BACKGROUND:
mGrContext->freeGpuResources();
SkGraphics::PurgeAllCaches();
mRenderThread.destroyRenderingContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6b2c99534a4c..f60c1f3c6ad8 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -236,7 +236,6 @@ void CanvasContext::setupPipelineSurface() {
if (mNativeSurface && !mNativeSurface->didSetExtraBuffers()) {
setBufferCount(mNativeSurface->getNativeWindow());
-
}
mFrameNumber = 0;
@@ -301,10 +300,6 @@ void CanvasContext::setOpaque(bool opaque) {
float CanvasContext::setColorMode(ColorMode mode) {
if (mode != mColorMode) {
- const bool isHdr = mode == ColorMode::Hdr || mode == ColorMode::Hdr10;
- if (isHdr && !mRenderPipeline->supportsExtendedRangeHdr()) {
- mode = ColorMode::WideColorGamut;
- }
mColorMode = mode;
mRenderPipeline->setSurfaceColorProperties(mode);
setupPipelineSurface();
@@ -871,6 +866,10 @@ SkISize CanvasContext::getNextFrameSize() const {
return size;
}
+const SkM44& CanvasContext::getPixelSnapMatrix() const {
+ return mRenderPipeline->getPixelSnapMatrix();
+}
+
void CanvasContext::prepareAndDraw(RenderNode* node) {
ATRACE_CALL();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3f2533959c20..d7215de92375 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -200,6 +200,9 @@ public:
SkISize getNextFrameSize() const;
+ // Returns the matrix to use to nudge non-AA'd points/lines towards the fragment center
+ const SkM44& getPixelSnapMatrix() const;
+
// Called when SurfaceStats are available.
static void onSurfaceStatsAvailable(void* context, int32_t surfaceControlId,
ASurfaceControlStats* stats);
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index 4fb114b71bf5..94f35fd9eaf2 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -423,6 +423,7 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
EGLint attribs[] = {EGL_NONE, EGL_NONE, EGL_NONE};
EGLConfig config = mEglConfig;
+ bool overrideWindowDataSpaceForHdr = false;
if (colorMode == ColorMode::A8) {
// A8 doesn't use a color space
if (!mEglConfigA8) {
@@ -450,12 +451,13 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
case ColorMode::Default:
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
break;
- // Extended Range HDR requires being able to manipulate the dataspace in ways
- // we cannot easily do while going through EGLSurface. Given this requires
- // composer3 support, just treat HDR as equivalent to wide color gamut if
- // the GLES path is still being hit
+ // We don't have an EGL colorspace for extended range P3 that's used for HDR
+ // So override it after configuring the EGL context
case ColorMode::Hdr:
case ColorMode::Hdr10:
+ overrideWindowDataSpaceForHdr = true;
+ attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
+ break;
case ColorMode::WideColorGamut: {
skcms_Matrix3x3 colorGamut;
LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&colorGamut),
@@ -491,6 +493,16 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
(void*)window, eglErrorString());
}
+ if (overrideWindowDataSpaceForHdr) {
+ // This relies on knowing that EGL will not re-set the dataspace after the call to
+ // eglCreateWindowSurface. Since the handling of the colorspace extension is largely
+ // implemented in libEGL in the platform, we can safely assume this is the case
+ int32_t err = ANativeWindow_setBuffersDataSpace(
+ window,
+ static_cast<android_dataspace>(STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_EXTENDED));
+ LOG_ALWAYS_FATAL_IF(err, "Failed to ANativeWindow_setBuffersDataSpace %d", err);
+ }
+
return surface;
}
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index c68fcdfc76f2..6c2cb9d71c55 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -95,8 +95,8 @@ public:
virtual void setPictureCapturedCallback(
const std::function<void(sk_sp<SkPicture>&&)>& callback) = 0;
- virtual bool supportsExtendedRangeHdr() const { return false; }
virtual void setTargetSdrHdrRatio(float ratio) = 0;
+ virtual const SkM44& getPixelSnapMatrix() const = 0;
virtual ~IRenderPipeline() {}
};
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 718d4a16d5c8..96bfc1061d4e 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -42,6 +42,36 @@ namespace android {
namespace uirenderer {
namespace renderthread {
+static std::array<std::string_view, 18> sEnableExtensions{
+ VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
+ VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
+ VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
+ VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
+ VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE1_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE2_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE3_EXTENSION_NAME,
+ VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME,
+ VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
+ VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
+ VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
+ VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
+ VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+};
+
+static bool shouldEnableExtension(const std::string_view& extension) {
+ for (const auto& it : sEnableExtensions) {
+ if (it == extension) {
+ return true;
+ }
+ }
+ return false;
+}
+
static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) {
// All Vulkan structs that could be part of the features chain will start with the
// structure type followed by the pNext pointer. We cast to the CommonVulkanHeader
@@ -139,6 +169,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
bool hasKHRSurfaceExtension = false;
bool hasKHRAndroidSurfaceExtension = false;
for (const VkExtensionProperties& extension : mInstanceExtensionsOwner) {
+ if (!shouldEnableExtension(extension.extensionName)) {
+ ALOGV("Not enabling instance extension %s", extension.extensionName);
+ continue;
+ }
+ ALOGV("Enabling instance extension %s", extension.extensionName);
mInstanceExtensions.push_back(extension.extensionName);
if (!strcmp(extension.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) {
hasKHRSurfaceExtension = true;
@@ -219,6 +254,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err);
bool hasKHRSwapchainExtension = false;
for (const VkExtensionProperties& extension : mDeviceExtensionsOwner) {
+ if (!shouldEnableExtension(extension.extensionName)) {
+ ALOGV("Not enabling device extension %s", extension.extensionName);
+ continue;
+ }
+ ALOGV("Enabling device extension %s", extension.extensionName);
mDeviceExtensions.push_back(extension.extensionName);
if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) {
hasKHRSwapchainExtension = true;
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index ae4f0572576e..10f456745147 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -63,6 +63,18 @@ static SkMatrix GetPreTransformMatrix(SkISize windowSize, int transform) {
return SkMatrix::I();
}
+static SkM44 GetPixelSnapMatrix(SkISize windowSize, int transform) {
+ // Small (~1/16th) nudge to ensure that pixel-aligned non-AA'd draws fill the
+ // desired fragment
+ static const SkScalar kOffset = 0.063f;
+ SkMatrix preRotation = GetPreTransformMatrix(windowSize, transform);
+ SkMatrix invert;
+ LOG_ALWAYS_FATAL_IF(!preRotation.invert(&invert));
+ return SkM44::Translate(kOffset, kOffset)
+ .postConcat(SkM44(preRotation))
+ .preConcat(SkM44(invert));
+}
+
static bool ConnectAndSetWindowDefaults(ANativeWindow* window) {
ATRACE_CALL();
@@ -178,6 +190,8 @@ bool VulkanSurface::InitializeWindowInfoStruct(ANativeWindow* window, ColorMode
outWindowInfo->preTransform =
GetPreTransformMatrix(outWindowInfo->size, outWindowInfo->transform);
+ outWindowInfo->pixelSnapMatrix =
+ GetPixelSnapMatrix(outWindowInfo->size, outWindowInfo->transform);
err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS, &query_value);
if (err != 0 || query_value < 0) {
@@ -413,6 +427,7 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
}
mWindowInfo.preTransform = GetPreTransformMatrix(mWindowInfo.size, mWindowInfo.transform);
+ mWindowInfo.pixelSnapMatrix = GetPixelSnapMatrix(mWindowInfo.size, mWindowInfo.transform);
}
uint32_t idx;
@@ -438,9 +453,15 @@ VulkanSurface::NativeBufferInfo* VulkanSurface::dequeueNativeBuffer() {
VulkanSurface::NativeBufferInfo* bufferInfo = &mNativeBuffers[idx];
if (bufferInfo->skSurface.get() == nullptr) {
+ SkSurfaceProps surfaceProps;
+ if (mWindowInfo.colorMode != ColorMode::Default) {
+ surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
+ surfaceProps.pixelGeometry());
+ }
bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
- kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, nullptr, /*from_window=*/true);
+ kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
+ /*from_window=*/true);
if (bufferInfo->skSurface.get() == nullptr) {
ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
@@ -530,16 +551,6 @@ void VulkanSurface::setColorSpace(sk_sp<SkColorSpace> colorSpace) {
}
}
-bool VulkanSurface::isBeyond8Bit() const {
- switch (mWindowInfo.bufferFormat) {
- case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- return true;
- default:
- return false;
- }
-}
-
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/VulkanSurface.h b/libs/hwui/renderthread/VulkanSurface.h
index 3b69b73bcab3..6f5280105e55 100644
--- a/libs/hwui/renderthread/VulkanSurface.h
+++ b/libs/hwui/renderthread/VulkanSurface.h
@@ -47,8 +47,7 @@ public:
const SkMatrix& getCurrentPreTransform() { return mWindowInfo.preTransform; }
void setColorSpace(sk_sp<SkColorSpace> colorSpace);
-
- bool isBeyond8Bit() const;
+ const SkM44& getPixelSnapMatrix() const { return mWindowInfo.pixelSnapMatrix; }
private:
/*
@@ -107,6 +106,7 @@ private:
SkISize actualSize;
// transform to be applied to the SkSurface to map the coordinates to the provided transform
SkMatrix preTransform;
+ SkM44 pixelSnapMatrix;
};
VulkanSurface(ANativeWindow* window, const WindowInfo& windowInfo, GrDirectContext* grContext);
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 1a3e54d54ee7..afa0a3271906 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -391,8 +391,26 @@ public final class SoundTriggerDetector {
* @hide
*/
@Override
- public void onError(int status) {
- Slog.d(TAG, "onError()" + status);
+ public void onRecognitionPaused() {
+ Slog.d(TAG, "onRecognitionPaused()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onRecognitionResumed() {
+ Slog.d(TAG, "onRecognitionResumed()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onPreempted() {
+ Slog.d(TAG, "onPreempted()");
mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
@@ -400,18 +418,27 @@ public final class SoundTriggerDetector {
* @hide
*/
@Override
- public void onRecognitionPaused() {
- Slog.d(TAG, "onRecognitionPaused()");
- mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE);
+ public void onModuleDied() {
+ Slog.d(TAG, "onModuleDied()");
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
/**
* @hide
*/
@Override
- public void onRecognitionResumed() {
- Slog.d(TAG, "onRecognitionResumed()");
- mHandler.sendEmptyMessage(MSG_DETECTION_RESUME);
+ public void onResumeFailed(int status) {
+ Slog.d(TAG, "onResumeFailed()" + status);
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public void onPauseFailed(int status) {
+ Slog.d(TAG, "onPauseFailed()" + status);
+ mHandler.sendEmptyMessage(MSG_DETECTION_ERROR);
}
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
new file mode 100644
index 000000000000..80bc5c07dd66
--- /dev/null
+++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java
@@ -0,0 +1,630 @@
+/**
+ * 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.media.soundtrigger;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.hardware.soundtrigger.ConversionUtil;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.soundtrigger_middleware.IAcknowledgeEvent;
+import android.media.soundtrigger_middleware.IInjectGlobalEvent;
+import android.media.soundtrigger_middleware.IInjectModelEvent;
+import android.media.soundtrigger_middleware.IInjectRecognitionEvent;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ISoundTriggerService;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes.
+ * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}.
+ * Only one instance of this class is valid at any given time, old instances will be delivered
+ * {@link GlobalCallback#onPreempted()}.
+ * @hide
+ */
+@TestApi
+public final class SoundTriggerInstrumentation {
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private IInjectGlobalEvent mInjectGlobalEvent = null;
+
+ @GuardedBy("mLock")
+ private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>();
+ @GuardedBy("mLock")
+ private IBinder mClientToken = null;
+
+ private final GlobalCallback mClientCallback;
+ private final Executor mGlobalCallbackExecutor;
+
+ /**
+ * Callback interface for un-sessioned events observed from the fake STHAL.
+ * Registered upon construction of {@link SoundTriggerInstrumentation}
+ * @hide
+ */
+ @TestApi
+ public interface GlobalCallback {
+ /**
+ * Called when the created {@link SoundTriggerInstrumentation} object is invalidated
+ * by another client creating an {@link SoundTriggerInstrumentation} to instrument the
+ * fake STHAL. Only one client may inject at a time.
+ * All sessions are invalidated, no further events will be received, and no
+ * injected events will be delivered.
+ */
+ default void onPreempted() {}
+ /**
+ * Called when the STHAL has been restarted by the framework, due to unexpected
+ * error conditions.
+ * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected.
+ */
+ default void onRestarted() {}
+ /**
+ * Called when the framework detaches from the fake HAL.
+ * This is not transmitted to real HALs, but it indicates that the
+ * framework has flushed its global state.
+ */
+ default void onFrameworkDetached() {}
+ /**
+ * Called when a client application attaches to the framework.
+ * This is not transmitted to real HALs, but it represents the state of
+ * the framework.
+ */
+ default void onClientAttached() {}
+ /**
+ * Called when a client application detaches from the framework.
+ * This is not transmitted to real HALs, but it represents the state of
+ * the framework.
+ */
+ default void onClientDetached() {}
+ /**
+ * Called when the fake HAL receives a model load from the framework.
+ * @param modelSession - A session which exposes additional injection
+ * functionality associated with the newly loaded
+ * model. See {@link ModelSession}.
+ */
+ void onModelLoaded(@NonNull ModelSession modelSession);
+ }
+
+ /**
+ * Callback for HAL events related to a loaded model. Register with
+ * {@link ModelSession#setModelCallback(Executor, ModelCallback)}
+ * Note, callbacks will not be delivered for events triggered by the injection.
+ * @hide
+ */
+ @TestApi
+ public interface ModelCallback {
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for was unloaded by the framework.
+ */
+ default void onModelUnloaded() {}
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for receives a set parameter call from the framework.
+ * @param param - Parameter being set.
+ * See {@link SoundTrigger.ModelParamTypes}
+ * @param value - Value the model parameter was set to.
+ */
+ default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {}
+ /**
+ * Called when the model associated with the {@link ModelSession} this callback
+ * was registered for receives a recognition start request.
+ * @param recognitionSession - A session which exposes additional injection
+ * functionality associated with the newly started
+ * recognition. See {@link RecognitionSession}
+ */
+ void onRecognitionStarted(@NonNull RecognitionSession recognitionSession);
+ }
+
+ /**
+ * Callback for HAL events related to a started recognition. Register with
+ * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)}
+ * Note, callbacks will not be delivered for events triggered by the injection.
+ * @hide
+ */
+ @TestApi
+ public interface RecognitionCallback {
+ /**
+ * Called when the recognition associated with the {@link RecognitionSession} this
+ * callback was registered for was stopped by the framework.
+ */
+ void onRecognitionStopped();
+ }
+
+ /**
+ * Session associated with a loaded model in the fake STHAL.
+ * Can be used to query details about the loaded model, register a callback for future
+ * model events, or trigger HAL events associated with a loaded model.
+ * This session is invalid once the model is unloaded, caused by a
+ * {@link ModelSession#triggerUnloadModel()},
+ * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is
+ * received.
+ * Further injections on an invalidated session will not be respected, and no future
+ * callbacks will be delivered.
+ * @hide
+ */
+ @TestApi
+ public class ModelSession {
+
+ /**
+ * Trigger the HAL to preemptively unload the model associated with this session.
+ * Typically occurs when a higher priority model is loaded which utilizes the same
+ * resources.
+ */
+ public void triggerUnloadModel() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ try {
+ mInjectModelEvent.triggerUnloadModel();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mModelSessionMap.remove(mInjectModelEvent.asBinder());
+ }
+ }
+
+ /**
+ * Get the {@link SoundTriggerManager.Model} associated with this session.
+ * @return - The model associated with this session.
+ */
+ public @NonNull SoundTriggerManager.Model getSoundModel() {
+ return mModel;
+ }
+
+ /**
+ * Get the list of {@link SoundTrigger.Keyphrase} associated with this session.
+ * @return - The keyphrases associated with this session.
+ */
+ public @NonNull List<SoundTrigger.Keyphrase> getPhrases() {
+ if (mPhrases == null) {
+ return new ArrayList<>();
+ } else {
+ return new ArrayList<>(Arrays.asList(mPhrases));
+ }
+ }
+
+ /**
+ * Get whether this model is of keyphrase type.
+ * @return - true if the model is a keyphrase model, false otherwise
+ */
+ public boolean isKeyphrase() {
+ return (mPhrases != null);
+ }
+
+ /**
+ * Registers the model callback associated with this session. Events associated
+ * with this model session will be reported via this callback.
+ * See {@link ModelCallback}
+ * @param executor - Executor which the callback is dispatched on
+ * @param callback - Model callback for reporting model session events.
+ */
+ public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull
+ ModelCallback callback) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mModelCallback = Objects.requireNonNull(callback);
+ mModelExecutor = Objects.requireNonNull(executor);
+ }
+ }
+
+ /**
+ * Clear the model callback associated with this session, if any has been
+ * set by {@link #setModelCallback(Executor, ModelCallback)}.
+ */
+ public void clearModelCallback() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mModelCallback = null;
+ mModelExecutor = null;
+ }
+ }
+
+ private ModelSession(SoundModel model, Phrase[] phrases,
+ IInjectModelEvent injection) {
+ mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid),
+ UUID.fromString(model.vendorUuid),
+ ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize));
+ if (phrases != null) {
+ mPhrases = new SoundTrigger.Keyphrase[phrases.length];
+ int i = 0;
+ for (var phrase : phrases) {
+ mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase);
+ }
+ } else {
+ mPhrases = null;
+ }
+ mInjectModelEvent = injection;
+ }
+
+ private void wrap(Consumer<ModelCallback> consumer) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mModelCallback != null && mModelExecutor != null) {
+ final ModelCallback callback = mModelCallback;
+ mModelExecutor.execute(() -> consumer.accept(callback));
+ }
+ }
+ }
+
+ private final SoundTriggerManager.Model mModel;
+ private final SoundTrigger.Keyphrase[] mPhrases;
+ private final IInjectModelEvent mInjectModelEvent;
+
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private ModelCallback mModelCallback = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private Executor mModelExecutor = null;
+ }
+
+ /**
+ * Session associated with a recognition start in the fake STHAL.
+ * Can be used to get information about the started recognition, register a callback
+ * for future events associated with this recognition, and triggering
+ * recognition events or aborts.
+ * This session is invalid once the recognition is stopped, caused by a
+ * {@link RecognitionSession#triggerAbortRecognition()},
+ * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)},
+ * the client stopping recognition, or any operation which invalidates the
+ * {@link ModelSession} which the session was created from.
+ * Further injections on an invalidated session will not be respected, and no future
+ * callbacks will be delivered.
+ * @hide
+ */
+ @TestApi
+ public class RecognitionSession {
+
+ /**
+ * Get an integer token representing the audio session associated with this
+ * recognition in the STHAL.
+ * @return - The session token.
+ */
+ public int getAudioSession() {
+ return mAudioSession;
+ }
+
+ /**
+ * Get the recognition config used to start this recognition.
+ * @return - The config passed to the HAL for startRecognition.
+ */
+ public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() {
+ return mRecognitionConfig;
+ }
+
+ /**
+ * Trigger a recognition in the fake STHAL.
+ * @param data - The opaque data buffer included in the recognition event.
+ * @param phraseExtras - Keyphrase metadata included in the event. The
+ * event must include metadata for the keyphrase id
+ * associated with this model to be received by the
+ * client application.
+ */
+ public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable
+ List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) {
+ PhraseRecognitionExtra[] converted = null;
+ if (phraseExtras != null) {
+ converted = new PhraseRecognitionExtra[phraseExtras.size()];
+ int i = 0;
+ for (var phraseExtra : phraseExtras) {
+ converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra);
+ }
+ }
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+ try {
+ mInjectRecognitionEvent.triggerRecognitionEvent(data, converted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Trigger an abort recognition event in the fake HAL. This represents a
+ * preemptive ending of the recognition session by the HAL, despite no
+ * recognition detection. Typically occurs during contention for microphone
+ * usage, or if model limits are hit.
+ * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block
+ * subsequent downward calls for contention reasons.
+ */
+ public void triggerAbortRecognition() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder());
+ try {
+ mInjectRecognitionEvent.triggerAbortRecognition();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Registers the recognition callback associated with this session. Events associated
+ * with this recognition session will be reported via this callback.
+ * See {@link RecognitionCallback}
+ * @param executor - Executor which the callback is dispatched on
+ * @param callback - Recognition callback for reporting recognition session events.
+ */
+ public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull RecognitionCallback callback) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionCallback = callback;
+ mRecognitionExecutor = executor;
+ }
+ }
+
+ /**
+ * Clear the recognition callback associated with this session, if any has been
+ * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}.
+ */
+ public void clearRecognitionCallback() {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mRecognitionCallback = null;
+ mRecognitionExecutor = null;
+ }
+ }
+
+ private RecognitionSession(int audioSession,
+ RecognitionConfig recognitionConfig,
+ IInjectRecognitionEvent injectRecognitionEvent) {
+ mAudioSession = audioSession;
+ mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig);
+ mInjectRecognitionEvent = injectRecognitionEvent;
+ }
+
+ private void wrap(Consumer<RecognitionCallback> consumer) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (mRecognitionCallback != null && mRecognitionExecutor != null) {
+ final RecognitionCallback callback = mRecognitionCallback;
+ mRecognitionExecutor.execute(() -> consumer.accept(callback));
+ }
+ }
+ }
+
+ private final int mAudioSession;
+ private final SoundTrigger.RecognitionConfig mRecognitionConfig;
+ private final IInjectRecognitionEvent mInjectRecognitionEvent;
+
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private Executor mRecognitionExecutor = null;
+ @GuardedBy("SoundTriggerInstrumentation.this.mLock")
+ private RecognitionCallback mRecognitionCallback = null;
+ }
+
+ // Implementation of injection interface passed to the HAL.
+ // This class will re-associate events received on this callback interface
+ // with sessions, to avoid staleness issues.
+ private class Injection extends ISoundTriggerInjection.Stub {
+ @Override
+ public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ mInjectGlobalEvent = globalInjection;
+ }
+ }
+
+ @Override
+ public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases,
+ IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ ModelSession modelSession = new ModelSession(model, phrases, modelInjection);
+ mModelSessionMap.put(modelInjection.asBinder(), modelSession);
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession));
+ }
+ }
+
+ @Override
+ public void onSoundModelUnloaded(IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded());
+ }
+ }
+
+ @Override
+ public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config,
+ IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ RecognitionSession recogSession = new RecognitionSession(
+ audioSessionHandle, config, recognitionInjection);
+ mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession);
+ clientModelSession.wrap((ModelCallback cb) ->
+ cb.onRecognitionStarted(recogSession));
+ }
+ }
+
+ @Override
+ public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ RecognitionSession clientRecognitionSession =
+ mRecognitionSessionMap.remove(recognitionSession.asBinder());
+ if (clientRecognitionSession == null) return;
+ clientRecognitionSession.wrap((RecognitionCallback cb)
+ -> cb.onRecognitionStopped());
+ }
+ }
+
+ @Override
+ public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder());
+ if (clientModelSession == null) return;
+ clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value));
+ }
+ }
+
+
+ @Override
+ public void onRestarted(IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mRecognitionSessionMap.clear();
+ mModelSessionMap.clear();
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted());
+ }
+ }
+
+ @Override
+ public void onFrameworkDetached(IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached());
+ }
+ }
+
+ @Override
+ public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return;
+ mClientToken = token;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached());
+ }
+ }
+
+ @Override
+ public void onClientDetached(IBinder token) {
+ synchronized (SoundTriggerInstrumentation.this.mLock) {
+ if (token != mClientToken) return;
+ mClientToken = null;
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached());
+ }
+ }
+
+ @Override
+ public void onPreempted() {
+ // This is always valid, independent of session
+ mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted());
+ // Callbacks will no longer be delivered, and injection will be silently dropped.
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ public SoundTriggerInstrumentation(ISoundTriggerService service,
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull GlobalCallback callback) {
+ mClientCallback = Objects.requireNonNull(callback);
+ mGlobalCallbackExecutor = Objects.requireNonNull(executor);
+ try {
+ service.attachInjection(new Injection());
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Simulate a HAL restart, typically caused by the framework on an unexpected error,
+ * or a restart of the core audio HAL.
+ * Application sessions will be detached, and all state will be cleared. The framework
+ * will re-attach to the HAL following restart.
+ * @hide
+ */
+ @TestApi
+ public void triggerRestart() {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException(
+ "Attempted to trigger HAL restart before registration");
+ }
+ try {
+ mInjectGlobalEvent.triggerRestart();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Trigger a resource available callback from the fake SoundTrigger HAL to the framework.
+ * This callback notifies the framework that methods which previously failed due to
+ * resource contention may now succeed.
+ * @hide
+ */
+ @TestApi
+ public void triggerOnResourcesAvailable() {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException(
+ "Attempted to trigger HAL resources available before registration");
+ }
+ try {
+ mInjectGlobalEvent.triggerOnResourcesAvailable();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Simulate resource contention, similar to when HAL which does not
+ * support concurrent capture opens a capture stream, or when a HAL
+ * has reached its maximum number of models.
+ * Subsequent model loads and recognition starts will gracefully error.
+ * Since this call does not trigger a callback through the framework, the
+ * call will block until the fake HAL has acknowledged the state change.
+ * @param isResourceContended - true to enable contention, false to return
+ * to normal functioning.
+ * @hide
+ */
+ @TestApi
+ public void setResourceContention(boolean isResourceContended) {
+ synchronized (mLock) {
+ if (mInjectGlobalEvent == null) {
+ throw new IllegalStateException("Injection interface not set up");
+ }
+ IInjectGlobalEvent current = mInjectGlobalEvent;
+ final CountDownLatch signal = new CountDownLatch(1);
+ try {
+ current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() {
+ @Override
+ public void eventReceived() {
+ signal.countDown();
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ // Block until we get a callback from the service that our request was serviced.
+ try {
+ // Rely on test timeout if we don't get a response.
+ signal.await();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
+
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index ae8121a59abf..c41bd1bc3094 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -18,11 +18,13 @@ package android.media.soundtrigger;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -45,6 +47,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.util.Slog;
@@ -53,9 +56,9 @@ import com.android.internal.app.ISoundTriggerSession;
import com.android.internal.util.Preconditions;
import java.util.HashMap;
-import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.Executor;
/**
* This class provides management of non-voice (general sound trigger) based sound recognition
@@ -609,4 +612,24 @@ public final class SoundTriggerManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake
+ * STHAL. Clients must attach to the appropriate underlying ST module.
+ * @param executor - Executor to dispatch global callbacks on
+ * @param callback - Callback for unsessioned events received by the fake STHAL
+ * @return - A {@link SoundTriggerInstrumentation} for observation/injection.
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
+ @NonNull
+ public static SoundTriggerInstrumentation attachInstrumentation(
+ @CallbackExecutor @NonNull Executor executor,
+ @NonNull SoundTriggerInstrumentation.GlobalCallback callback) {
+ ISoundTriggerService service = ISoundTriggerService.Stub.asInterface(
+ ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE));
+ return new SoundTriggerInstrumentation(service, executor, callback);
+ }
+
}
diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java
index 8ec8967a353e..5a690a57dbb2 100644
--- a/media/java/android/media/voice/KeyphraseModelManager.java
+++ b/media/java/android/media/voice/KeyphraseModelManager.java
@@ -21,7 +21,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.hardware.soundtrigger.SoundTrigger;
+import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Slog;
@@ -154,4 +156,23 @@ public final class KeyphraseModelManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Override the persistent enrolled model database with an in-memory
+ * fake for testing purposes.
+ *
+ * @param enabled - {@code true} if the model enrollment database should be overridden with an
+ * in-memory fake. {@code false} if the real, persistent model enrollment database should be
+ * used.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ @TestApi
+ public void setModelDatabaseForTestEnabled(boolean enabled) {
+ try {
+ mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h
index c7530207d1fa..060abfdc1ee5 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -44,12 +44,19 @@ struct JMediaCodecLinearBlock {
std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) const {
if (mBuffer) {
+ // TODO: if returned C2Buffer is different from mBuffer, we should
+ // find a way to connect the life cycle between this C2Buffer and
+ // mBuffer.
if (mBuffer->data().type() != C2BufferData::LINEAR) {
return nullptr;
}
C2ConstLinearBlock block = mBuffer->data().linearBlocks().front();
if (offset == 0 && size == block.capacity()) {
- return mBuffer;
+ // Let C2Buffer be new one to queue to MediaCodec. It will allow
+ // the related input slot to be released by onWorkDone from C2
+ // Component. Currently, the life cycle of mBuffer should be
+ // protected by different flows.
+ return std::make_shared<C2Buffer>(*mBuffer);
}
std::shared_ptr<C2Buffer> buffer =
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 116237f6b998..609c7a40bb36 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -196,6 +196,15 @@ status_t JMediaExtractor::readSampleData(
dstSize = (size_t) env->GetDirectBufferCapacity(byteBuf);
}
+ // unlikely, but GetByteArrayElements() can fail
+ if (dst == nullptr) {
+ ALOGE("no buffer into which to read the data");
+ if (byteArray != NULL) {
+ env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
+ }
+ return -ENOMEM;
+ }
+
if (dstSize < offset) {
if (byteArray != NULL) {
env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0);
@@ -204,8 +213,10 @@ status_t JMediaExtractor::readSampleData(
return -ERANGE;
}
+ // passes in the backing memory to use, so it doesn't fail
sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset);
+ buffer->setRange(0, 0); // mark it empty
status_t err = mImpl->readSampleData(buffer);
if (byteArray != NULL) {
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index d87abb98ebde..ebfb86d4d6ba 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -34,7 +34,7 @@
<string name="summary_watch">This app is needed to manage your <xliff:g id="device_name" example="My Watch">%1$s</xliff:g>. <xliff:g id="app_name" example="Android Wear">%2$s</xliff:g> will be allowed to sync info, like the name of someone calling, interact with your notifications and access your Phone, SMS, Contacts, Calendar, Call logs and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of WATCH profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
+ <string name="summary_watch_single_device">This app will be allowed to sync info, like the name of someone calling, and access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_GLASSES ================= -->
@@ -48,7 +48,7 @@
<string name="summary_glasses_multi_device">This app is needed to manage <xliff:g id="device_name" example="My Glasses">%1$s</xliff:g>. <xliff:g id="app_name" example="Glasses">%2$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
<!-- Description of the privileges the application will get if associated with the companion device of GLASSES profile for singleDevice(type) [CHAR LIMIT=NONE] -->
- <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_name" example="phone">%1$s</xliff:g></string>
+ <string name="summary_glasses_single_device">This app will be allowed to access these permissions on your <xliff:g id="device_type" example="phone">%1$s</xliff:g></string>
<!-- ================= DEVICE_PROFILE_APP_STREAMING ================= -->
@@ -59,7 +59,7 @@
<string name="helper_title_app_streaming">Cross-device services</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
+ <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
@@ -81,7 +81,7 @@
<string name="helper_title_computer">Google Play services</string>
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
+ <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="display_name" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
<!-- ================= DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ================= -->
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index e9b2e1041c22..3e652517270d 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -117,6 +117,8 @@
<string name="get_dialog_sign_in_type_username_separator" translatable="false">" • "</string>
<!-- This text is followed by a list of one or more options. [CHAR LIMIT=80] -->
<string name="get_dialog_title_sign_in_options">Sign-in options</string>
+ <!-- Button label for viewing the full information about an account. [CHAR LIMIT=80] -->
+ <string name="button_label_view_more">View more</string>
<!-- Column heading for displaying sign-ins for a specific username. [CHAR LIMIT=80] -->
<string name="get_dialog_heading_for_username">For <xliff:g id="username" example="becket@gmail.com">%1$s</xliff:g></string>
<!-- Column heading for displaying locked (that is, the user needs to first authenticate via pin, fingerprint, faceId, etc.) sign-ins. [CHAR LIMIT=80] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index dd4419bc6540..a9bee039264e 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -65,8 +65,8 @@ class CredentialManagerRepo(
)
val originName: String? = when (requestInfo?.type) {
- RequestInfo.TYPE_CREATE -> requestInfo?.createCredentialRequest?.origin
- RequestInfo.TYPE_GET -> requestInfo?.getCredentialRequest?.origin
+ RequestInfo.TYPE_CREATE -> requestInfo.createCredentialRequest?.origin
+ RequestInfo.TYPE_GET -> requestInfo.getCredentialRequest?.origin
else -> null
}
@@ -107,7 +107,7 @@ class CredentialManagerRepo(
initialUiState = when (requestInfo?.type) {
RequestInfo.TYPE_CREATE -> {
- val defaultProviderId = userConfigRepo.getDefaultProviderId()
+ val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId()
val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
val providerEnableListUiState = getCreateProviderEnableListInitialUiState()
val providerDisableListUiState = getCreateProviderDisableListInitialUiState()
@@ -115,12 +115,14 @@ class CredentialManagerRepo(
getCreateRequestDisplayInfoInitialUiState(originName)!!
UiState(
createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState(
- providerEnableListUiState,
- providerDisableListUiState,
- defaultProviderId,
- requestDisplayInfoUiState,
+ enabledProviders = providerEnableListUiState,
+ disabledProviders = providerDisableListUiState,
+ defaultProviderIdPreferredByApp =
+ requestDisplayInfoUiState.appPreferredDefaultProviderId,
+ defaultProviderIdSetByUser = defaultProviderIdSetByUser,
+ requestDisplayInfo = requestDisplayInfoUiState,
isOnPasskeyIntroStateAlready = false,
- isPasskeyFirstUse
+ isPasskeyFirstUse = isPasskeyFirstUse,
)!!,
getCredentialUiState = null,
cancelRequestState = cancelUiRequestState
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
index 24f92c00c772..7581b5c0aa91 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -19,9 +19,9 @@ package com.android.credentialmanager
import android.content.Intent
import android.credentials.ui.BaseDialogResult
import android.credentials.ui.RequestInfo
+import android.net.Uri
import android.os.Bundle
import android.os.ResultReceiver
-import android.provider.Settings
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -192,7 +192,9 @@ class CredentialSelectorActivity : ComponentActivity() {
this@CredentialSelectorActivity.finish()
} else if (dialogState == DialogState.CANCELED_FOR_SETTINGS) {
Log.d(Constants.LOG_TAG, "Received signal to finish the activity and launch settings.")
- this@CredentialSelectorActivity.startActivity(Intent(Settings.ACTION_SYNC_SETTINGS))
+ val settingsIntent = Intent(ACTION_CREDENTIAL_PROVIDER)
+ settingsIntent.data = Uri.parse("package:" + this.getPackageName())
+ this@CredentialSelectorActivity.startActivity(settingsIntent)
this@CredentialSelectorActivity.finish()
}
}
@@ -222,4 +224,8 @@ class CredentialSelectorActivity : ComponentActivity() {
dismissOnTimeout = true,
)
}
+
+ companion object {
+ const val ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER"
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 29ec970966d6..4d2bb4c6016a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -250,9 +250,15 @@ class CredentialSelectorViewModel(
return
}
val newUiState = CreateFlowUtils.toCreateCredentialUiState(
- prevUiState.enabledProviders, prevUiState.disabledProviders,
- userConfigRepo.getDefaultProviderId(), prevUiState.requestDisplayInfo, true,
- userConfigRepo.getIsPasskeyFirstUse())
+ enabledProviders = prevUiState.enabledProviders,
+ disabledProviders = prevUiState.disabledProviders,
+ defaultProviderIdPreferredByApp =
+ prevUiState.requestDisplayInfo.appPreferredDefaultProviderId,
+ defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(),
+ requestDisplayInfo = prevUiState.requestDisplayInfo,
+ isOnPasskeyIntroStateAlready = true,
+ isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+ )
if (newUiState == null) {
Log.d(Constants.LOG_TAG, "Unable to update create ui state")
onInternalError()
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 725401fb85bb..108f4945c9e9 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -198,7 +198,8 @@ class GetFlowUtils {
it.type,
it.credentialRetrievalData,
it.credentialRetrievalData,
- it.isSystemProviderRequired
+ it.isSystemProviderRequired,
+ it.allowedProviders,
)
if (credentialOptionJetpack is GetPublicKeyCredentialOption) {
credentialOptionJetpack.preferImmediatelyAvailableCredentials
@@ -210,7 +211,11 @@ class GetFlowUtils {
appName = originName
?: getAppLabel(context.packageManager, requestInfo.appPackageName)
?: return null,
- preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
+ preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials,
+ preferIdentityDocUi = getCredentialRequest.data.getBoolean(
+ // TODO(b/276777444): replace with direct library constant reference once
+ // exposed.
+ "androidx.credentials.BUNDLE_KEY_PREFER_IDENTITY_DOC_UI"),
)
}
@@ -241,20 +246,13 @@ class GetFlowUtils {
userName = credentialEntry.username.toString(),
displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
- shouldTintIcon = credentialEntry.isDefaultIcon ?: false,
+ shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
isAutoSelectable = credentialEntry.isAutoSelectAllowed &&
credentialEntry.autoSelectAllowedFromOption,
))
}
is PublicKeyCredentialEntry -> {
- val passkeyUsername = credentialEntry.username.toString()
- val passkeyDisplayName = credentialEntry.displayName?.toString() ?: ""
- val (username, displayName) = userAndDisplayNameForPasskey(
- passkeyUsername = passkeyUsername,
- passkeyDisplayName = passkeyDisplayName,
- )
-
result.add(CredentialEntryInfo(
providerId = providerId,
providerDisplayName = providerLabel,
@@ -264,8 +262,8 @@ class GetFlowUtils {
fillInIntent = it.frameworkExtrasIntent,
credentialType = CredentialType.PASSKEY,
credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
- userName = username,
- displayName = displayName,
+ userName = credentialEntry.username.toString(),
+ displayName = credentialEntry.displayName?.toString(),
icon = credentialEntry.icon.loadDrawable(context),
shouldTintIcon = credentialEntry.isDefaultIcon,
lastUsedTimeMillis = credentialEntry.lastUsedTime,
@@ -465,8 +463,12 @@ class CreateFlowUtils {
createCredentialRequest.type,
createCredentialRequest.credentialData,
createCredentialRequest.candidateQueryData,
- createCredentialRequest.isSystemProviderRequired
+ createCredentialRequest.isSystemProviderRequired,
+ createCredentialRequest.origin,
)
+ val appPreferredDefaultProviderId: String? =
+ if (!requestInfo.hasPermissionToOverrideDefault()) null
+ else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider
return when (createCredentialRequestJetpack) {
is CreatePasswordRequest -> RequestDisplayInfo(
createCredentialRequestJetpack.id,
@@ -475,6 +477,7 @@ class CreateFlowUtils {
appLabel,
context.getDrawable(R.drawable.ic_password_24) ?: return null,
preferImmediatelyAvailableCredentials = false,
+ appPreferredDefaultProviderId = appPreferredDefaultProviderId,
)
is CreatePublicKeyCredentialRequest -> {
newRequestDisplayInfoFromPasskeyJson(
@@ -483,6 +486,7 @@ class CreateFlowUtils {
context = context,
preferImmediatelyAvailableCredentials =
createCredentialRequestJetpack.preferImmediatelyAvailableCredentials,
+ appPreferredDefaultProviderId = appPreferredDefaultProviderId,
)
}
is CreateCustomCredentialRequest -> {
@@ -498,6 +502,7 @@ class CreateFlowUtils {
typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context)
?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null,
preferImmediatelyAvailableCredentials = false,
+ appPreferredDefaultProviderId = appPreferredDefaultProviderId,
)
}
else -> null
@@ -507,20 +512,27 @@ class CreateFlowUtils {
fun toCreateCredentialUiState(
enabledProviders: List<EnabledProviderInfo>,
disabledProviders: List<DisabledProviderInfo>?,
- defaultProviderId: String?,
+ defaultProviderIdPreferredByApp: String?,
+ defaultProviderIdSetByUser: String?,
requestDisplayInfo: RequestDisplayInfo,
isOnPasskeyIntroStateAlready: Boolean,
isPasskeyFirstUse: Boolean,
): CreateCredentialUiState? {
var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
var remoteEntry: RemoteInfo? = null
- var defaultProvider: EnabledProviderInfo? = null
+ var defaultProviderPreferredByApp: EnabledProviderInfo? = null
+ var defaultProviderSetByUser: EnabledProviderInfo? = null
var createOptionsPairs:
MutableList<Pair<CreateOptionInfo, EnabledProviderInfo>> = mutableListOf()
enabledProviders.forEach { enabledProvider ->
- if (defaultProviderId != null) {
- if (enabledProvider.id == defaultProviderId) {
- defaultProvider = enabledProvider
+ if (defaultProviderIdPreferredByApp != null) {
+ if (enabledProvider.id == defaultProviderIdPreferredByApp) {
+ defaultProviderPreferredByApp = enabledProvider
+ }
+ }
+ if (defaultProviderIdSetByUser != null) {
+ if (enabledProvider.id == defaultProviderIdSetByUser) {
+ defaultProviderSetByUser = enabledProvider
}
}
if (enabledProvider.createOptions.isNotEmpty()) {
@@ -539,12 +551,14 @@ class CreateFlowUtils {
remoteEntry = currRemoteEntry
}
}
+ val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser
val initialScreenState = toCreateScreenState(
- /*createOptionSize=*/createOptionsPairs.size,
- /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
- /*requestDisplayInfo=*/requestDisplayInfo,
- /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
- /*isPasskeyFirstUse=*/isPasskeyFirstUse
+ createOptionSize = createOptionsPairs.size,
+ isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready,
+ requestDisplayInfo = requestDisplayInfo,
+ defaultProvider = defaultProvider,
+ remoteEntry = remoteEntry,
+ isPasskeyFirstUse = isPasskeyFirstUse
) ?: return null
return CreateCredentialUiState(
enabledProviders = enabledProviders,
@@ -670,6 +684,7 @@ class CreateFlowUtils {
appLabel: String,
context: Context,
preferImmediatelyAvailableCredentials: Boolean,
+ appPreferredDefaultProviderId: String?,
): RequestDisplayInfo? {
val json = JSONObject(requestJson)
var passkeyUsername = ""
@@ -690,6 +705,7 @@ class CreateFlowUtils {
appLabel,
context.getDrawable(R.drawable.ic_passkey_24) ?: return null,
preferImmediatelyAvailableCredentials,
+ appPreferredDefaultProviderId,
)
}
}
@@ -703,7 +719,7 @@ class CreateFlowUtils {
* 2) username on top if display-name is not available.
* 3) don't show username on second line if username == display-name
*/
-private fun userAndDisplayNameForPasskey(
+fun userAndDisplayNameForPasskey(
passkeyUsername: String,
passkeyDisplayName: String,
): Pair<String, String> {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
index 307d95313ec8..10a75d436a5b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/material/ModalBottomSheet.kt
@@ -316,7 +316,7 @@ fun ModalBottomSheetLayout(
rememberModalBottomSheetState(Hidden),
sheetShape: Shape = MaterialTheme.shapes.large,
sheetElevation: Dp = ModalBottomSheetDefaults.Elevation,
- sheetBackgroundColor: Color = MaterialTheme.colorScheme.surface,
+ sheetBackgroundColor: Color,
sheetContentColor: Color = contentColorFor(sheetBackgroundColor),
content: @Composable () -> Unit
) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
index 7a720b1e858b..2dba2ab6777c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Entry.kt
@@ -32,7 +32,6 @@ import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.SuggestionChipDefaults
import androidx.compose.material3.TopAppBar
@@ -52,6 +51,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.LayoutDirection
@@ -80,6 +80,7 @@ fun Entry(
/** If true, draws a trailing lock icon. */
isLockedAuthEntry: Boolean = false,
enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
) {
val iconPadding = Modifier.wrapContentSize().padding(
// Horizontal padding should be 16dp, but the suggestion chip itself
@@ -104,7 +105,11 @@ fun Entry(
) {
// Apply weight so that the trailing icon can always show.
Column(modifier = Modifier.wrapContentHeight().fillMaxWidth().weight(1f)) {
- SmallTitleText(text = entryHeadlineText, enforceOneLine = enforceOneLine)
+ SmallTitleText(
+ text = entryHeadlineText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
if (passwordValue != null) {
Row(
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
@@ -142,10 +147,18 @@ fun Entry(
)
}
} else if (entrySecondLineText != null) {
- BodySmallText(text = entrySecondLineText, enforceOneLine = enforceOneLine)
+ BodySmallText(
+ text = entrySecondLineText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
}
if (entryThirdLineText != null) {
- BodySmallText(text = entryThirdLineText, enforceOneLine = enforceOneLine)
+ BodySmallText(
+ text = entryThirdLineText,
+ enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
+ )
}
}
if (isLockedAuthEntry) {
@@ -155,7 +168,7 @@ fun Entry(
// Decorative purpose only.
contentDescription = null,
modifier = Modifier.size(24.dp),
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
)
}
}
@@ -169,7 +182,7 @@ fun Entry(
Icon(
modifier = iconSize,
bitmap = iconImageBitmap,
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -193,7 +206,7 @@ fun Entry(
Icon(
modifier = iconSize,
imageVector = iconImageVector,
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -205,7 +218,7 @@ fun Entry(
Icon(
modifier = iconSize,
painter = iconPainter,
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -217,9 +230,8 @@ fun Entry(
border = null,
colors = SuggestionChipDefaults.suggestionChipColors(
containerColor = LocalAndroidColorScheme.current.colorSurfaceContainerHigh,
- // TODO: remove?
- labelColor = MaterialTheme.colorScheme.onSurfaceVariant,
- iconContentColor = MaterialTheme.colorScheme.onSurfaceVariant,
+ labelColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ iconContentColor = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
),
)
}
@@ -282,7 +294,7 @@ fun PasskeyBenefitRow(
Icon(
modifier = Modifier.size(24.dp),
painter = leadingIconPainter,
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
// Decorative purpose only.
contentDescription = null,
)
@@ -341,7 +353,7 @@ fun MoreOptionTopAppBar(
R.string.accessibility_back_arrow_button
),
modifier = Modifier.size(24.dp).autoMirrored(),
- tint = MaterialTheme.colorScheme.onSurfaceVariant,
+ tint = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
)
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
index 358122809985..2df0c7a9b1e8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt
@@ -20,25 +20,31 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
@Composable
-fun CredentialListSectionHeader(text: String) {
- InternalSectionHeader(text, MaterialTheme.colorScheme.onSurfaceVariant)
+fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) {
+ InternalSectionHeader(
+ text = text,
+ color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
+ applyTopPadding = !isFirstSection
+ )
}
@Composable
fun MoreAboutPasskeySectionHeader(text: String) {
- InternalSectionHeader(text, MaterialTheme.colorScheme.onSurface)
+ InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurface)
}
@Composable
-private fun InternalSectionHeader(text: String, color: Color) {
- Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) {
+private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) {
+ Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(
+ top = if (applyTopPadding) 8.dp else 0.dp
+ )) {
SectionHeaderText(
text,
modifier = Modifier.padding(top = 20.dp, bottom = 8.dp),
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
index 22871bcbe767..6b46636964e4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/Texts.kt
@@ -22,8 +22,10 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
/**
* The headline for a screen. E.g. "Create a passkey for X", "Choose a saved sign-in for X".
@@ -35,7 +37,7 @@ fun HeadlineText(text: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = MaterialTheme.colorScheme.onSurface,
+ color = LocalAndroidColorScheme.current.colorOnSurface,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.headlineSmall,
)
@@ -49,7 +51,7 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
+ color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
}
@@ -58,14 +60,20 @@ fun BodyMediumText(text: String, modifier: Modifier = Modifier) {
* Body-small typography; on-surface-variant color.
*/
@Composable
-fun BodySmallText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
+fun BodySmallText(
+ text: String,
+ modifier: Modifier = Modifier,
+ enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
+ color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis,
- maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
+ maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
+ onTextLayout = onTextLayout,
)
}
@@ -77,7 +85,7 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = MaterialTheme.colorScheme.onSurface,
+ color = LocalAndroidColorScheme.current.colorOnSurface,
style = MaterialTheme.typography.titleLarge,
)
}
@@ -86,14 +94,20 @@ fun LargeTitleText(text: String, modifier: Modifier = Modifier) {
* Title-small typography; on-surface color.
*/
@Composable
-fun SmallTitleText(text: String, modifier: Modifier = Modifier, enforceOneLine: Boolean = false) {
+fun SmallTitleText(
+ text: String,
+ modifier: Modifier = Modifier,
+ enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
+) {
Text(
modifier = modifier.wrapContentSize(),
text = text,
- color = MaterialTheme.colorScheme.onSurface,
+ color = LocalAndroidColorScheme.current.colorOnSurface,
style = MaterialTheme.typography.titleSmall,
overflow = TextOverflow.Ellipsis,
- maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE
+ maxLines = if (enforceOneLine) 1 else Int.MAX_VALUE,
+ onTextLayout = onTextLayout,
)
}
@@ -145,7 +159,7 @@ fun LargeLabelTextOnSurfaceVariant(text: String, modifier: Modifier = Modifier)
modifier = modifier.wrapContentSize(),
text = text,
textAlign = TextAlign.Center,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
+ color = LocalAndroidColorScheme.current.colorOnSurfaceVariant,
style = MaterialTheme.typography.labelLarge,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 648d83268541..96010cc66821 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -30,7 +30,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.material3.Divider
-import androidx.compose.material3.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.NewReleases
import androidx.compose.material.icons.filled.Add
@@ -67,6 +66,7 @@ import com.android.credentialmanager.common.ui.SheetContainerCard
import com.android.credentialmanager.common.ui.PasskeyBenefitRow
import com.android.credentialmanager.common.ui.HeadlineText
import com.android.credentialmanager.logging.CreateCredentialEvent
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
@@ -435,7 +435,7 @@ fun MoreOptionsRowIntroCard(
}
SheetContainerCard {
item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) }
- item { Divider(thickness = 24.dp, color = Color.Transparent) }
+ item { Divider(thickness = 16.dp, color = Color.Transparent) }
item {
HeadlineText(
text = stringResource(
@@ -559,7 +559,7 @@ fun CreationSelectionCard(
item {
Divider(
thickness = 1.dp,
- color = MaterialTheme.colorScheme.outlineVariant,
+ color = LocalAndroidColorScheme.current.colorOutlineVariant,
modifier = Modifier.padding(vertical = 16.dp)
)
}
@@ -633,6 +633,7 @@ fun MoreAboutPasskeysIntroCard(
}
}
item {
+ Divider(thickness = 8.dp, color = Color.Transparent)
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.public_key_cryptography_title)
)
@@ -641,6 +642,7 @@ fun MoreAboutPasskeysIntroCard(
}
}
item {
+ Divider(thickness = 8.dp, color = Color.Transparent)
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.improved_account_security_title)
)
@@ -649,6 +651,7 @@ fun MoreAboutPasskeysIntroCard(
}
}
item {
+ Divider(thickness = 8.dp, color = Color.Transparent)
MoreAboutPasskeySectionHeader(
text = stringResource(R.string.seamless_transition_title)
)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 4332fb34ce79..12bb6298b282 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -107,6 +107,7 @@ data class RequestDisplayInfo(
val appName: String,
val typeIcon: Drawable,
val preferImmediatelyAvailableCredentials: Boolean,
+ val appPreferredDefaultProviderId: String?,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 6d7ecd70eb0b..98bd020b234b 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -34,11 +34,15 @@ import androidx.compose.material3.Divider
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.TextLayoutResult
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.android.credentialmanager.CredentialSelectorViewModel
@@ -62,6 +66,7 @@ import com.android.credentialmanager.common.ui.HeadlineIcon
import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant
import com.android.credentialmanager.common.ui.Snackbar
import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.credentialmanager.userAndDisplayNameForPasskey
import com.android.internal.logging.UiEventLogger.UiEventEnum
@Composable
@@ -158,6 +163,7 @@ fun PrimarySelectionCard(
onMoreOptionSelected: () -> Unit,
onLog: @Composable (UiEventEnum) -> Unit,
) {
+ val showMoreForTruncatedEntry = remember { mutableStateOf(false) }
val sortedUserNameToCredentialEntryList =
providerDisplayInfo.sortedUserNameToCredentialEntryList
val authenticationEntryList = providerDisplayInfo.authenticationEntryList
@@ -209,6 +215,8 @@ fun PrimarySelectionCard(
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
val usernameForCredentialSize = sortedUserNameToCredentialEntryList.size
val authenticationEntrySize = authenticationEntryList.size
+ // If true, render a view more button for the single truncated entry on the
+ // front page.
// Show max 4 entries in this primary page
if (usernameForCredentialSize + authenticationEntrySize <= 4) {
sortedUserNameToCredentialEntryList.forEach {
@@ -216,6 +224,9 @@ fun PrimarySelectionCard(
credentialEntryInfo = it.sortedCredentialEntryList.first(),
onEntrySelected = onEntrySelected,
enforceOneLine = true,
+ onTextLayout = {
+ showMoreForTruncatedEntry.value = it.hasVisualOverflow
+ }
)
}
authenticationEntryList.forEach {
@@ -269,6 +280,13 @@ fun PrimarySelectionCard(
onMoreOptionSelected
)
}
+ } else if (showMoreForTruncatedEntry.value) {
+ {
+ ActionButton(
+ stringResource(R.string.button_label_view_more),
+ onMoreOptionSelected
+ )
+ }
} else null,
rightButton = if (activeEntry != null) { // Only one sign-in options exist
{
@@ -305,12 +323,15 @@ fun AllSignInOptionCard(
bottomPadding = 0.dp,
)
}) {
+ var isFirstSection = true
// For username
items(sortedUserNameToCredentialEntryList) { item ->
PerUserNameCredentials(
perUserNameCredentialEntryList = item,
onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
// Locked password manager
if (authenticationEntryList.isNotEmpty()) {
@@ -318,7 +339,9 @@ fun AllSignInOptionCard(
LockedCredentials(
authenticationEntryList = authenticationEntryList,
onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
}
// From another device
@@ -328,15 +351,19 @@ fun AllSignInOptionCard(
RemoteEntryCard(
remoteEntry = remoteEntry,
onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
}
// Manage sign-ins (action chips)
item {
ActionChips(
providerInfoList = providerInfoList,
- onEntrySelected = onEntrySelected
+ onEntrySelected = onEntrySelected,
+ isFirstSection = isFirstSection,
)
+ isFirstSection = false
}
}
onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
@@ -349,6 +376,7 @@ fun AllSignInOptionCard(
fun ActionChips(
providerInfoList: List<ProviderInfo>,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
val actionChips = providerInfoList.flatMap { it.actionEntryList }
if (actionChips.isEmpty()) {
@@ -356,7 +384,8 @@ fun ActionChips(
}
CredentialListSectionHeader(
- text = stringResource(R.string.get_dialog_heading_manage_sign_ins)
+ text = stringResource(R.string.get_dialog_heading_manage_sign_ins),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
@@ -371,9 +400,11 @@ fun ActionChips(
fun RemoteEntryCard(
remoteEntry: RemoteEntryInfo,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
CredentialListSectionHeader(
- text = stringResource(R.string.get_dialog_heading_from_another_device)
+ text = stringResource(R.string.get_dialog_heading_from_another_device),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(
@@ -395,9 +426,11 @@ fun RemoteEntryCard(
fun LockedCredentials(
authenticationEntryList: List<AuthenticationEntryInfo>,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
CredentialListSectionHeader(
- text = stringResource(R.string.get_dialog_heading_locked_password_managers)
+ text = stringResource(R.string.get_dialog_heading_locked_password_managers),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(
@@ -415,11 +448,13 @@ fun LockedCredentials(
fun PerUserNameCredentials(
perUserNameCredentialEntryList: PerUserNameCredentialEntryList,
onEntrySelected: (BaseEntry) -> Unit,
+ isFirstSection: Boolean,
) {
CredentialListSectionHeader(
text = stringResource(
R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName
- )
+ ),
+ isFirstSection = isFirstSection,
)
CredentialContainerCard {
Column(
@@ -438,7 +473,12 @@ fun CredentialEntryRow(
credentialEntryInfo: CredentialEntryInfo,
onEntrySelected: (BaseEntry) -> Unit,
enforceOneLine: Boolean = false,
+ onTextLayout: (TextLayoutResult) -> Unit = {},
) {
+ val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY)
+ userAndDisplayNameForPasskey(
+ credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "")
+ else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName)
Entry(
onClick = { onEntrySelected(credentialEntryInfo) },
iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(),
@@ -447,13 +487,13 @@ fun CredentialEntryRow(
iconPainter =
if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24)
else null,
- entryHeadlineText = credentialEntryInfo.userName,
+ entryHeadlineText = username,
entrySecondLineText = if (
credentialEntryInfo.credentialType == CredentialType.PASSWORD) {
"••••••••••••"
} else {
val itemsToDisplay = listOf(
- credentialEntryInfo.displayName,
+ displayName,
credentialEntryInfo.credentialTypeDisplayName,
credentialEntryInfo.providerDisplayName
).filterNot(TextUtils::isEmpty)
@@ -463,6 +503,7 @@ fun CredentialEntryRow(
)
},
enforceOneLine = enforceOneLine,
+ onTextLayout = onTextLayout,
)
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 7a8679038579..c9c62a48d130 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -42,7 +42,7 @@ internal fun hasContentToDisplay(state: GetCredentialUiState): Boolean {
}
internal fun isFallbackScreen(state: GetCredentialUiState): Boolean {
- return false
+ return state.requestDisplayInfo.preferIdentityDocUi
}
internal fun findAutoSelectEntry(providerDisplayInfo: ProviderDisplayInfo): CredentialEntryInfo? {
@@ -172,6 +172,7 @@ class ActionEntryInfo(
data class RequestDisplayInfo(
val appName: String,
val preferImmediatelyAvailableCredentials: Boolean,
+ val preferIdentityDocUi: Boolean,
)
/**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
index 8928e1869838..a33904d30393 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/ui/theme/AndroidColorScheme.kt
@@ -42,6 +42,9 @@ val LocalAndroidColorScheme =
class AndroidColorScheme internal constructor(context: Context) {
val colorSurfaceBright = getColor(context, R.attr.materialColorSurfaceBright)
val colorSurfaceContainerHigh = getColor(context, R.attr.materialColorSurfaceContainerHigh)
+ val colorOutlineVariant = getColor(context, R.attr.materialColorOutlineVariant)
+ val colorOnSurface = getColor(context, R.attr.materialColorOnSurface)
+ val colorOnSurfaceVariant = getColor(context, R.attr.materialColorOnSurfaceVariant)
companion object {
fun getColor(context: Context, attr: Int): Color {
diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml
index c2aaeace1af6..776bf2b761ce 100644
--- a/packages/DynamicSystemInstallationService/AndroidManifest.xml
+++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml
@@ -36,6 +36,10 @@
<data android:scheme="http" />
<data android:scheme="https" />
</intent-filter>
+ <intent-filter>
+ <action android:name="android.os.image.action.START_INSTALL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
</activity>
<receiver
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 2c4b4786c968..b265a425d2e7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -19,6 +19,7 @@ package com.android.dynsystem;
import static android.os.AsyncTask.Status.FINISHED;
import static android.os.AsyncTask.Status.PENDING;
import static android.os.AsyncTask.Status.RUNNING;
+import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
@@ -27,6 +28,8 @@ import static android.os.image.DynamicSystemClient.CAUSE_ERROR_IO;
import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED;
import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED;
import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED;
+import static android.os.image.DynamicSystemClient.KEY_ENABLE_WHEN_COMPLETED;
+import static android.os.image.DynamicSystemClient.KEY_ONE_SHOT;
import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS;
import static android.os.image.DynamicSystemClient.STATUS_IN_USE;
import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED;
@@ -77,8 +80,6 @@ public class DynamicSystemInstallationService extends Service
private static final String TAG = "DynamicSystemInstallationService";
- // TODO (b/131866826): This is currently for test only. Will move this to System API.
- static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
static final String DEFAULT_DSU_SLOT = "dsu";
static final String KEY_PUBKEY = "KEY_PUBKEY";
@@ -172,6 +173,8 @@ public class DynamicSystemInstallationService extends Service
// This is for testing only now
private boolean mEnableWhenCompleted;
+ private boolean mOneShot;
+ private boolean mHideNotification;
private InstallationAsyncTask.Progress mInstallTaskProgress;
private InstallationAsyncTask mInstallTask;
@@ -229,6 +232,8 @@ public class DynamicSystemInstallationService extends Service
executeRebootToNormalCommand();
} else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) {
executeNotifyIfInUseCommand();
+ } else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
+ executeHideNotificationCommand();
}
return Service.START_NOT_STICKY;
@@ -318,6 +323,7 @@ public class DynamicSystemInstallationService extends Service
long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+ mOneShot = intent.getBooleanExtra(KEY_ONE_SHOT, true);
String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
String publicKey = intent.getStringExtra(KEY_PUBKEY);
@@ -384,9 +390,9 @@ public class DynamicSystemInstallationService extends Service
boolean enabled = false;
if (mInstallTask != null && mInstallTask.isCompleted()) {
- enabled = mInstallTask.commit();
+ enabled = mInstallTask.commit(mOneShot);
} else if (isDynamicSystemInstalled()) {
- enabled = mDynSystem.setEnable(true, true);
+ enabled = mDynSystem.setEnable(true, mOneShot);
} else {
Log.e(TAG, "Trying to reboot to AOT while there is no complete installation");
return;
@@ -439,12 +445,16 @@ public class DynamicSystemInstallationService extends Service
private void executeNotifyIfInUseCommand() {
switch (getStatus()) {
case STATUS_IN_USE:
- startForeground(NOTIFICATION_ID,
- buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+ if (!mHideNotification) {
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED));
+ }
break;
case STATUS_READY:
- startForeground(NOTIFICATION_ID,
- buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+ if (!mHideNotification) {
+ startForeground(NOTIFICATION_ID,
+ buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED));
+ }
break;
case STATUS_IN_PROGRESS:
break;
@@ -454,6 +464,16 @@ public class DynamicSystemInstallationService extends Service
}
}
+ private void executeHideNotificationCommand() {
+ mHideNotification = true;
+ switch (getStatus()) {
+ case STATUS_IN_USE:
+ case STATUS_READY:
+ stopForeground(STOP_FOREGROUND_REMOVE);
+ break;
+ }
+ }
+
private void resetTaskAndStop() {
resetTaskAndStop(/* removeNotification= */ false);
}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index a41399fb0d0d..42b620abe734 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -803,7 +803,7 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> {
return mIsCompleted;
}
- boolean commit() {
- return mDynSystem.setEnable(true, true);
+ boolean commit(boolean oneShot) {
+ return mDynSystem.setEnable(true, oneShot);
}
}
diff --git a/packages/PrintSpooler/tests/outofprocess/Android.bp b/packages/PrintSpooler/tests/outofprocess/Android.bp
index 69a1d7fa59e4..ef0d122c7273 100644
--- a/packages/PrintSpooler/tests/outofprocess/Android.bp
+++ b/packages/PrintSpooler/tests/outofprocess/Android.bp
@@ -31,7 +31,7 @@ android_test {
libs: ["android.test.runner.stubs"],
static_libs: [
"androidx.test.rules",
- "ub-uiautomator",
+ "androidx.test.uiautomator_uiautomator",
"mockito-target-minus-junit4",
"print-test-util-lib",
],
diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
index 132545b2d5d2..1509b7077046 100644
--- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
+++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java
@@ -35,15 +35,15 @@ import android.print.test.services.AddPrintersActivity;
import android.print.test.services.FirstPrintService;
import android.print.test.services.PrinterDiscoverySessionCallbacks;
import android.print.test.services.StubbablePrinterDiscoverySession;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiSelector;
-import android.support.test.uiautomator.Until;
import android.util.Log;
import androidx.test.filters.LargeTest;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+import androidx.test.uiautomator.Until;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
index 8cda37665035..bf24c86b8d8b 100644
--- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
+++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java
@@ -59,32 +59,36 @@ public class FooterPreference extends Preference {
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
TextView title = holder.itemView.findViewById(android.R.id.title);
- if (!TextUtils.isEmpty(mContentDescription)) {
+ if (title != null && !TextUtils.isEmpty(mContentDescription)) {
title.setContentDescription(mContentDescription);
}
TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more);
- if (learnMore != null && mLearnMoreListener != null) {
- learnMore.setVisibility(View.VISIBLE);
- if (TextUtils.isEmpty(mLearnMoreText)) {
- mLearnMoreText = learnMore.getText();
+ if (learnMore != null) {
+ if (mLearnMoreListener != null) {
+ learnMore.setVisibility(View.VISIBLE);
+ if (TextUtils.isEmpty(mLearnMoreText)) {
+ mLearnMoreText = learnMore.getText();
+ } else {
+ learnMore.setText(mLearnMoreText);
+ }
+ SpannableString learnMoreText = new SpannableString(mLearnMoreText);
+ if (mLearnMoreSpan != null) {
+ learnMoreText.removeSpan(mLearnMoreSpan);
+ }
+ mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
+ learnMoreText.setSpan(mLearnMoreSpan, 0,
+ learnMoreText.length(), 0);
+ learnMore.setText(learnMoreText);
} else {
- learnMore.setText(mLearnMoreText);
+ learnMore.setVisibility(View.GONE);
}
- SpannableString learnMoreText = new SpannableString(mLearnMoreText);
- if (mLearnMoreSpan != null) {
- learnMoreText.removeSpan(mLearnMoreSpan);
- }
- mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener);
- learnMoreText.setSpan(mLearnMoreSpan, 0,
- learnMoreText.length(), 0);
- learnMore.setText(learnMoreText);
- } else {
- learnMore.setVisibility(View.GONE);
}
View icon = holder.itemView.findViewById(R.id.icon_frame);
- icon.setVisibility(mIconVisibility);
+ if (icon != null) {
+ icon.setVisibility(mIconVisibility);
+ }
}
@Override
diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS
index 1b3020af3d78..81340f561bc1 100644
--- a/packages/SettingsLib/OWNERS
+++ b/packages/SettingsLib/OWNERS
@@ -3,9 +3,8 @@ dsandler@android.com
edgarwang@google.com
evanlaird@google.com
juliacr@google.com
-lijun@google.com
-songchenxi@google.com
yantingyang@google.com
+ykhung@google.com
# Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS)
per-file *.xml=*
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
index 47ac2df67c76..e07a6298a627 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt
@@ -69,6 +69,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -460,6 +461,9 @@ private fun TopAppBarLayout(
ProvideTextStyle(value = titleTextStyle) {
CompositionLocalProvider(
LocalContentColor provides titleContentColor,
+ // Disable the title font scaling by only passing the density but not the
+ // font scale.
+ LocalDensity provides Density(density = LocalDensity.current.density),
content = title
)
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
index 30a4349e942f..6f2c38caa3bc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt
@@ -18,11 +18,11 @@ package com.android.settingslib.spa.widget.scaffold
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
+import androidx.compose.material3.minimumInteractiveComponentSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -46,7 +46,7 @@ internal fun SettingsTab(
selected = selected,
onClick = onClick,
modifier = Modifier
- .height(48.dp)
+ .minimumInteractiveComponentSize()
.padding(horizontal = 4.dp, vertical = 6.dp)
.clip(SettingsShape.CornerMedium)
.background(
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 5342def0003d..2c3e58ece8e0 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
@@ -20,9 +20,12 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -33,7 +36,11 @@ import kotlinx.coroutines.runBlocking
*/
internal interface AppListRepository {
/** Loads the list of [ApplicationInfo]. */
- suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo>
+ suspend fun loadApps(
+ userId: Int,
+ showInstantApps: Boolean = false,
+ matchAnyUserForAdmin: Boolean = false,
+ ): List<ApplicationInfo>
/** Gets the flow of predicate that could used to filter system app. */
fun showSystemPredicate(
@@ -61,10 +68,12 @@ object AppListRepositoryUtil {
internal class AppListRepositoryImpl(private val context: Context) : AppListRepository {
private val packageManager = context.packageManager
+ private val userManager = context.userManager
override suspend fun loadApps(
userId: Int,
showInstantApps: Boolean,
+ matchAnyUserForAdmin: Boolean,
): List<ApplicationInfo> = coroutineScope {
val hiddenSystemModulesDeferred = async {
packageManager.getInstalledModules(0)
@@ -75,12 +84,8 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo
val hideWhenDisabledPackagesDeferred = async {
context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)
}
- val flags = PackageManager.ApplicationInfoFlags.of(
- (PackageManager.MATCH_DISABLED_COMPONENTS or
- PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
- )
val installedApplicationsAsUser =
- packageManager.getInstalledApplicationsAsUser(flags, userId)
+ getInstalledApplications(userId, matchAnyUserForAdmin)
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await()
@@ -89,6 +94,46 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo
}
}
+ private suspend fun getInstalledApplications(
+ userId: Int,
+ matchAnyUserForAdmin: Boolean,
+ ): List<ApplicationInfo> {
+ val regularFlags = ApplicationInfoFlags.of(
+ (PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
+ )
+ return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) {
+ packageManager.getInstalledApplicationsAsUser(regularFlags, userId)
+ } else {
+ coroutineScope {
+ val deferredPackageNamesInChildProfiles =
+ userManager.getProfileIdsWithDisabled(userId)
+ .filter { it != userId }
+ .map {
+ async {
+ packageManager.getInstalledApplicationsAsUser(regularFlags, it)
+ .map { it.packageName }
+ }
+ }
+ val adminFlags = ApplicationInfoFlags.of(
+ PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value
+ )
+ val allInstalledApplications =
+ packageManager.getInstalledApplicationsAsUser(adminFlags, userId)
+ val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles
+ .awaitAll()
+ .flatten()
+ .toSet()
+ // If an app is for a child profile and not installed on the owner, not display as
+ // 'not installed for this user' in the owner. This will prevent duplicates of work
+ // only apps showing up in the personal profile.
+ allInstalledApplications.filter {
+ it.installed || it.packageName !in packageNamesInChildProfiles
+ }
+ }
+ }
+ }
+
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 889604209b08..bd99ebdfa3e5 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -80,12 +80,15 @@ internal open class AppListViewModelImpl<T : AppRecord>(
private val scope = viewModelScope + Dispatchers.IO
private val userSubGraphsFlow = appListConfig.flow.map { config ->
- config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) }
+ config.userIds.map { userId ->
+ UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin)
+ }
}.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
private inner class UserSubGraph(
private val userId: Int,
private val showInstantApps: Boolean,
+ private val matchAnyUserForAdmin: Boolean,
) {
private val userIdFlow = flowOf(userId)
@@ -110,7 +113,8 @@ internal open class AppListViewModelImpl<T : AppRecord>(
fun reloadApps() {
scope.launch {
- appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps)
+ appsStateFlow.value =
+ appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin)
}
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index f4a6b59d4c4b..066db3475b36 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -62,6 +62,7 @@ private const val CONTENT_TYPE_HEADER = "header"
data class AppListConfig(
val userIds: List<Int>,
val showInstantApps: Boolean,
+ val matchAnyUserForAdmin: Boolean,
)
data class AppListState(
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 2ebbe8aab809..89bfa0eb646b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -38,6 +38,7 @@ fun <T : AppRecord> AppListPage(
title: String,
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
+ matchAnyUserForAdmin: Boolean = false,
primaryUserOnly: Boolean = false,
noItemMessage: String? = null,
moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
@@ -59,6 +60,7 @@ fun <T : AppRecord> AppListPage(
config = AppListConfig(
userIds = userGroup.userInfos.map { it.id },
showInstantApps = showInstantApps,
+ matchAnyUserForAdmin = matchAnyUserForAdmin,
),
listModel = listModel,
state = AppListState(
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 57972edc6d3a..b732a6a4495e 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
@@ -23,9 +23,13 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.PackageManager.ResolveInfoFlags
import android.content.pm.ResolveInfo
+import android.content.pm.UserInfo
import android.content.res.Resources
+import android.os.UserManager
+import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.internal.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.first
@@ -35,10 +39,13 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.Spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever
@@ -49,8 +56,8 @@ class AppListRepositoryTest {
@get:Rule
val mockito: MockitoRule = MockitoJUnit.rule()
- @Mock
- private lateinit var context: Context
+ @Spy
+ private val context: Context = ApplicationProvider.getApplicationContext()
@Mock
private lateinit var resources: Resources
@@ -58,6 +65,9 @@ class AppListRepositoryTest {
@Mock
private lateinit var packageManager: PackageManager
+ @Mock
+ private lateinit var userManager: UserManager
+
private lateinit var repository: AppListRepository
@Before
@@ -66,36 +76,116 @@ class AppListRepositoryTest {
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(emptyArray())
whenever(context.packageManager).thenReturn(packageManager)
+ whenever(context.userManager).thenReturn(userManager)
whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
whenever(
- packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt())
).thenReturn(emptyList())
+ whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply {
+ flags = UserInfo.FLAG_ADMIN
+ })
+ whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID))
+ .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID))
repository = AppListRepositoryImpl(context)
}
- private fun mockInstalledApplications(apps: List<ApplicationInfo>) {
+ private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) {
whenever(
- packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+ packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId))
).thenReturn(apps)
}
@Test
fun loadApps_notShowInstantApps() = runTest {
- mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ showInstantApps = false,
+ )
- assertThat(appListFlow).containsExactly(NORMAL_APP)
+ assertThat(appList).containsExactly(NORMAL_APP)
}
@Test
fun loadApps_showInstantApps() = runTest {
- mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP))
+ mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ showInstantApps = true,
+ )
+
+ assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP)
+ }
+
+ @Test
+ fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = false,
+ )
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+ verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+ assertThat(flags.value.value).isEqualTo(
+ PackageManager.MATCH_DISABLED_COMPONENTS or
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ )
+ }
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true)
+ @Test
+ fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest {
+ mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = true,
+ )
+
+ assertThat(appList).containsExactly(NORMAL_APP)
+ val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java)
+ verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID))
+ assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L)
+ }
+
+ @Test
+ fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest {
+ val managedProfileOnlyPackageName = "installed.on.managed.profile.only"
+ mockInstalledApplications(listOf(ApplicationInfo().apply {
+ packageName = managedProfileOnlyPackageName
+ }), ADMIN_USER_ID)
+ mockInstalledApplications(listOf(ApplicationInfo().apply {
+ packageName = managedProfileOnlyPackageName
+ flags = ApplicationInfo.FLAG_INSTALLED
+ }), MANAGED_PROFILE_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = true,
+ )
- assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP)
+ assertThat(appList).isEmpty()
+ }
+
+ @Test
+ fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest {
+ val secondaryUserOnlyApp = ApplicationInfo().apply {
+ packageName = "installed.on.secondary.user.only"
+ }
+ mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID)
+ mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID)
+
+ val appList = repository.loadApps(
+ userId = ADMIN_USER_ID,
+ matchAnyUserForAdmin = true,
+ )
+
+ assertThat(appList).containsExactly(secondaryUserOnlyApp)
}
@Test
@@ -106,11 +196,11 @@ class AppListRepositoryTest {
}
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).isEmpty()
+ assertThat(appList).isEmpty()
}
@Test
@@ -122,11 +212,11 @@ class AppListRepositoryTest {
}
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).isEmpty()
+ assertThat(appList).isEmpty()
}
@Test
@@ -137,11 +227,11 @@ class AppListRepositoryTest {
}
whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames))
.thenReturn(arrayOf(app.packageName))
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).containsExactly(app)
+ assertThat(appList).containsExactly(app)
}
@Test
@@ -151,11 +241,11 @@ class AppListRepositoryTest {
enabled = false
enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
}
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).containsExactly(app)
+ assertThat(appList).containsExactly(app)
}
@Test
@@ -164,11 +254,11 @@ class AppListRepositoryTest {
packageName = "disabled"
enabled = false
}
- mockInstalledApplications(listOf(app))
+ mockInstalledApplications(listOf(app), ADMIN_USER_ID)
- val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false)
+ val appList = repository.loadApps(userId = ADMIN_USER_ID)
- assertThat(appListFlow).isEmpty()
+ assertThat(appList).isEmpty()
}
@Test
@@ -219,7 +309,11 @@ class AppListRepositoryTest {
val app = IN_LAUNCHER_APP
whenever(
- packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ packageManager.queryIntentActivitiesAsUser(
+ any(),
+ any<ResolveInfoFlags>(),
+ eq(ADMIN_USER_ID)
+ )
).thenReturn(listOf(resolveInfoOf(packageName = app.packageName)))
val showSystemPredicate = getShowSystemPredicate(showSystem = false)
@@ -229,12 +323,16 @@ class AppListRepositoryTest {
@Test
fun getSystemPackageNames_returnExpectedValues() = runTest {
- mockInstalledApplications(listOf(
- NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP))
+ mockInstalledApplications(
+ apps = listOf(
+ NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+ ),
+ userId = ADMIN_USER_ID,
+ )
val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames(
context = context,
- userId = USER_ID,
+ userId = ADMIN_USER_ID,
showInstantApps = false,
)
@@ -243,12 +341,13 @@ class AppListRepositoryTest {
private suspend fun getShowSystemPredicate(showSystem: Boolean) =
repository.showSystemPredicate(
- userIdFlow = flowOf(USER_ID),
+ userIdFlow = flowOf(ADMIN_USER_ID),
showSystemFlow = flowOf(showSystem),
).first()
private companion object {
- const val USER_ID = 0
+ const val ADMIN_USER_ID = 0
+ const val MANAGED_PROFILE_USER_ID = 11
val NORMAL_APP = ApplicationInfo().apply {
packageName = "normal"
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index 4f0cddef078b..36d9db5ef251 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -85,7 +85,11 @@ class AppListViewModelTest {
}
private object FakeAppListRepository : AppListRepository {
- override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP)
+ override suspend fun loadApps(
+ userId: Int,
+ showInstantApps: Boolean,
+ matchAnyUserForAdmin: Boolean,
+ ) = listOf(APP)
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
@@ -112,7 +116,11 @@ class AppListViewModelTest {
const val USER_ID = 0
const val PACKAGE_NAME = "package.name"
const val LABEL = "Label"
- val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false)
+ val CONFIG = AppListConfig(
+ userIds = listOf(USER_ID),
+ showInstantApps = false,
+ matchAnyUserForAdmin = false,
+ )
val APP = ApplicationInfo().apply {
packageName = PACKAGE_NAME
}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
index a99d02de0df0..241a134b6a76 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -114,7 +114,11 @@ class AppListTest {
) {
composeTestRule.setContent {
AppListInput(
- config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false),
+ config = AppListConfig(
+ userIds = listOf(USER_ID),
+ showInstantApps = false,
+ matchAnyUserForAdmin = false,
+ ),
listModel = TestAppListModel(enableGrouping = enableGrouping),
state = AppListState(
showSystem = false.toState(),
diff --git a/packages/SettingsLib/res/drawable/ic_dock_device.xml b/packages/SettingsLib/res/drawable/ic_dock_device.xml
new file mode 100644
index 000000000000..96a4900f361f
--- /dev/null
+++ b/packages/SettingsLib/res/drawable/ic_dock_device.xml
@@ -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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="#000000"
+ android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM120,720Q87,720 63.5,696.5Q40,673 40,640L60,160Q60,127 83.5,103.5Q107,80 140,80L820,80Q853,80 876.5,103.5Q900,127 900,160L920,640Q920,673 896.5,696.5Q873,720 840,720L120,720ZM120,640L840,640Q840,640 840,640Q840,640 840,640L820,160Q820,160 820,160Q820,160 820,160L140,160Q140,160 140,160Q140,160 140,160L120,640Q120,640 120,640Q120,640 120,640ZM320,880Q259,880 209.5,850Q160,820 160,765L160,720L240,720L240,760Q253,780 274.5,790Q296,800 320,800L640,800Q664,800 685.5,790.5Q707,781 720,761L720,720L800,720L800,765Q800,820 750.5,850Q701,880 640,880L320,880ZM480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Q480,400 480,400Q480,400 480,400L480,400Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index e22f3f0e720d..5fbb4c3e5712 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -37,8 +37,6 @@ import com.android.settingslib.Utils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
@@ -287,16 +285,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
return defaultValue;
}
- try {
- Method method = mService.getClass().getDeclaredMethod("getDeviceSideInternal",
- BluetoothDevice.class);
- method.setAccessible(true);
- return (int) method.invoke(mService, device);
- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
- Log.e(TAG, "fail to get getDeviceSideInternal\n" + e.toString() + "\n"
- + Log.getStackTraceString(new Throwable()));
- return defaultValue;
- }
+ return mService.getDeviceSide(device);
}
/**
@@ -313,17 +302,7 @@ public class HearingAidProfile implements LocalBluetoothProfile {
return defaultValue;
}
- try {
- Method method = mService.getClass().getDeclaredMethod("getDeviceModeInternal",
- BluetoothDevice.class);
- method.setAccessible(true);
- return (int) method.invoke(mService, device);
- } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
- Log.e(TAG, "fail to get getDeviceModeInternal\n" + e.toString() + "\n"
- + Log.getStackTraceString(new Throwable()));
-
- return defaultValue;
- }
+ return mService.getDeviceMode(device);
}
public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
new file mode 100644
index 000000000000..5326e73a3f82
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverLogging.java
@@ -0,0 +1,53 @@
+/*
+ * 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.fuelgauge;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utilities related to battery saver logging.
+ */
+public final class BatterySaverLogging {
+ /**
+ * Record the reason while enabling power save mode manually.
+ * See {@link SaverManualEnabledReason} for all available states.
+ */
+ public static final String EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON =
+ "extra_power_save_mode_manual_enabled_reason";
+
+ /** Broadcast action to record battery saver manual enabled reason. */
+ public static final String ACTION_SAVER_MANUAL_ENABLED_REASON =
+ "com.android.settingslib.fuelgauge.ACTION_SAVER_MANUAL_ENABLED_REASON";
+
+ /** An interface for the battery saver manual enable reason. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SAVER_ENABLED_UNKNOWN, SAVER_ENABLED_CONFIRMATION, SAVER_ENABLED_VOICE,
+ SAVER_ENABLED_SETTINGS, SAVER_ENABLED_QS, SAVER_ENABLED_LOW_WARNING,
+ SAVER_ENABLED_SEVERE_WARNING})
+ public @interface SaverManualEnabledReason {}
+
+ public static final int SAVER_ENABLED_UNKNOWN = 0;
+ public static final int SAVER_ENABLED_CONFIRMATION = 1;
+ public static final int SAVER_ENABLED_VOICE = 2;
+ public static final int SAVER_ENABLED_SETTINGS = 3;
+ public static final int SAVER_ENABLED_QS = 4;
+ public static final int SAVER_ENABLED_LOW_WARNING = 5;
+ public static final int SAVER_ENABLED_SEVERE_WARNING = 6;
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 52f3111d967c..e28ada4771c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -16,6 +16,10 @@
package com.android.settingslib.fuelgauge;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.ACTION_SAVER_MANUAL_ENABLED_REASON;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -116,9 +120,9 @@ public class BatterySaverUtils {
* @return true if the request succeeded.
*/
public static synchronized boolean setPowerSaveMode(Context context,
- boolean enable, boolean needFirstTimeWarning) {
+ boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) {
if (DEBUG) {
- Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF"));
+ Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason);
}
final ContentResolver cr = context.getContentResolver();
@@ -145,8 +149,10 @@ public class BatterySaverUtils {
&& Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0
&& Secure.getInt(cr,
Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) {
- showAutoBatterySaverSuggestion(context, confirmationExtras);
+ sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION,
+ confirmationExtras);
}
+ recordBatterySaverEnabledReason(context, reason);
}
return true;
@@ -175,21 +181,23 @@ public class BatterySaverUtils {
// Already shown.
return false;
}
- context.sendBroadcast(
- getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, extras));
+ sendSystemUiBroadcast(context, ACTION_SHOW_START_SAVER_CONFIRMATION, extras);
return true;
}
- private static void showAutoBatterySaverSuggestion(Context context, Bundle extras) {
- context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, extras));
+ private static void recordBatterySaverEnabledReason(Context context,
+ @SaverManualEnabledReason int reason) {
+ final Bundle enabledReasonExtras = new Bundle(1);
+ enabledReasonExtras.putInt(EXTRA_POWER_SAVE_MODE_MANUAL_ENABLED_REASON, reason);
+ sendSystemUiBroadcast(context, ACTION_SAVER_MANUAL_ENABLED_REASON, enabledReasonExtras);
}
- private static Intent getSystemUiBroadcast(String action, Bundle extras) {
- final Intent i = new Intent(action);
- i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- i.setPackage(SYSUI_PACKAGE);
- i.putExtras(extras);
- return i;
+ private static void sendSystemUiBroadcast(Context context, String action, Bundle extras) {
+ final Intent intent = new Intent(action);
+ intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ intent.setPackage(SYSUI_PACKAGE);
+ intent.putExtras(extras);
+ context.sendBroadcast(intent);
}
private static void setBatterySaverConfirmationAcknowledged(Context context) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
index 6c0eab3fb016..e38e041a87dc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/DeviceIconUtil.java
@@ -55,7 +55,7 @@ public class DeviceIconUtil {
new Device(
AudioDeviceInfo.TYPE_DOCK,
MediaRoute2Info.TYPE_DOCK,
- R.drawable.ic_headphone),
+ R.drawable.ic_dock_device),
new Device(
AudioDeviceInfo.TYPE_HDMI,
MediaRoute2Info.TYPE_HDMI,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index ad022a63eaf6..cb386fbff4ed 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,7 @@
package com.android.settingslib.fuelgauge;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
@@ -72,7 +73,8 @@ public class BatterySaverUtilsTest {
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isFalse();
verify(mMockContext, times(1)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean());
@@ -92,7 +94,8 @@ public class BatterySaverUtilsTest {
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -111,7 +114,8 @@ public class BatterySaverUtilsTest {
Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1);
Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1);
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -129,7 +133,8 @@ public class BatterySaverUtilsTest {
Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null");
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true));
@@ -147,7 +152,8 @@ public class BatterySaverUtilsTest {
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
// When disabling, needFirstTimeWarning doesn't matter.
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
@@ -166,7 +172,8 @@ public class BatterySaverUtilsTest {
Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null");
// When disabling, needFirstTimeWarning doesn't matter.
- assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue();
+ assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true,
+ SAVER_ENABLED_UNKNOWN)).isTrue();
verify(mMockContext, times(0)).sendBroadcast(any(Intent.class));
verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false));
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
index 55125c53b4c9..049c90e971a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java
@@ -18,6 +18,9 @@ package com.android.settingslib.widget;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@@ -87,4 +90,52 @@ public class FooterPreferenceTest {
assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE);
}
+
+ @Test
+ public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() {
+ PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+ when(viewHolder.findViewById(R.id.title)).thenReturn(null);
+
+ Throwable actualThrowable = null;
+ try {
+ mFooterPreference.onBindViewHolder(viewHolder);
+ } catch (Throwable throwable) {
+ actualThrowable = throwable;
+ }
+
+ assertThat(actualThrowable).isNull();
+ }
+
+ @Test
+ public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() {
+ PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+ when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null);
+
+ Throwable actualThrowable = null;
+ try {
+ mFooterPreference.onBindViewHolder(viewHolder);
+ } catch (Throwable throwable) {
+ actualThrowable = throwable;
+ }
+
+ assertThat(actualThrowable).isNull();
+ }
+
+ @Test
+ public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() {
+ PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests(
+ LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null)));
+ when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null);
+
+ Throwable actualThrowable = null;
+ try {
+ mFooterPreference.onBindViewHolder(viewHolder);
+ } catch (Throwable throwable) {
+ actualThrowable = throwable;
+ }
+
+ assertThat(actualThrowable).isNull();
+ }
}
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 59cd7a051fad..a93cd62e6301 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -43,6 +43,8 @@
<bool name="def_install_non_market_apps">false</bool>
<!-- 0 == off, 3 == on -->
<integer name="def_location_mode">3</integer>
+ <!-- 0 == off, 1 == on-->
+ <integer name="def_paired_device_location_mode">1</integer>
<bool name="assisted_gps_enabled">true</bool>
<bool name="def_netstats_enabled">true</bool>
<bool name="def_usb_mass_storage_enabled">true</bool>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index e50f52229a16..41ce58eb7b4e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -103,5 +103,7 @@ public class GlobalSettings {
Settings.Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS,
Settings.Global.HDR_CONVERSION_MODE,
Settings.Global.HDR_FORCE_CONVERSION_TYPE,
+ Settings.Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV,
+ Settings.Global.Wearable.REDUCE_MOTION,
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index d5386c1d75ac..a1c01723ad55 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -285,7 +285,6 @@ public class GlobalSettingsValidators {
}));
VALIDATORS.put(Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.SIDE_BUTTON, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.Wearable.BUTTON_SET, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.ANDROID_WEAR_VERSION, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.SYSTEM_CAPABILITIES, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.Wearable.SYSTEM_EDITION, ANY_INTEGER_VALIDATOR);
@@ -345,6 +344,7 @@ public class GlobalSettingsValidators {
String.valueOf(Global.Wearable.HFP_CLIENT_DISABLED)
}));
VALIDATORS.put(Global.Wearable.COMPANION_OS_VERSION, ANY_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.COMPANION_APP_NAME, ANY_STRING_VALIDATOR);
VALIDATORS.put(Global.Wearable.ENABLE_ALL_LANGUAGES, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.OEM_SETUP_VERSION, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(
@@ -404,16 +404,6 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_HARD_MODE, BOOLEAN_VALIDATOR);
- VALIDATORS.put(
- Global.Wearable.EARLY_UPDATES_STATUS,
- new DiscreteValueValidator(
- new String[] {
- String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_NOT_STARTED),
- String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_STARTED),
- String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SUCCESS),
- String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_SKIPPED),
- String.valueOf(Global.Wearable.EARLY_UPDATES_STATUS_ABORTED),
- }));
VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS,
@@ -423,5 +413,22 @@ public class GlobalSettingsValidators {
String.valueOf(Global.Wearable.UPGRADE_DATA_MIGRATION_PENDING),
String.valueOf(Global.Wearable.UPGRADE_DATA_MIGRATION_DONE)
}));
+ VALIDATORS.put(Global.Wearable.DISABLE_AOD_WHILE_PLUGGED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.NETWORK_LOCATION_OPT_IN, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_STATUS,
+ new InclusiveIntegerRangeValidator(
+ Global.Wearable.PHONE_SWITCHING_STATUS_NOT_STARTED,
+ Global.Wearable.PHONE_SWITCHING_STATUS_IN_PROGRESS_MIGRATION_SUCCESS));
+ VALIDATORS.put(Global.Wearable.REDUCE_MOTION, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.RTL_SWIPE_TO_DISMISS_ENABLED_DEV, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Global.Wearable.TETHER_CONFIG_STATE,
+ new DiscreteValueValidator(
+ new String[] {
+ String.valueOf(Global.Wearable.TETHERED_CONFIG_UNKNOWN),
+ String.valueOf(Global.Wearable.TETHERED_CONFIG_STANDALONE),
+ String.valueOf(Global.Wearable.TETHERED_CONFIG_TETHERED)
+ }));
+ VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b0a19270018a..284b06b86cb6 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -3748,7 +3748,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 217;
+ private static final int SETTINGS_VERSION = 218;
private final int mUserId;
@@ -5334,74 +5334,73 @@ public class SettingsProvider extends ContentProvider {
if (currentVersion == 203) {
// Version 203: initialize entries migrated from wear settings provide.
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.HAS_PAY_TOKENS, false);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.GMS_CHECKIN_TIMEOUT_MIN, 6);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.HOTWORD_DETECTION_ENABLED,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_hotwordDetectionEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.SMART_REPLIES_ENABLED, true);
Setting locationMode =
getSecureSettingsLocked(userId).getSettingLocked(Secure.LOCATION_MODE);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.OBTAIN_PAIRED_DEVICE_LOCATION,
!locationMode.isNull()
&& !Integer.toString(Secure.LOCATION_MODE_OFF)
.equals(locationMode.getValue()));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.PHONE_PLAY_STORE_AVAILABILITY,
Global.Wearable.PHONE_PLAY_STORE_AVAILABILITY_UNKNOWN);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.BUG_REPORT,
"user".equals(Build.TYPE) // is user build?
? Global.Wearable.BUG_REPORT_DISABLED
: Global.Wearable.BUG_REPORT_ENABLED);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.SMART_ILLUMINATE_ENABLED,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_smartIlluminateEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.CLOCKWORK_AUTO_TIME,
Global.Wearable.SYNC_TIME_FROM_PHONE);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE,
Global.Wearable.SYNC_TIME_ZONE_FROM_PHONE);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.CLOCKWORK_24HR_TIME, false);
- initGlobalSettingsDefaultValForWearLocked(Global.Wearable.AUTO_WIFI, true);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(Global.Wearable.AUTO_WIFI, true);
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.WIFI_POWER_SAVE,
getContext()
.getResources()
.getInteger(
R.integer
.def_wearable_offChargerWifiUsageLimitMinutes));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.ALT_BYPASS_WIFI_REQUIREMENT_TIME_MILLIS, 0L);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.SETUP_SKIPPED, Global.Wearable.SETUP_SKIPPED_UNKNOWN);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.LAST_CALL_FORWARD_ACTION,
Global.Wearable.CALL_FORWARD_NO_LAST_ACTION);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.MUTE_WHEN_OFF_BODY_ENABLED,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_muteWhenOffBodyEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.WEAR_OS_VERSION_STRING, "");
- initGlobalSettingsDefaultValForWearLocked(Global.Wearable.BUTTON_SET, false);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.SIDE_BUTTON,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_sideButtonPresent));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.ANDROID_WEAR_VERSION,
Long.parseLong(
getContext()
@@ -5410,55 +5409,55 @@ public class SettingsProvider extends ContentProvider {
final int editionGlobal = 1;
final int editionLocal = 2;
boolean isLe = getContext().getPackageManager().hasSystemFeature("cn.google");
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.SYSTEM_EDITION, isLe ? editionLocal : editionGlobal);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.SYSTEM_CAPABILITIES, getWearSystemCapabilities(isLe));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.WEAR_PLATFORM_MR_NUMBER,
SystemProperties.getInt("ro.cw_build.platform_mr", 0));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.MOBILE_SIGNAL_DETECTOR,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_mobileSignalDetectorAllowed));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.AMBIENT_ENABLED,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_ambientEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.AMBIENT_TILT_TO_WAKE,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_tiltToWakeEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.AMBIENT_LOW_BIT_ENABLED_DEV, false);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.AMBIENT_TOUCH_TO_WAKE,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_touchToWakeEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.AMBIENT_TILT_TO_BRIGHT,
getContext()
.getResources()
.getBoolean(R.bool.def_wearable_tiltToBrightEnabled));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Global.Wearable.DECOMPOSABLE_WATCHFACE, false);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.AMBIENT_FORCE_WHEN_DOCKED,
SystemProperties.getBoolean("ro.ambient.force_when_docked", false));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.AMBIENT_LOW_BIT_ENABLED,
SystemProperties.getBoolean("ro.ambient.low_bit_enabled", false));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.AMBIENT_PLUGGED_TIMEOUT_MIN,
SystemProperties.getInt("ro.ambient.plugged_timeout_min", -1));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE,
Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE_UNKNOWN);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
Settings.Global.Wearable.HFP_CLIENT_UNSET);
Setting disabledProfileSetting =
@@ -5468,7 +5467,7 @@ public class SettingsProvider extends ContentProvider {
disabledProfileSetting.isNull()
? 0
: Long.parseLong(disabledProfileSetting.getValue());
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.COMPANION_OS_VERSION,
Settings.Global.Wearable.COMPANION_OS_VERSION_UNDEFINED);
final boolean defaultBurnInProtectionEnabled =
@@ -5482,17 +5481,17 @@ public class SettingsProvider extends ContentProvider {
.config_enableBurnInProtection);
final boolean forceBurnInProtection =
SystemProperties.getBoolean("persist.debug.force_burn_in", false);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.BURN_IN_PROTECTION_ENABLED,
defaultBurnInProtectionEnabled || forceBurnInProtection);
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.CLOCKWORK_SYSUI_PACKAGE,
getContext()
.getResources()
.getString(
com.android.internal.R.string.config_wearSysUiPackage));
- initGlobalSettingsDefaultValForWearLocked(
+ initGlobalSettingsDefaultValLocked(
Settings.Global.Wearable.CLOCKWORK_SYSUI_MAIN_ACTIVITY,
getContext()
.getResources()
@@ -5622,63 +5621,16 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 210;
}
if (currentVersion == 210) {
- final SettingsState secureSettings = getSecureSettingsLocked(userId);
- final Setting currentSetting = secureSettings.getSettingLocked(
- Secure.STATUS_BAR_SHOW_VIBRATE_ICON);
- if (currentSetting.isNull()) {
- final int defaultValueVibrateIconEnabled = getContext().getResources()
- .getInteger(R.integer.def_statusBarVibrateIconEnabled);
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
- String.valueOf(defaultValueVibrateIconEnabled),
- null /* tag */, true /* makeDefault */,
- SettingsState.SYSTEM_PACKAGE_NAME);
- }
+ // Unused. Moved to version 217.
currentVersion = 211;
}
if (currentVersion == 211) {
- // Version 211: Set default value for
- // Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS
- final SettingsState secureSettings = getSecureSettingsLocked(userId);
- final Setting lockScreenUnseenSetting = secureSettings
- .getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS);
- if (lockScreenUnseenSetting.isNull()) {
- final boolean defSetting = getContext().getResources()
- .getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications);
- secureSettings.insertSettingOverrideableByRestoreLocked(
- Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- defSetting ? "1" : "0",
- null /* tag */,
- true /* makeDefault */,
- SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
+ // Unused. Moved to version 217.
currentVersion = 212;
}
if (currentVersion == 212) {
- final SettingsState globalSettings = getGlobalSettingsLocked();
- final SettingsState secureSettings = getSecureSettingsLocked(userId);
-
- final Setting bugReportInPowerMenu = globalSettings.getSettingLocked(
- Global.BUGREPORT_IN_POWER_MENU);
-
- if (!bugReportInPowerMenu.isNull()) {
- Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to "
- + bugReportInPowerMenu.getValue() + " in Secure settings.");
- secureSettings.insertSettingLocked(
- Secure.BUGREPORT_IN_POWER_MENU,
- bugReportInPowerMenu.getValue(), null /* tag */,
- false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
-
- // set global bug_report_in_power_menu setting to null since it's deprecated
- Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to null"
- + " in Global settings since it's deprecated.");
- globalSettings.insertSettingLocked(
- Global.BUGREPORT_IN_POWER_MENU, null /* value */, null /* tag */,
- true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
- }
-
+ // Unused. Moved to version 217.
currentVersion = 213;
}
@@ -5804,6 +5756,89 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 217;
}
+ if (currentVersion == 217) {
+ // Version 217: merge and rebase wear settings init logic.
+
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final SettingsState globalSettings = getGlobalSettingsLocked();
+
+ // Following init logic is moved from version 210 to this version in order to
+ // resolve version conflict with wear branch.
+ final Setting currentSetting = secureSettings.getSettingLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON);
+ if (currentSetting.isNull()) {
+ final int defaultValueVibrateIconEnabled = getContext().getResources()
+ .getInteger(R.integer.def_statusBarVibrateIconEnabled);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ String.valueOf(defaultValueVibrateIconEnabled),
+ null /* tag */, true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ // Set default value for Secure#LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS
+ // Following init logic is moved from version 211 to this version in order to
+ // resolve version conflict with wear branch.
+ final Setting lockScreenUnseenSetting = secureSettings
+ .getSettingLocked(Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS);
+ if (lockScreenUnseenSetting.isNull()) {
+ final boolean defSetting = getContext().getResources()
+ .getBoolean(R.bool.def_lock_screen_show_only_unseen_notifications);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ defSetting ? "1" : "0",
+ null /* tag */,
+ true /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ // Following init logic is moved from version 212 to this version in order to
+ // resolve version conflict with wear branch.
+ final Setting bugReportInPowerMenu = globalSettings.getSettingLocked(
+ Global.BUGREPORT_IN_POWER_MENU);
+
+ if (!bugReportInPowerMenu.isNull()) {
+ Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to "
+ + bugReportInPowerMenu.getValue() + " in Secure settings.");
+ secureSettings.insertSettingLocked(
+ Secure.BUGREPORT_IN_POWER_MENU,
+ bugReportInPowerMenu.getValue(), null /* tag */,
+ false /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+
+ // set global bug_report_in_power_menu setting to null since it's deprecated
+ Slog.i(LOG_TAG, "Setting bugreport_in_power_menu to null"
+ + " in Global settings since it's deprecated.");
+ globalSettings.insertSettingLocked(
+ Global.BUGREPORT_IN_POWER_MENU, null /* value */, null /* tag */,
+ true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ // Following init logic is rebased from wear OS branch.
+ // Initialize default value of tether configuration to unknown.
+ initGlobalSettingsDefaultValLocked(
+ Settings.Global.Wearable.TETHER_CONFIG_STATE,
+ Global.Wearable.TETHERED_CONFIG_UNKNOWN);
+ // Init paired device location setting from resources.
+ initGlobalSettingsDefaultValLocked(
+ Global.Wearable.OBTAIN_PAIRED_DEVICE_LOCATION,
+ getContext()
+ .getResources()
+ .getInteger(R.integer.def_paired_device_location_mode));
+ // Init media packages from resources.
+ final String mediaControlsPackage = getContext().getResources().getString(
+ com.android.internal.R.string.config_wearMediaControlsPackage);
+ final String mediaSessionsPackage = getContext().getResources().getString(
+ com.android.internal.R.string.config_wearMediaSessionsPackage);
+ initGlobalSettingsDefaultValLocked(
+ Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE,
+ mediaControlsPackage);
+ initGlobalSettingsDefaultValLocked(
+ Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE,
+ mediaSessionsPackage);
+
+ currentVersion = 218;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
@@ -5821,19 +5856,19 @@ public class SettingsProvider extends ContentProvider {
return currentVersion;
}
- private void initGlobalSettingsDefaultValForWearLocked(String key, boolean val) {
- initGlobalSettingsDefaultValForWearLocked(key, val ? "1" : "0");
+ private void initGlobalSettingsDefaultValLocked(String key, boolean val) {
+ initGlobalSettingsDefaultValLocked(key, val ? "1" : "0");
}
- private void initGlobalSettingsDefaultValForWearLocked(String key, int val) {
- initGlobalSettingsDefaultValForWearLocked(key, String.valueOf(val));
+ private void initGlobalSettingsDefaultValLocked(String key, int val) {
+ initGlobalSettingsDefaultValLocked(key, String.valueOf(val));
}
- private void initGlobalSettingsDefaultValForWearLocked(String key, long val) {
- initGlobalSettingsDefaultValForWearLocked(key, String.valueOf(val));
+ private void initGlobalSettingsDefaultValLocked(String key, long val) {
+ initGlobalSettingsDefaultValLocked(key, String.valueOf(val));
}
- private void initGlobalSettingsDefaultValForWearLocked(String key, String val) {
+ private void initGlobalSettingsDefaultValLocked(String key, String val) {
final SettingsState globalSettings = getGlobalSettingsLocked();
Setting currentSetting = globalSettings.getSettingLocked(key);
if (currentSetting.isNull()) {
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 19f1a86ec90c..a202e1614b67 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -627,7 +627,6 @@ public class SettingsBackupTest {
Settings.Global.Wearable.STEM_3_DATA,
Settings.Global.Wearable.STEM_3_DEFAULT_DATA,
Settings.Global.Wearable.WEAR_OS_VERSION_STRING,
- Settings.Global.Wearable.BUTTON_SET,
Settings.Global.Wearable.SIDE_BUTTON,
Settings.Global.Wearable.ANDROID_WEAR_VERSION,
Settings.Global.Wearable.SYSTEM_CAPABILITIES,
@@ -643,6 +642,7 @@ public class SettingsBackupTest {
Settings.Global.Wearable.PAIRED_DEVICE_OS_TYPE,
Settings.Global.Wearable.COMPANION_BLE_ROLE,
Settings.Global.Wearable.COMPANION_NAME,
+ Settings.Global.Wearable.COMPANION_APP_NAME,
Settings.Global.Wearable.USER_HFP_CLIENT_SETTING,
Settings.Global.Wearable.COMPANION_OS_VERSION,
Settings.Global.Wearable.ENABLE_ALL_LANGUAGES,
@@ -662,13 +662,21 @@ public class SettingsBackupTest {
Settings.Global.Wearable.SCREEN_UNLOCK_SOUND_ENABLED,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
- Settings.Global.Wearable.EARLY_UPDATES_STATUS,
Settings.Global.Wearable.RSB_WAKE_ENABLED,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_ENABLED,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_TYPE,
Settings.Global.Wearable.ACCESSIBILITY_VIBRATION_WATCH_SPEED,
- Settings.Global.Wearable.SCREENSHOT_ENABLED);
+ Settings.Global.Wearable.SCREENSHOT_ENABLED,
+ Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
+ Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
+ Settings.Global.Wearable.CUSTOM_COLOR_FOREGROUND,
+ Settings.Global.Wearable.CUSTOM_COLOR_BACKGROUND,
+ Settings.Global.Wearable.PHONE_SWITCHING_STATUS,
+ Settings.Global.Wearable.TETHER_CONFIG_STATE,
+ Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED,
+ Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE,
+ Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE);
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index fedfb43535cc..78d93bd19a15 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -58,7 +58,6 @@
<uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<uses-permission android:name="android.permission.BODY_SENSORS" />
- <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" />
<uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" />
<uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" />
<uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" />
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3007d4a79d13..7a1d9a3ad025 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -156,9 +156,10 @@ android_library {
"WifiTrackerLib",
"WindowManager-Shell",
"SystemUIAnimationLib",
+ "SystemUICommon",
+ "SystemUICustomizationLib",
"SystemUIPluginLib",
"SystemUISharedLib",
- "SystemUICustomizationLib",
"SystemUI-statsd",
"SettingsLib",
"androidx.core_core-ktx",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 36a0b5dd72b8..a00f401756f7 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -347,15 +347,6 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
- <!-- Intent Chooser -->
- <permission
- android:name="android.permission.ADD_CHOOSER_PINS"
- android:protectionLevel="signature" />
- <uses-permission android:name="android.permission.ADD_CHOOSER_PINS" />
- <permission
- android:name="android.permission.RECEIVE_CHOOSER_PIN_MIGRATION"
- android:protectionLevel="signature" />
-
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
@@ -993,7 +984,13 @@
android:exported="true"
android:excludeFromRecents="true"
android:resizeableActivity="false"
- android:theme="@android:style/Theme.NoDisplay" />
+ android:theme="@android:style/Theme.NoDisplay" >
+
+ <intent-filter>
+ <action android:name="com.android.systemui.action.LAUNCH_NOTE_TASK"/>
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
<!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
to specify an Android user when launching the default notes app. -->
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml
new file mode 100644
index 000000000000..1d67066028be
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/res/layout/preferences_action_bar.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/action_bar_title"
+ style="@style/TextAppearance.AppCompat.Title"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"
+ android:maxLines="5"/>
+</LinearLayout>
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index 02d279fa4962..5ed450abede5 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -16,6 +16,7 @@
package com.android.systemui.accessibility.accessibilitymenu.activity;
+import android.app.ActionBar;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -24,6 +25,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.provider.Browser;
import android.provider.Settings;
+import android.widget.TextView;
import android.view.View;
import androidx.annotation.Nullable;
@@ -46,6 +48,13 @@ public class A11yMenuSettingsActivity extends FragmentActivity {
.beginTransaction()
.replace(android.R.id.content, new A11yMenuPreferenceFragment())
.commit();
+
+ ActionBar actionBar = getActionBar();
+ actionBar.setDisplayShowCustomEnabled(true);
+ actionBar.setCustomView(R.layout.preferences_action_bar);
+ ((TextView) findViewById(R.id.action_bar_title)).setText(
+ getResources().getString(R.string.accessibility_menu_settings_name)
+ );
}
/**
diff --git a/packages/SystemUI/common/.gitignore b/packages/SystemUI/common/.gitignore
new file mode 100644
index 000000000000..f9a33dbbcc7e
--- /dev/null
+++ b/packages/SystemUI/common/.gitignore
@@ -0,0 +1,9 @@
+.idea/
+.gradle/
+gradle/
+build/
+gradlew*
+local.properties
+*.iml
+android.properties
+buildSrc \ No newline at end of file
diff --git a/core/tests/expresslog/Android.bp b/packages/SystemUI/common/Android.bp
index cab49a76a734..e36ada89b207 100644
--- a/core/tests/expresslog/Android.bp
+++ b/packages/SystemUI/common/Android.bp
@@ -15,33 +15,25 @@
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
}
-android_test {
- name: "ExpressLogTests",
+android_library {
+
+ name: "SystemUICommon",
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
],
static_libs: [
- "androidx.test.rules",
- "modules-utils-build",
- ],
-
- libs: [
- "android.test.base",
- "android.test.runner",
- ],
-
- platform_apis: true,
- test_suites: [
- "general-tests",
+ "androidx.core_core-ktx",
],
- certificate: "platform",
+ manifest: "AndroidManifest.xml",
+ kotlincflags: ["-Xjvm-default=all"],
}
diff --git a/core/tests/expresslog/AndroidManifest.xml b/packages/SystemUI/common/AndroidManifest.xml
index 94a39e06c974..6f757eb67d2e 100644
--- a/core/tests/expresslog/AndroidManifest.xml
+++ b/packages/SystemUI/common/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2023 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.
@@ -15,15 +16,6 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- android:installLocation="internalOnly"
- package="com.android.internal.expresslog" >
-
- <application >
- <uses-library android:name="android.test.runner" />
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.internal.expresslog"
- android:label="Telemetry Express Logging Helper Tests" />
+ package="com.android.systemui.common">
</manifest>
diff --git a/packages/SystemUI/common/OWNERS b/packages/SystemUI/common/OWNERS
new file mode 100644
index 000000000000..9b8a79e6f3c7
--- /dev/null
+++ b/packages/SystemUI/common/OWNERS
@@ -0,0 +1,2 @@
+darrellshi@google.com
+evanlaird@google.com
diff --git a/packages/SystemUI/common/README.md b/packages/SystemUI/common/README.md
new file mode 100644
index 000000000000..1cc5277aa83e
--- /dev/null
+++ b/packages/SystemUI/common/README.md
@@ -0,0 +1,5 @@
+# SystemUICommon
+
+`SystemUICommon` is a module within SystemUI that hosts standalone helper libraries. It is intended to be used by other modules, and therefore should not have other SystemUI dependencies to avoid circular dependencies.
+
+To maintain the structure of this module, please refrain from adding components at the top level. Instead, add them to specific sub-packages, such as `systemui/common/buffer/`. This will help to keep the module organized and easy to navigate.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
index 4773f54a079e..de49d1c2c5ee 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/util/RingBuffer.kt
+++ b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.plugins.util
+package com.android.systemui.common.buffer
import kotlin.math.max
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index a7e95b58a6e4..eaf3229bf0c8 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.PluginLifecycleManager
import com.android.systemui.plugins.PluginListener
import com.android.systemui.plugins.PluginManager
import com.android.systemui.util.Assert
+import java.io.PrintWriter
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineDispatcher
@@ -485,6 +486,14 @@ open class ClockRegistry(
return availableClocks[targetClockId]?.provider?.createClock(settings)
}
+ fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("ClockRegistry:")
+ pw.println(" settings = $settings")
+ for ((id, info) in availableClocks) {
+ pw.println(" availableClocks[$id] = $info")
+ }
+ }
+
private data class ClockInfo(
val metadata: ClockMetadata,
var provider: ClockProvider?,
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index fb1c454de70d..e306d4aac398 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -37,6 +37,7 @@ java_library {
"error_prone_annotations",
"PluginCoreLib",
"SystemUIAnimationLib",
+ "SystemUICommon",
],
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
index 322fc774e805..05630e795476 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -199,6 +199,9 @@ data class ClockConfig(
*/
val hasCustomPositionUpdatedAnimation: Boolean = false,
+ /** Transition to AOD should move smartspace like large clock instead of small clock */
+ val useAlternateSmartspaceAODTransition: Boolean = false,
+
/** True if the clock will react to tone changes in the seed color. */
val isReactiveToTone: Boolean = true,
)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
index 52dfc55c105d..2dd146c5134d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -4,7 +4,7 @@ import android.os.Bundle
import androidx.annotation.VisibleForTesting
class WeatherData
-private constructor(
+constructor(
val description: String,
val state: WeatherStateIcon,
val useCelsius: Boolean,
@@ -47,6 +47,7 @@ private constructor(
}
}
+ // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon
enum class WeatherStateIcon(val id: Int) {
UNKNOWN_ICON(0),
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
index 3e34885a6d9c..4a6e0b61ecc9 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt
@@ -18,7 +18,7 @@ package com.android.systemui.plugins.log
import android.os.Trace
import android.util.Log
-import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.common.buffer.RingBuffer
import com.google.errorprone.annotations.CompileTimeConstant
import java.io.PrintWriter
import java.util.concurrent.ArrayBlockingQueue
diff --git a/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
new file mode 100644
index 000000000000..61a1cb5f1d31
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25">
+ <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/>
+ <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/>
+ <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/>
+ <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866">
+ <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_palette.xml b/packages/SystemUI/res-keyguard/drawable/ic_palette.xml
new file mode 100644
index 000000000000..cbea369c0236
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_palette.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,395 112,322Q144,249 199.5,195Q255,141 329.5,110.5Q404,80 489,80Q568,80 639,106.5Q710,133 763.5,180Q817,227 848.5,291.5Q880,356 880,433Q880,541 817,603.5Q754,666 650,666L575,666Q557,666 544,680Q531,694 531,711Q531,738 545.5,757Q560,776 560,801Q560,839 539,859.5Q518,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM247,506Q267,506 282,491Q297,476 297,456Q297,436 282,421Q267,406 247,406Q227,406 212,421Q197,436 197,456Q197,476 212,491Q227,506 247,506ZM373,336Q393,336 408,321Q423,306 423,286Q423,266 408,251Q393,236 373,236Q353,236 338,251Q323,266 323,286Q323,306 338,321Q353,336 373,336ZM587,336Q607,336 622,321Q637,306 637,286Q637,266 622,251Q607,236 587,236Q567,236 552,251Q537,266 537,286Q537,306 552,321Q567,336 587,336ZM718,506Q738,506 753,491Q768,476 768,456Q768,436 753,421Q738,406 718,406Q698,406 683,421Q668,436 668,456Q668,476 683,491Q698,506 718,506ZM480,820Q491,820 495.5,815.5Q500,811 500,801Q500,787 485.5,775Q471,763 471,722Q471,676 501,641Q531,606 577,606L650,606Q726,606 773,561.5Q820,517 820,433Q820,301 720,220.5Q620,140 489,140Q343,140 241.5,238.5Q140,337 140,480Q140,621 239.5,720.5Q339,820 480,820Z"/>
+</vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
index 951d6fed0a17..f3325ecefada 100644
--- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
+++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml
@@ -104,4 +104,9 @@
android:fromId="@id/unlocked"
android:toId="@id/locked"
android:drawable="@drawable/unlocked_to_locked" />
+
+ <transition
+ android:fromId="@id/locked_fp"
+ android:toId="@id/locked"
+ android:drawable="@drawable/fp_to_locked" />
</animated-selector>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 2143fc4db852..edd3047203b3 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -21,19 +21,19 @@
<!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pin">Enter your PIN</string>
- <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_pin">Enter PIN</string>
<!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_pattern">Enter your pattern</string>
- <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_pattern">Draw pattern</string>
<!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] -->
<string name="keyguard_enter_your_password">Enter your password</string>
- <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] -->
+ <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] -->
<string name="keyguard_enter_password">Enter password</string>
<!-- Shown in the lock screen when there is SIM card IO error. -->
@@ -128,103 +128,103 @@
<!-- Message shown when user enters wrong pattern -->
<string name="kg_wrong_pattern">Wrong pattern</string>
- <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
<string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string>
<!-- Message shown when user enters wrong password -->
<string name="kg_wrong_password">Wrong password</string>
- <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] -->
<string name="kg_wrong_password_try_again">Wrong password. Try again.</string>
<!-- Message shown when user enters wrong PIN -->
<string name="kg_wrong_pin">Wrong PIN</string>
- <!-- Message shown when user enters wrong PIN [CHAR LIMIT=26] -->
+ <!-- Message shown when user enters wrong PIN [CHAR LIMIT=48] -->
<string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string>
- <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=52] -->
+ <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=70] -->
<string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string>
- <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=26] -->
+ <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=48] -->
<string name="kg_fp_not_recognized">Fingerprint not recognized</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=26] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=48] -->
<string name="bouncer_face_not_recognized">Face not recognized</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_pin">Try again or enter PIN</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_password">Try again or enter password</string>
- <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] -->
+ <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] -->
<string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string>
- <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] -->
<string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string>
- <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] -->
+ <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] -->
<string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string>
- <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string>
- <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] -->
+ <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] -->
<string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string>
- <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=52] -->
+ <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=70] -->
<string name="kg_prompt_unattended_update">Update will install during inactive hours</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] -->
<string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string>
- <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] -->
+ <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=76] -->
<string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string>
- <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=52] -->
+ <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=82] -->
<string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string>
- <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=52] -->
+ <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=70] -->
<string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string>
- <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=52] -->
+ <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=75] -->
<string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string>
- <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=52] -->
+ <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=70] -->
<string name="kg_trust_agent_disabled">Trust agent is unavailable</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string>
- <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] -->
+ <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] -->
<string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string>
- <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]-->
+ <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]-->
<string name="kg_too_many_failed_attempts_countdown">{count, plural,
=1 {Try again in # second.}
other {Try again in # seconds.}
@@ -296,13 +296,13 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
- <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string>
- <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string>
- <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] -->
+ <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] -->
<string name="kg_prompt_reason_restart_password">Password is required after device restarts</string>
<!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] -->
diff --git a/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml b/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml
new file mode 100644
index 000000000000..8c2937c8c483
--- /dev/null
+++ b/packages/SystemUI/res/anim/keyguard_settings_popup_ease_out_interpolator.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<pathInterpolator
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:controlX1="0.30"
+ android:controlY1="0.00"
+ android:controlX2="0.33"
+ android:controlY2="1.00" />
diff --git a/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml b/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml
new file mode 100644
index 000000000000..5fa88224968b
--- /dev/null
+++ b/packages/SystemUI/res/anim/long_press_lock_screen_popup_enter.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <scale
+ android:interpolator="@android:anim/accelerate_decelerate_interpolator"
+ android:duration="200"
+ android:fromXScale="0.5"
+ android:fromYScale="0.5"
+ android:toXScale="1.02"
+ android:toYScale="1.02"
+ android:pivotX="50%"
+ android:pivotY="50%" />
+
+ <scale
+ android:interpolator="@anim/keyguard_settings_popup_ease_out_interpolator"
+ android:startOffset="200"
+ android:duration="200"
+ android:fromXScale="1"
+ android:fromYScale="1"
+ android:toXScale="0.98"
+ android:toYScale="0.98"
+ android:pivotX="50%"
+ android:pivotY="50%" />
+
+ <alpha
+ android:interpolator="@android:anim/linear_interpolator"
+ android:duration="83"
+ android:fromAlpha="0"
+ android:toAlpha="1" />
+
+</set>
diff --git a/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml b/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml
new file mode 100644
index 000000000000..a6938defd4be
--- /dev/null
+++ b/packages/SystemUI/res/anim/long_press_lock_screen_popup_exit.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<set
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <scale
+ android:interpolator="@android:anim/accelerate_interpolator"
+ android:duration="233"
+ android:fromXScale="1"
+ android:fromYScale="1"
+ android:toXScale="0.5"
+ android:toYScale="0.5"
+ android:pivotX="50%"
+ android:pivotY="50%" />
+
+ <alpha
+ android:interpolator="@android:anim/linear_interpolator"
+ android:delay="150"
+ android:duration="83"
+ android:fromAlpha="1"
+ android:toAlpha="0" />
+
+</set>
diff --git a/packages/SystemUI/res/drawable/chipbar_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml
index 57221776a32f..7530f5ba244a 100644
--- a/packages/SystemUI/res/drawable/chipbar_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_background.xml
@@ -17,6 +17,6 @@
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <solid android:color="?androidprv:attr/colorAccentSecondary" />
+ <solid android:color="?androidprv:attr/materialColorSecondaryFixed" />
<corners android:radius="32dp" />
</shape>
diff --git a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
index 80c7207a35b6..a3832eef957f 100644
--- a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
+++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml
@@ -20,7 +20,7 @@
android:color="?android:textColorPrimary">
<item android:id="@android:id/background">
<shape>
- <solid android:color="@android:color/system_accent1_200"/>
+ <solid android:color="?androidprv:attr/materialColorPrimaryFixedDim"/>
<corners android:radius="24dp" />
</shape>
</item>
diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml
new file mode 100644
index 000000000000..02f5f92ec5fe
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml b/packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml
index da581061370b..da581061370b 100644
--- a/packages/SystemUI/res/drawable/ic_qs_no_calling_sms.xml
+++ b/packages/SystemUI/res/drawable/ic_shade_no_calling_sms.xml
diff --git a/packages/SystemUI/res/drawable/ic_qs_sim_card.xml b/packages/SystemUI/res/drawable/ic_shade_sim_card.xml
index 6eda929b54d3..6eda929b54d3 100644
--- a/packages/SystemUI/res/drawable/ic_qs_sim_card.xml
+++ b/packages/SystemUI/res/drawable/ic_shade_sim_card.xml
diff --git a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
index 3807b92ae39d..a0ceb81d42f4 100644
--- a/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
+++ b/packages/SystemUI/res/drawable/keyguard_settings_popup_menu_background.xml
@@ -17,17 +17,17 @@
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:color="?android:attr/colorControlHighlight">
+ android:color="#4d000000">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@android:color/white"/>
- <corners android:radius="28dp" />
+ <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
</shape>
</item>
<item>
<shape android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface" />
- <corners android:radius="28dp" />
+ <solid android:color="?androidprv:attr/materialColorOnBackground" />
+ <corners android:radius="@dimen/keyguard_affordance_fixed_radius" />
</shape>
</item>
</ripple>
diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml
index 762dcdced9c4..8fa975be43d2 100644
--- a/packages/SystemUI/res/layout/chipbar.xml
+++ b/packages/SystemUI/res/layout/chipbar.xml
@@ -49,15 +49,17 @@
android:alpha="0.0"
/>
+ <!-- LINT.IfChange textColor -->
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:textSize="@dimen/chipbar_text_size"
- android:textColor="@color/chipbar_text_and_icon_color"
+ style="@style/Chipbar.Text"
+ android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
android:alpha="0.0"
/>
+ <!-- LINT.ThenChange(systemui.temporarydisplay.chipbar.ChipbarInfo.kt) -->
<!-- At most one of [loading, failure_icon, undo] will be visible at a time. -->
<ImageView
@@ -66,7 +68,7 @@
android:layout_height="@dimen/chipbar_end_icon_size"
android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
android:src="@drawable/ic_progress_activity"
- android:tint="@android:color/system_accent2_700"
+ android:tint="?androidprv:attr/materialColorOnSecondaryFixedVariant"
android:alpha="0.0"
/>
@@ -84,9 +86,9 @@
android:id="@+id/end_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginStart="@dimen/chipbar_end_item_start_margin"
- android:textSize="@dimen/chipbar_text_size"
+ style="@style/Chipbar.Text"
+ android:textColor="?androidprv:attr/materialColorOnPrimaryFixed"
android:paddingStart="@dimen/chipbar_outer_padding"
android:paddingEnd="@dimen/chipbar_outer_padding"
android:paddingTop="@dimen/chipbar_end_button_vertical_padding"
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index dffe40b8454a..441f963a855a 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -94,7 +94,7 @@
<include
android:id="@+id/carrier_group"
- layout="@layout/qs_carrier_group"
+ layout="@layout/shade_carrier_group"
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
android:minHeight="@dimen/large_screen_shade_header_min_height"
app:layout_constraintWidth_min="48dp"
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 4048a39344bd..e9acf3fa10c8 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -20,7 +20,7 @@
android:id="@+id/keyguard_bottom_area"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:outlineProvider="none" > <!-- Put it above the status bar header -->
+ android:outlineProvider="none" >
<LinearLayout
android:id="@+id/keyguard_indication_area"
@@ -59,33 +59,55 @@
</LinearLayout>
- <com.android.systemui.animation.view.LaunchableImageView
- android:id="@+id/start_button"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:layout_gravity="bottom|start"
- android:scaleType="fitCenter"
- android:padding="@dimen/keyguard_affordance_fixed_padding"
- android:tint="?android:attr/textColorPrimary"
- android:background="@drawable/keyguard_bottom_affordance_bg"
- android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
- android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset"
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_gravity="bottom"
+ android:layout_marginHorizontal="@dimen/keyguard_affordance_horizontal_offset"
android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
- android:visibility="gone" />
+ android:gravity="bottom"
+ >
- <com.android.systemui.animation.view.LaunchableImageView
- android:id="@+id/end_button"
- android:layout_height="@dimen/keyguard_affordance_fixed_height"
- android:layout_width="@dimen/keyguard_affordance_fixed_width"
- android:layout_gravity="bottom|end"
- android:scaleType="fitCenter"
- android:padding="@dimen/keyguard_affordance_fixed_padding"
- android:tint="?android:attr/textColorPrimary"
- android:background="@drawable/keyguard_bottom_affordance_bg"
- android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
- android:layout_marginEnd="@dimen/keyguard_affordance_horizontal_offset"
- android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset"
- android:visibility="gone" />
+ <com.android.systemui.animation.view.LaunchableImageView
+ android:id="@+id/start_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+ android:visibility="invisible" />
+
+ <FrameLayout
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="24dp"
+ >
+ <include
+ android:id="@+id/keyguard_settings_button"
+ layout="@layout/keyguard_settings_popup_menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+
+ <com.android.systemui.animation.view.LaunchableImageView
+ android:id="@+id/end_button"
+ android:layout_height="@dimen/keyguard_affordance_fixed_height"
+ android:layout_width="@dimen/keyguard_affordance_fixed_width"
+ android:scaleType="fitCenter"
+ android:padding="@dimen/keyguard_affordance_fixed_padding"
+ android:tint="?android:attr/textColorPrimary"
+ android:background="@drawable/keyguard_bottom_affordance_bg"
+ android:foreground="@drawable/keyguard_bottom_affordance_selected_border"
+ android:visibility="invisible" />
+
+ </LinearLayout>
<FrameLayout
android:id="@+id/overlay_container"
diff --git a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
index 89d88fea1817..65ee8b370f9b 100644
--- a/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
+++ b/packages/SystemUI/res/layout/keyguard_settings_popup_menu.xml
@@ -15,25 +15,24 @@
~
-->
-<LinearLayout
+<com.android.systemui.animation.view.LaunchableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:minHeight="52dp"
+ android:minHeight="@dimen/keyguard_affordance_fixed_height"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/keyguard_settings_popup_menu_background"
- android:paddingStart="16dp"
- android:paddingEnd="24dp"
- android:paddingVertical="16dp">
+ android:padding="12dp">
<ImageView
android:id="@+id/icon"
- android:layout_width="20dp"
- android:layout_height="20dp"
- android:layout_marginEnd="16dp"
- android:tint="?android:attr/textColorPrimary"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_marginEnd="8dp"
+ android:tint="?androidprv:attr/materialColorOnSecondaryFixed"
android:importantForAccessibility="no"
tools:ignore="UseAppTint" />
@@ -42,9 +41,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?androidprv:attr/materialColorOnSecondaryFixed"
android:textSize="14sp"
android:maxLines="1"
android:ellipsize="end" />
-</LinearLayout> \ No newline at end of file
+</com.android.systemui.animation.view.LaunchableLinearLayout>
diff --git a/packages/SystemUI/res/layout/qs_carrier.xml b/packages/SystemUI/res/layout/shade_carrier.xml
index a854660f64f8..0fed393a7ed3 100644
--- a/packages/SystemUI/res/layout/qs_carrier.xml
+++ b/packages/SystemUI/res/layout/shade_carrier.xml
@@ -14,7 +14,7 @@
~ limitations under the License
-->
-<com.android.systemui.qs.carrier.QSCarrier
+<com.android.systemui.shade.carrier.ShadeCarrier
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/linear_carrier"
android:layout_width="wrap_content"
@@ -29,7 +29,7 @@
android:focusable="true" >
<com.android.systemui.util.AutoMarqueeTextView
- android:id="@+id/qs_carrier_text"
+ android:id="@+id/shade_carrier_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
@@ -53,4 +53,4 @@
android:layout_marginStart="@dimen/qs_carrier_margin_width"
android:visibility="gone" />
-</com.android.systemui.qs.carrier.QSCarrier> \ No newline at end of file
+</com.android.systemui.shade.carrier.ShadeCarrier> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_carrier_group.xml b/packages/SystemUI/res/layout/shade_carrier_group.xml
index 6e13ab972226..2e8f98cbd190 100644
--- a/packages/SystemUI/res/layout/qs_carrier_group.xml
+++ b/packages/SystemUI/res/layout/shade_carrier_group.xml
@@ -15,7 +15,7 @@
-->
<!-- Extends LinearLayout -->
-<com.android.systemui.qs.carrier.QSCarrierGroup
+<com.android.systemui.shade.carrier.ShadeCarrierGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_mobile"
android:layout_width="0dp"
@@ -39,25 +39,25 @@
android:visibility="gone"/>
<include
- layout="@layout/qs_carrier"
+ layout="@layout/shade_carrier"
android:id="@+id/carrier1"
android:layout_weight="1"/>
<View
- android:id="@+id/qs_carrier_divider1"
+ android:id="@+id/shade_carrier_divider1"
android:layout_width="@dimen/qs_header_carrier_separator_width"
android:layout_height="match_parent"
android:visibility="gone"
android:importantForAccessibility="no"/>
<include
- layout="@layout/qs_carrier"
+ layout="@layout/shade_carrier"
android:id="@+id/carrier2"
android:layout_weight="1"
android:visibility="gone"/>
<View
- android:id="@+id/qs_carrier_divider2"
+ android:id="@+id/shade_carrier_divider2"
android:layout_width="@dimen/qs_header_carrier_separator_width"
android:layout_height="match_parent"
android:layout_weight="1"
@@ -65,9 +65,9 @@
android:importantForAccessibility="no"/>
<include
- layout="@layout/qs_carrier"
+ layout="@layout/shade_carrier"
android:id="@+id/carrier3"
android:layout_weight="1"
android:visibility="gone"/>
-</com.android.systemui.qs.carrier.QSCarrierGroup> \ No newline at end of file
+</com.android.systemui.shade.carrier.ShadeCarrierGroup> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index a11ffcda9830..d7106762d17b 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -120,10 +120,6 @@
/>
</com.android.systemui.shade.NotificationsQuickSettingsContainer>
- <include
- layout="@layout/keyguard_bottom_area"
- android:visibility="gone" />
-
<ViewStub
android:id="@+id/keyguard_user_switcher_stub"
android:layout="@layout/keyguard_user_switcher"
@@ -153,9 +149,7 @@
</com.android.keyguard.LockIconView>
- <FrameLayout
- android:id="@+id/preview_container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- </FrameLayout>
+ <include
+ layout="@layout/keyguard_bottom_area"
+ android:visibility="gone" />
</com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 96e6d4e1a234..cb8c2a73c15d 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -231,9 +231,6 @@
<color name="people_tile_background">@color/material_dynamic_secondary95</color>
- <!-- Chipbar -->
- <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color>
-
<!-- Internet Dialog -->
<!-- Material next state on color-->
<color name="settingslib_state_on_color">@color/settingslib_state_on</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 714d495af762..0eb0a078a478 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1110,6 +1110,7 @@
<!-- Chipbar -->
<!-- (Used for media tap-to-transfer chip for sender device and active unlock) -->
<dimen name="chipbar_outer_padding">16dp</dimen>
+ <dimen name="chipbar_outer_padding_half">8dp</dimen>
<dimen name="chipbar_text_size">16sp</dimen>
<dimen name="chipbar_start_icon_size">24dp</dimen>
<dimen name="chipbar_end_icon_size">20dp</dimen>
@@ -1774,13 +1775,6 @@
<dimen name="rear_display_title_top_padding">24dp</dimen>
<dimen name="rear_display_title_bottom_padding">16dp</dimen>
- <!--
- Vertical distance between the pointer and the popup menu that shows up on the lock screen when
- it is long-pressed.
- -->
- <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
-
-
<!-- Bouncer user switcher margins -->
<dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
<dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index befbfab7dbc3..675ae326b5a2 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -36,7 +36,7 @@
fade_out_complete_frame -->
<dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
- <integer name="qs_carrier_max_em">7</integer>
+ <integer name="shade_carrier_max_em">7</integer>
<!-- Maximum number of notification icons shown on the Always on Display
(excluding overflow dot) -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f1777f84cd6d..74ae954a539c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3057,13 +3057,17 @@
<string name="call_from_work_profile_close">Close</string>
<!--
- Label for a menu item in a menu that is shown when the user wishes to configure the lock screen.
+ Label for a menu item in a menu that is shown when the user wishes to customize the lock screen.
Clicking on this menu item takes the user to a screen where they can modify the settings of the
lock screen.
+ It is critical that this text is as short as possible. If needed, translators should feel free
+ to drop "lock screen" from their translation and keep just "Customize" or "Customization", in
+ cases when that verb is not available in their target language.
+
[CHAR LIMIT=32]
-->
- <string name="lock_screen_settings">Lock screen settings</string>
+ <string name="lock_screen_settings">Customize lock screen</string>
<!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]-->
<string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 064cea112b5a..a359d3b21938 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -70,6 +70,15 @@
<item name="android:fontWeight">700</item>
</style>
+ <style name="Chipbar" />
+
+ <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title">
+ <!-- Text size should be kept in sync with the notification conversation header size. (The
+ conversation header doesn't have a defined style, so the size must be copied here.)
+ See notification_template_conversation_header.xml. -->
+ <item name="android:textSize">16sp</item>
+ </style>
+
<style name="TextAppearance" />
<style name="TextAppearance.QS">
@@ -1399,4 +1408,9 @@
<style name="ShortcutItemBackground">
<item name="android:background">@color/ksh_key_item_new_background</item>
</style>
+
+ <style name="LongPressLockScreenAnimation">
+ <item name="android:windowEnterAnimation">@anim/long_press_lock_screen_popup_enter</item>
+ <item name="android:windowExitAnimation">@anim/long_press_lock_screen_popup_exit</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 4d7d0ea264a4..3c447a8eb6d8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,6 +124,8 @@ public class QuickStepContract {
public static final int SYSUI_STATE_WAKEFULNESS_TRANSITION = 1 << 29;
// The notification panel expansion fraction is > 0
public static final int SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE = 1 << 30;
+ // When keyguard will be dismissed but didn't start animation yet
+ public static final int SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY = 1 << 31;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -172,6 +174,7 @@ public class QuickStepContract {
SYSUI_STATE_AWAKE,
SYSUI_STATE_WAKEFULNESS_TRANSITION,
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE,
+ SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
})
public @interface SystemUiStateFlags {}
@@ -270,6 +273,9 @@ public class QuickStepContract {
if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0) {
str.add("notif_visible");
}
+ if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY) != 0) {
+ str.add("keygrd_going_away");
+ }
return str.toString();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 7971e84769a2..b15378570358 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -21,7 +21,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
-import android.net.wifi.WifiManager;
+import android.os.Trace;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener;
@@ -37,6 +37,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository;
import com.android.systemui.telephony.TelephonyListenerManager;
import java.util.List;
@@ -50,7 +51,10 @@ import javax.inject.Inject;
* Controller that generates text including the carrier names and/or the status of all the SIM
* interfaces in the device. Through a callback, the updates can be retrieved either as a list or
* separated by a given separator {@link CharSequence}.
+ *
+ * @deprecated use {@link com.android.systemui.statusbar.pipeline.wifi} instead
*/
+@Deprecated
public class CarrierTextManager {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "CarrierTextController";
@@ -64,7 +68,7 @@ public class CarrierTextManager {
private final AtomicBoolean mNetworkSupported = new AtomicBoolean();
@VisibleForTesting
protected KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final WifiManager mWifiManager;
+ private final WifiRepository mWifiRepository;
private final boolean[] mSimErrorState;
private final int mSimSlotsNumber;
@Nullable // Check for nullability before dispatching
@@ -165,7 +169,7 @@ public class CarrierTextManager {
CharSequence separator,
boolean showAirplaneMode,
boolean showMissingSim,
- @Nullable WifiManager wifiManager,
+ WifiRepository wifiRepository,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -177,8 +181,7 @@ public class CarrierTextManager {
mShowAirplaneMode = showAirplaneMode;
mShowMissingSim = showMissingSim;
-
- mWifiManager = wifiManager;
+ mWifiRepository = wifiRepository;
mTelephonyManager = telephonyManager;
mSeparator = separator;
mTelephonyListenerManager = telephonyListenerManager;
@@ -297,6 +300,7 @@ public class CarrierTextManager {
}
protected void updateCarrierText() {
+ Trace.beginSection("CarrierTextManager#updateCarrierText");
boolean allSimsMissing = true;
boolean anySimReadyAndInService = false;
CharSequence displayText = null;
@@ -329,20 +333,20 @@ public class CarrierTextManager {
carrierNames[i] = carrierTextForSimState;
}
if (simState == TelephonyManager.SIM_STATE_READY) {
+ Trace.beginSection("WFC check");
ServiceState ss = mKeyguardUpdateMonitor.mServiceStates.get(subId);
if (ss != null && ss.getDataRegistrationState() == ServiceState.STATE_IN_SERVICE) {
// hack for WFC (IWLAN) not turning off immediately once
// Wi-Fi is disassociated or disabled
if (ss.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN
- || (mWifiManager != null && mWifiManager.isWifiEnabled()
- && mWifiManager.getConnectionInfo() != null
- && mWifiManager.getConnectionInfo().getBSSID() != null)) {
+ || mWifiRepository.isWifiConnectedWithValidSsid()) {
if (DEBUG) {
Log.d(TAG, "SIM ready and in service: subId=" + subId + ", ss=" + ss);
}
anySimReadyAndInService = true;
}
}
+ Trace.endSection();
}
}
// Only create "No SIM card" if no cards with CarrierName && no wifi when some sim is READY
@@ -406,6 +410,7 @@ public class CarrierTextManager {
subsIds,
airplaneMode);
postToCallback(info);
+ Trace.endSection();
}
@VisibleForTesting
@@ -633,7 +638,7 @@ public class CarrierTextManager {
public static class Builder {
private final Context mContext;
private final String mSeparator;
- private final WifiManager mWifiManager;
+ private final WifiRepository mWifiRepository;
private final TelephonyManager mTelephonyManager;
private final TelephonyListenerManager mTelephonyListenerManager;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -647,7 +652,7 @@ public class CarrierTextManager {
public Builder(
Context context,
@Main Resources resources,
- @Nullable WifiManager wifiManager,
+ @Nullable WifiRepository wifiRepository,
TelephonyManager telephonyManager,
TelephonyListenerManager telephonyListenerManager,
WakefulnessLifecycle wakefulnessLifecycle,
@@ -657,7 +662,7 @@ public class CarrierTextManager {
mContext = context;
mSeparator = resources.getString(
com.android.internal.R.string.kg_text_message_separator);
- mWifiManager = wifiManager;
+ mWifiRepository = wifiRepository;
mTelephonyManager = telephonyManager;
mTelephonyListenerManager = telephonyListenerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
@@ -681,7 +686,7 @@ public class CarrierTextManager {
/** Create a CarrierTextManager. */
public CarrierTextManager build() {
return new CarrierTextManager(
- mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiManager,
+ mContext, mSeparator, mShowAirplaneMode, mShowMissingSim, mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle,
mMainExecutor, mBgExecutor, mKeyguardUpdateMonitor);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0779653430b2..7262a736c433 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -25,6 +25,7 @@ import android.graphics.Rect
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
+import android.view.View.OnAttachStateChangeListener
import android.view.ViewTreeObserver
import android.widget.FrameLayout
import androidx.annotation.VisibleForTesting
@@ -105,6 +106,24 @@ constructor(
}
updateFontSizes()
updateTimeListeners()
+ value.smallClock.view.addOnAttachStateChangeListener(
+ object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View?) {
+ value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onViewDetachedFromWindow(p0: View?) {
+ }
+ })
+ value.largeClock.view.addOnAttachStateChangeListener(
+ object : OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(p0: View?) {
+ value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+ }
+
+ override fun onViewDetachedFromWindow(p0: View?) {
+ }
+ })
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt
index 3a89c13ddd64..40f6f48288dc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardActiveUnlockModel.kt
@@ -17,9 +17,9 @@
package com.android.keyguard
import android.annotation.CurrentTimeMillisLong
+import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
/** Verbose debug information. */
data class KeyguardActiveUnlockModel(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 0326b6d3edca..5ba0ad62e9fb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -294,11 +294,11 @@ public class KeyguardClockSwitch extends RelativeLayout {
public void dump(PrintWriter pw, String[] args) {
pw.println("KeyguardClockSwitch:");
- pw.println(" mSmallClockFrame: " + mSmallClockFrame);
- pw.println(" mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha());
- pw.println(" mLargeClockFrame: " + mLargeClockFrame);
- pw.println(" mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha());
- pw.println(" mStatusArea: " + mStatusArea);
- pw.println(" mDisplayedClockSize: " + mDisplayedClockSize);
+ pw.println(" mSmallClockFrame = " + mSmallClockFrame);
+ pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+ pw.println(" mLargeClockFrame = " + mLargeClockFrame);
+ pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+ pw.println(" mStatusArea = " + mStatusArea);
+ pw.println(" mDisplayedClockSize = " + mDisplayedClockSize);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 9290220b8698..ad333b7bfbb6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -109,7 +109,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final ContentObserver mShowWeatherObserver = new ContentObserver(null) {
@Override
public void onChange(boolean change) {
- setDateWeatherVisibility();
+ setWeatherVisibility();
}
};
@@ -236,6 +236,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
updateDoubleLineClock();
setDateWeatherVisibility();
+ setWeatherVisibility();
mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
mKeyguardUnlockAnimationListener);
@@ -266,6 +267,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mStatusArea.removeView(mDateWeatherView);
addDateWeatherView(index);
}
+ setDateWeatherVisibility();
+ setWeatherVisibility();
}
int index = mStatusArea.indexOfChild(mSmartspaceView);
if (index >= 0) {
@@ -487,16 +490,19 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
private void setDateWeatherVisibility() {
- if (mDateWeatherView != null || mWeatherView != null) {
+ if (mDateWeatherView != null) {
mUiExecutor.execute(() -> {
- if (mDateWeatherView != null) {
- mDateWeatherView.setVisibility(
- clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE);
- }
- if (mWeatherView != null) {
- mWeatherView.setVisibility(
- mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
- }
+ mDateWeatherView.setVisibility(
+ clockHasCustomWeatherDataDisplay() ? View.INVISIBLE : View.VISIBLE);
+ });
+ }
+ }
+
+ private void setWeatherVisibility() {
+ if (mWeatherView != null) {
+ mUiExecutor.execute(() -> {
+ mWeatherView.setVisibility(
+ mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE);
});
}
}
@@ -513,9 +519,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
- pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE));
- pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock);
+ pw.println("currentClockSizeLarge: " + (mCurrentClockSize == LARGE));
+ pw.println("mCanShowDoubleLineClock: " + mCanShowDoubleLineClock);
mView.dump(pw, args);
+ mClockRegistry.dump(pw, args);
ClockController clock = getClock();
if (clock != null) {
clock.dump(pw);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index c98e9b40e7ab..5b0e29005d82 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -17,9 +17,9 @@
package com.android.keyguard
import android.annotation.CurrentTimeMillisLong
+import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
/** Verbose debug information associated. */
data class KeyguardFaceListenModel(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
index 57130ed80d26..b8c0ccbd8aaa 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFingerprintListenModel.kt
@@ -17,9 +17,9 @@
package com.android.keyguard
import android.annotation.CurrentTimeMillisLong
+import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
/** Verbose debug information. */
data class KeyguardFingerprintListenModel(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a775866faf..db38d3414915 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -75,6 +75,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -115,6 +116,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final SessionTracker mSessionTracker;
private final Optional<SideFpsController> mSideFpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
+ private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private int mTranslationY;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
@@ -300,6 +302,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
+ mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
@@ -389,7 +392,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
FalsingA11yDelegate falsingA11yDelegate,
TelephonyManager telephonyManager,
ViewMediatorCallback viewMediatorCallback,
- AudioManager audioManager
+ AudioManager audioManager,
+ KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -414,6 +418,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mTelephonyManager = telephonyManager;
mViewMediatorCallback = viewMediatorCallback;
mAudioManager = audioManager;
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index b8e196fb8787..d8e1eb0f0860 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,10 +19,12 @@ package com.android.keyguard;
import static java.util.Collections.emptySet;
import android.content.Context;
+import android.os.Build;
import android.os.Trace;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
import android.widget.GridLayout;
import com.android.systemui.R;
@@ -118,6 +120,16 @@ public class KeyguardStatusView extends GridLayout {
}
@Override
+ public ViewPropertyAnimator animate() {
+ if (Build.IS_DEBUGGABLE) {
+ throw new IllegalArgumentException(
+ "KeyguardStatusView does not support ViewPropertyAnimator. "
+ + "Use PropertyAnimator instead.");
+ }
+ return super.animate();
+ }
+
+ @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Trace.beginSection("KeyguardStatusView#onMeasure");
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index c4df836e401f..0cdef4d63639 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -16,12 +16,37 @@
package com.android.keyguard;
+import static androidx.constraintlayout.widget.ConstraintSet.END;
+import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
+
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.graphics.Rect;
+import android.transition.ChangeBounds;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.transition.TransitionValues;
import android.util.Slog;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import androidx.annotation.VisibleForTesting;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.ConstraintSet;
+
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.KeyguardClockSwitch.ClockSize;
import com.android.keyguard.logging.KeyguardLogger;
+import com.android.systemui.R;
+import com.android.systemui.animation.Interpolators;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -42,7 +67,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardStatusViewController";
- private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
+ /**
+ * Duration to use for the animator when the keyguard status view alignment changes, and a
+ * custom clock animation is in use.
+ */
+ private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
+
+ public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private final KeyguardSliceViewController mKeyguardSliceViewController;
@@ -50,8 +81,25 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
+ private final FeatureFlags mFeatureFlags;
+ private final InteractionJankMonitor mInteractionJankMonitor;
private final Rect mClipBounds = new Rect();
+ private Boolean mStatusViewCentered = true;
+
+ private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
+ new TransitionListenerAdapter() {
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ }
+ };
+
@Inject
public KeyguardStatusViewController(
KeyguardStatusView keyguardStatusView,
@@ -62,7 +110,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
ConfigurationController configurationController,
DozeParameters dozeParameters,
ScreenOffAnimationController screenOffAnimationController,
- KeyguardLogger logger) {
+ KeyguardLogger logger,
+ FeatureFlags featureFlags,
+ InteractionJankMonitor interactionJankMonitor) {
super(keyguardStatusView);
mKeyguardSliceViewController = keyguardSliceViewController;
mKeyguardClockSwitchController = keyguardClockSwitchController;
@@ -71,6 +121,8 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
dozeParameters, screenOffAnimationController, /* animateYPos= */ true,
logger.getBuffer());
+ mInteractionJankMonitor = interactionJankMonitor;
+ mFeatureFlags = featureFlags;
}
@Override
@@ -169,16 +221,32 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
mView.setImportantForAccessibility(mode);
}
+ @VisibleForTesting
+ void setProperty(AnimatableProperty property, float value, boolean animate) {
+ PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate);
+ }
+
/**
* Update position of the view with an optional animation
*/
public void updatePosition(int x, int y, float scale, boolean animate) {
float oldY = mView.getY();
- PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
- animate);
+ setProperty(AnimatableProperty.Y, y, animate);
+
+ ClockController clock = mKeyguardClockSwitchController.getClock();
+ if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) {
+ // If requested, scale the entire view instead of just the clock view
+ mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */,
+ CLOCK_ANIMATION_PROPERTIES, animate);
+ setProperty(AnimatableProperty.SCALE_X, scale, animate);
+ setProperty(AnimatableProperty.SCALE_Y, scale, animate);
+ } else {
+ mKeyguardClockSwitchController.updatePosition(x, scale,
+ CLOCK_ANIMATION_PROPERTIES, animate);
+ setProperty(AnimatableProperty.SCALE_X, 1f, animate);
+ setProperty(AnimatableProperty.SCALE_Y, 1f, animate);
+ }
- mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES,
- animate);
if (oldY != y) {
mKeyguardClockSwitchController.updateKeyguardStatusViewOffset();
}
@@ -242,9 +310,141 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
}
- /** Gets the current clock controller. */
- @Nullable
- public ClockController getClockController() {
- return mKeyguardClockSwitchController.getClock();
+ /**
+ * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
+ */
+ public void updateAlignment(
+ ConstraintLayout notifContainerParent,
+ boolean splitShadeEnabled,
+ boolean shouldBeCentered,
+ boolean animate) {
+ if (mStatusViewCentered == shouldBeCentered) {
+ return;
+ }
+
+ mStatusViewCentered = shouldBeCentered;
+ if (notifContainerParent == null) {
+ return;
+ }
+
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(notifContainerParent);
+ int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+ constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
+ if (!animate) {
+ constraintSet.applyTo(notifContainerParent);
+ return;
+ }
+
+ mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
+ ChangeBounds transition = new ChangeBounds();
+ if (splitShadeEnabled) {
+ // Excluding media from the transition on split-shade, as it doesn't transition
+ // horizontally properly.
+ transition.excludeTarget(R.id.status_view_media_container, true);
+ }
+
+ transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+ ClockController clock = mKeyguardClockSwitchController.getClock();
+ boolean customClockAnimation = clock != null
+ && clock.getConfig().getHasCustomPositionUpdatedAnimation();
+
+ if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
+ // Find the clock, so we can exclude it from this transition.
+ FrameLayout clockContainerView = mView.findViewById(R.id.lockscreen_clock_view_large);
+
+ // The clock container can sometimes be null. If it is, just fall back to the
+ // old animation rather than setting up the custom animations.
+ if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
+ transition.addListener(mKeyguardStatusAlignmentTransitionListener);
+ TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+ } else {
+ View clockView = clockContainerView.getChildAt(0);
+
+ transition.excludeTarget(clockView, /* exclude= */ true);
+
+ TransitionSet set = new TransitionSet();
+ set.addTransition(transition);
+
+ SplitShadeTransitionAdapter adapter =
+ new SplitShadeTransitionAdapter(mKeyguardClockSwitchController);
+
+ // Use linear here, so the actual clock can pick its own interpolator.
+ adapter.setInterpolator(Interpolators.LINEAR);
+ adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
+ adapter.addTarget(clockView);
+ set.addTransition(adapter);
+ set.addListener(mKeyguardStatusAlignmentTransitionListener);
+ TransitionManager.beginDelayedTransition(notifContainerParent, set);
+ }
+ } else {
+ transition.addListener(mKeyguardStatusAlignmentTransitionListener);
+ TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+ }
+
+ constraintSet.applyTo(notifContainerParent);
+ }
+
+ @VisibleForTesting
+ static class SplitShadeTransitionAdapter extends Transition {
+ private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
+ private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
+
+ private final KeyguardClockSwitchController mController;
+
+ @VisibleForTesting
+ SplitShadeTransitionAdapter(KeyguardClockSwitchController controller) {
+ mController = controller;
+ }
+
+ private void captureValues(TransitionValues transitionValues) {
+ Rect boundsRect = new Rect();
+ boundsRect.left = transitionValues.view.getLeft();
+ boundsRect.top = transitionValues.view.getTop();
+ boundsRect.right = transitionValues.view.getRight();
+ boundsRect.bottom = transitionValues.view.getBottom();
+ transitionValues.values.put(PROP_BOUNDS, boundsRect);
+ }
+
+ @Override
+ public void captureEndValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Override
+ public void captureStartValues(TransitionValues transitionValues) {
+ captureValues(transitionValues);
+ }
+
+ @Nullable
+ @Override
+ public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+ @Nullable TransitionValues endValues) {
+ if (startValues == null || endValues == null) {
+ return null;
+ }
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+
+ Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
+ Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
+
+ anim.addUpdateListener(animation -> {
+ ClockController clock = mController.getClock();
+ if (clock == null) {
+ return;
+ }
+
+ clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
+ });
+
+ return anim;
+ }
+
+ @Override
+ public String[] getTransitionProperties() {
+ return TRANSITION_PROPERTIES;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e1bca89091b2..cc6438913667 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -96,6 +96,7 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
import android.hardware.biometrics.SensorProperties;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -153,6 +154,15 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.DetectionStatus;
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
@@ -371,6 +381,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final FingerprintManager mFpm;
@Nullable
private final FaceManager mFaceManager;
+ @Nullable
+ private KeyguardFaceAuthInteractor mFaceAuthInteractor;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
@DevicePostureInt
@@ -1164,8 +1176,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.endSection();
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceAuthFailed() {
Assert.isMainThread();
+ String reason =
+ mKeyguardBypassController.canBypass() ? "bypass"
+ : mAlternateBouncerShowing ? "alternateBouncer"
+ : mPrimaryBouncerFullyShown ? "bouncer"
+ : "udfpsFpDown";
+ requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+ "faceFailure-" + reason);
+
mLogger.d("onFaceAuthFailed");
mFaceCancelSignal = null;
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -1179,6 +1204,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getString(R.string.kg_face_not_recognized));
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceAcquired(int acquireInfo) {
Assert.isMainThread();
mLogger.logFaceAcquired(acquireInfo);
@@ -1188,8 +1217,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onBiometricAcquired(FACE, acquireInfo);
}
}
+
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ acquireInfo)) {
+ requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+ "faceAcquireInfo-" + acquireInfo);
+ }
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
try {
@@ -1202,7 +1242,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLogger.logFaceAuthForWrongUser(authUserId);
return;
}
- if (isFaceDisabled(userId)) {
+ if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) {
mLogger.logFaceAuthDisabledForUser(userId);
return;
}
@@ -1214,7 +1254,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.endSection();
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceHelp(int msgId, String helpString) {
+ if (mFaceAcquiredInfoIgnoreList.contains(msgId)) {
+ return;
+ }
Assert.isMainThread();
mLogger.logFaceAuthHelpMsg(msgId, helpString);
for (int i = 0; i < mCallbacks.size(); i++) {
@@ -1225,22 +1272,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- private final Runnable mRetryFaceAuthentication = new Runnable() {
- @Override
- public void run() {
- mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
- }
- };
-
- private void onFaceCancelNotReceived() {
- mLogger.e("Face cancellation not received, transitioning to STOPPED");
- mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
- FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
- }
-
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceError(int msgId, final String originalErrMsg) {
Assert.isMainThread();
String errString = originalErrMsg;
@@ -1278,6 +1313,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) {
lockedOutStateChanged = !mFaceLockedOutPermanent;
mFaceLockedOutPermanent = true;
+ if (isFaceClass3()) {
+ updateFingerprintListeningState(BIOMETRIC_ACTION_STOP);
+ }
}
if (isHwUnavailable && cameraPrivacyEnabled) {
@@ -1295,6 +1333,28 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (lockedOutStateChanged) {
notifyLockedOutStateChanged(FACE);
}
+
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) {
+ requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+ "faceError-" + msgId);
+ }
+ }
+
+ private final Runnable mRetryFaceAuthentication = new Runnable() {
+ @Override
+ public void run() {
+ mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
+ updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
+ }
+ };
+
+ private void onFaceCancelNotReceived() {
+ mLogger.e("Face cancellation not received, transitioning to STOPPED");
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
}
private void handleFaceLockoutReset(@LockoutMode int mode) {
@@ -1339,10 +1399,61 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
}
+ /**
+ * @deprecated This is being migrated to use modern architecture.
+ */
+ @Deprecated
public boolean isFaceDetectionRunning() {
+ if (isFaceAuthInteractorEnabled()) {
+ return getFaceAuthInteractor().isRunning();
+ }
return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
}
+ private boolean isFaceAuthInteractorEnabled() {
+ return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled();
+ }
+
+ private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+ return mFaceAuthInteractor;
+ }
+
+ /**
+ * Set the face auth interactor that should be used for initiating face authentication.
+ */
+ public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) {
+ mFaceAuthInteractor = faceAuthInteractor;
+ mFaceAuthInteractor.registerListener(mFaceAuthenticationListener);
+ }
+
+ private FaceAuthenticationListener mFaceAuthenticationListener =
+ new FaceAuthenticationListener() {
+ @Override
+ public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
+ if (status instanceof AcquiredAuthenticationStatus) {
+ handleFaceAcquired(
+ ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
+ } else if (status instanceof ErrorAuthenticationStatus) {
+ ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+ handleFaceError(error.getMsgId(), error.getMsg());
+ } else if (status instanceof FailedAuthenticationStatus) {
+ handleFaceAuthFailed();
+ } else if (status instanceof HelpAuthenticationStatus) {
+ HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+ handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
+ } else if (status instanceof SuccessAuthenticationStatus) {
+ FaceManager.AuthenticationResult result =
+ ((SuccessAuthenticationStatus) status).getSuccessResult();
+ handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
+ }
+ }
+
+ @Override
+ public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+ handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+ }
+ };
+
private boolean isTrustDisabled() {
// Don't allow trust agent if device is secured with a SIM PIN. This is here
// mainly because there's no other way to prompt the user to enter their SIM PIN
@@ -1356,6 +1467,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| isSimPinSecure();
}
+ /**
+ * @deprecated This method is not needed anymore with the new face auth system.
+ */
+ @Deprecated
private boolean isFaceDisabled(int userId) {
// TODO(b/140035044)
return whitelistIpcs(() ->
@@ -1367,7 +1482,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* @return whether the current user has been authenticated with face. This may be true
* on the lockscreen if the user doesn't have bypass enabled.
+ *
+ * @deprecated This is being migrated to use modern architecture.
*/
+ @Deprecated
public boolean getIsFaceAuthenticated() {
boolean faceAuthenticated = false;
BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
@@ -1487,8 +1605,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// STRONG_AUTH_REQUIRED_AFTER_LOCKOUT which is the same as mFingerprintLockedOutPermanent;
// however the strong auth tracker does not include the temporary lockout
// mFingerprintLockedOut.
+ // Class 3 biometric lockout will lockout ALL biometrics
return mStrongAuthTracker.isUnlockingWithBiometricAllowed(isStrongBiometric)
- && !mFingerprintLockedOut;
+ && (!isFingerprintClass3() || !isFingerprintLockedOut())
+ && (!isFaceClass3() || !mFaceLockedOutPermanent);
}
/**
@@ -1506,9 +1626,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@NonNull BiometricSourceType biometricSourceType) {
switch (biometricSourceType) {
case FINGERPRINT:
- return isUnlockingWithBiometricAllowed(true);
+ return isUnlockingWithBiometricAllowed(isFingerprintClass3());
case FACE:
- return isUnlockingWithBiometricAllowed(false);
+ return isUnlockingWithBiometricAllowed(isFaceClass3());
default:
return false;
}
@@ -1613,6 +1733,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
void setAssistantVisible(boolean assistantVisible) {
mAssistantVisible = assistantVisible;
mLogger.logAssistantVisible(mAssistantVisible);
+ if (isFaceAuthInteractorEnabled()) {
+ mFaceAuthInteractor.onAssistantTriggeredOnLockScreen();
+ }
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
if (mAssistantVisible) {
@@ -1826,54 +1949,27 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationFailed() {
- String reason =
- mKeyguardBypassController.canBypass() ? "bypass"
- : mAlternateBouncerShowing ? "alternateBouncer"
- : mPrimaryBouncerFullyShown ? "bouncer"
- : "udfpsFpDown";
- requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
- "faceFailure-" + reason);
-
handleFaceAuthFailed();
}
@Override
public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
- Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
- Trace.endSection();
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
- if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
- return;
- }
handleFaceHelp(helpMsgId, helpString.toString());
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
handleFaceError(errMsgId, errString.toString());
-
- if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
- requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
- "faceError-" + errMsgId);
- }
}
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFaceAcquired(acquireInfo);
-
- if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
- acquireInfo)) {
- requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
- "faceAcquireInfo-" + acquireInfo);
- }
}
};
@@ -2473,7 +2569,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void updateFaceEnrolled(int userId) {
- Boolean isFaceEnrolled = mFaceManager != null && !mFaceSensorProperties.isEmpty()
+ final Boolean isFaceEnrolled = isFaceSupported()
&& mBiometricEnabledForUser.get(userId)
&& mAuthController.isFaceAuthEnrolled(userId);
if (mIsFaceEnrolled != isFaceEnrolled) {
@@ -2482,10 +2578,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mIsFaceEnrolled = isFaceEnrolled;
}
- public boolean isFaceSupported() {
+ private boolean isFaceSupported() {
return mFaceManager != null && !mFaceSensorProperties.isEmpty();
}
+ private boolean isFingerprintSupported() {
+ return mFpm != null && !mFingerprintSensorProperties.isEmpty();
+ }
+
/**
* @return true if there's at least one udfps enrolled for the current user.
*/
@@ -2618,7 +2718,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
* @return current face auth detection state, true if it is running.
+ * @deprecated This is being migrated to use modern architecture.
*/
+ @Deprecated
public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
mLogger.logFaceAuthRequested(reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
@@ -2633,6 +2735,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
+ if (isFaceAuthInteractorEnabled()) return;
// If this message exists, we should not authenticate again until this message is
// consumed by the handler
if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
@@ -2792,10 +2895,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| !mLockPatternUtils.isSecure(user);
// Don't trigger active unlock if fp is locked out
- final boolean fpLockedOut = mFingerprintLockedOut || mFingerprintLockedOutPermanent;
+ final boolean fpLockedOut = isFingerprintLockedOut();
// Don't trigger active unlock if primary auth is required
- final boolean primaryAuthRequired = !isUnlockingWithBiometricAllowed(true);
+ final boolean primaryAuthRequired = !isUnlockingWithTrustAgentAllowed();
final boolean shouldTriggerActiveUnlock =
(mAuthInterruptActive || triggerActiveUnlockForAssistant || awakeKeyguard)
@@ -2857,7 +2960,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mGoingToSleep
|| shouldListenForFingerprintAssistant
|| (mKeyguardOccluded && mIsDreaming)
- || (mKeyguardOccluded && userDoesNotHaveTrust
+ || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
&& (mOccludingAppRequestingFp || isUdfps || mAlternateBouncerShowing));
// Only listen if this KeyguardUpdateMonitor belongs to the primary user. There is an
@@ -2949,7 +3052,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// allow face detection to happen even if stronger auth is required. When face is detected,
// we show the bouncer. However, if the user manually locked down the device themselves,
// never attempt to detect face.
- final boolean supportsDetect = !mFaceSensorProperties.isEmpty()
+ final boolean supportsDetect = isFaceSupported()
&& mFaceSensorProperties.get(0).supportsFaceDetection
&& canBypass && !mPrimaryBouncerIsOrWillBeShowing
&& !isUserInLockdown(user);
@@ -3104,7 +3207,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
: WAKE_REASON_UNKNOWN
).toFaceAuthenticateOptions();
// This would need to be updated for multi-sensor devices
- final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
+ final boolean supportsFaceDetection = isFaceSupported()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
if (!isUnlockingWithBiometricAllowed(FACE)) {
final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
@@ -3144,6 +3247,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
public boolean isFaceLockedOut() {
+ if (isFaceAuthInteractorEnabled()) {
+ return getFaceAuthInteractor().isLockedOut();
+ }
return mFaceLockedOutPermanent;
}
@@ -3166,21 +3272,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @return {@code true} if possible.
*/
public boolean isUnlockingWithNonStrongBiometricsPossible(int userId) {
- // This assumes that there is at most one face and at most one fingerprint sensor
- return (mFaceManager != null && !mFaceSensorProperties.isEmpty()
- && (mFaceSensorProperties.get(0).sensorStrength != SensorProperties.STRENGTH_STRONG)
- && isUnlockWithFacePossible(userId))
- || (mFpm != null && !mFingerprintSensorProperties.isEmpty()
- && (mFingerprintSensorProperties.get(0).sensorStrength
- != SensorProperties.STRENGTH_STRONG) && isUnlockWithFingerprintPossible(userId));
+ return (!isFaceClass3() && isUnlockWithFacePossible(userId))
+ || (isFingerprintClass3() && isUnlockWithFingerprintPossible(userId));
}
@SuppressLint("MissingPermission")
@VisibleForTesting
boolean isUnlockWithFingerprintPossible(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
- boolean newFpEnrolled = mFpm != null
- && !mFingerprintSensorProperties.isEmpty()
+ boolean newFpEnrolled = isFingerprintSupported()
&& !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId);
Boolean oldFpEnrolled = mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
if (oldFpEnrolled != newFpEnrolled) {
@@ -3198,13 +3298,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
}
+ /**
+ * @deprecated This is being migrated to use modern architecture.
+ */
+ @Deprecated
private boolean isUnlockWithFacePossible(int userId) {
+ if (isFaceAuthInteractorEnabled()) {
+ return getFaceAuthInteractor().canFaceAuthRun();
+ }
return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
}
/**
* If face hardware is available, user has enrolled and enabled auth via setting.
+ *
+ * @deprecated This is being migrated to use modern architecture.
*/
+ @Deprecated
public boolean isFaceAuthEnabledForUser(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
updateFaceEnrolled(userId);
@@ -3228,6 +3338,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
+ if (isFaceAuthInteractorEnabled()) return;
mLogger.v("stopListeningForFace()");
mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
@@ -3330,12 +3441,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// Immediately stop previous biometric listening states.
// Resetting lockout states updates the biometric listening states.
- if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ if (isFaceSupported()) {
stopListeningForFace(FACE_AUTH_UPDATED_USER_SWITCHING);
handleFaceLockoutReset(mFaceManager.getLockoutModeForUser(
mFaceSensorProperties.get(0).sensorId, userId));
}
- if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ if (isFingerprintSupported()) {
stopListeningForFingerprint();
handleFingerprintLockoutReset(mFpm.getLockoutModeForUser(
mFingerprintSensorProperties.get(0).sensorId, userId));
@@ -4071,6 +4182,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return BIOMETRIC_LOCKOUT_RESET_DELAY_MS;
}
+ @VisibleForTesting
+ protected boolean isFingerprintClass3() {
+ // This assumes that there is at most one fingerprint sensor property
+ return isFingerprintSupported() && isClass3Biometric(mFingerprintSensorProperties.get(0));
+ }
+
+ @VisibleForTesting
+ protected boolean isFaceClass3() {
+ // This assumes that there is at most one face sensor property
+ return isFaceSupported() && isClass3Biometric(mFaceSensorProperties.get(0));
+ }
+
+ private boolean isClass3Biometric(SensorPropertiesInternal sensorProperties) {
+ return sensorProperties.sensorStrength == SensorProperties.STRENGTH_STRONG;
+ }
+
/**
* Unregister all listeners.
*/
@@ -4078,6 +4205,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
+ if (isFaceAuthInteractorEnabled()) {
+ mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+ }
if (mDeviceProvisionedObserver != null) {
mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
@@ -4107,6 +4237,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(getCurrentUser()));
+ pw.println(" isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
@@ -4122,11 +4253,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
for (int subId : mServiceStates.keySet()) {
pw.println(" " + subId + "=" + mServiceStates.get(subId));
}
- if (mFpm != null && !mFingerprintSensorProperties.isEmpty()) {
+ if (isFingerprintSupported()) {
final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated fingerprint = mUserFingerprintAuthenticated.get(userId);
pw.println(" Fingerprint state (user=" + userId + ")");
+ pw.println(" isFingerprintClass3=" + isFingerprintClass3());
pw.println(" areAllFpAuthenticatorsRegistered="
+ mAuthController.areAllFingerprintAuthenticatorsRegistered());
pw.println(" allowed="
@@ -4184,11 +4316,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mFingerprintListenBuffer.toList()
).printTableData(pw);
}
- if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) {
+ if (isFaceSupported()) {
final int userId = mUserTracker.getUserId();
final int strongAuthFlags = mStrongAuthTracker.getStrongAuthForUser(userId);
BiometricAuthenticated face = mUserFaceAuthenticated.get(userId);
pw.println(" Face authentication state (user=" + userId + ")");
+ pw.println(" isFaceClass3=" + isFaceClass3());
pw.println(" allowed="
+ (face != null && isUnlockingWithBiometricAllowed(face.mIsStrongBiometric)));
pw.println(" auth'd="
@@ -4240,7 +4373,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* Cancels all operations in the scheduler if it is hung for 10 seconds.
*/
public void startBiometricWatchdog() {
- if (mFaceManager != null) {
+ if (mFaceManager != null && !isFaceAuthInteractorEnabled()) {
mFaceManager.scheduleWatchdog();
}
if (mFpm != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index ac0a3fd8dbc4..651c9796140e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -18,8 +18,8 @@ package com.android.keyguard;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import android.util.Property;
import android.view.View;
-import android.view.ViewPropertyAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.log.LogBuffer;
@@ -28,13 +28,14 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.phone.AnimatorHandle;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.google.errorprone.annotations.CompileTimeConstant;
+import java.util.function.Consumer;
+
/**
* Helper class for updating visibility of keyguard views based on keyguard and status bar state.
* This logic is shared by both the keyguard status view and the keyguard user switcher.
@@ -48,7 +49,6 @@ public class KeyguardVisibilityHelper {
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mAnimateYPos;
private boolean mKeyguardViewVisibilityAnimating;
- private AnimatorHandle mKeyguardAnimatorHandle;
private boolean mLastOccludedState = false;
private final AnimationProperties mAnimationProperties = new AnimationProperties();
private final LogBuffer mLogBuffer;
@@ -85,51 +85,49 @@ public class KeyguardVisibilityHelper {
boolean keyguardFadingAway,
boolean goingToFullShade,
int oldStatusBarState) {
- if (mKeyguardAnimatorHandle != null) {
- mKeyguardAnimatorHandle.cancel();
- mKeyguardAnimatorHandle = null;
- }
- mView.animate().cancel();
+ PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA);
boolean isOccluded = mKeyguardStateController.isOccluded();
mKeyguardViewVisibilityAnimating = false;
if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
&& statusBarState != KEYGUARD) || goingToFullShade) {
mKeyguardViewVisibilityAnimating = true;
- mView.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setDuration(160)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(
- mAnimateKeyguardStatusViewGoneEndRunnable);
+
+ AnimationProperties animProps = new AnimationProperties()
+ .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT)
+ .setAnimationEndAction(mSetGoneEndAction);
if (keyguardFadingAway) {
- mView.animate()
- .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
- .start();
+ animProps
+ .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+ .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration());
log("goingToFullShade && keyguardFadingAway");
} else {
+ animProps.setDelay(0).setDuration(160);
log("goingToFullShade && !keyguardFadingAway");
}
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */);
} else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
mView.setVisibility(View.VISIBLE);
mKeyguardViewVisibilityAnimating = true;
mView.setAlpha(0f);
- mView.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setDuration(320)
- .setInterpolator(Interpolators.ALPHA_IN)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
- log("keyguardFadingAway transition w/ Y Animation");
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 1f,
+ new AnimationProperties()
+ .setDelay(0)
+ .setDuration(320)
+ .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN)
+ .setAnimationEndAction(
+ property -> mSetVisibleEndRunnable.run()),
+ true /* animate */);
+ log("keyguardFadingAway transition w/ Y Aniamtion");
} else if (statusBarState == KEYGUARD) {
if (keyguardFadingAway) {
mKeyguardViewVisibilityAnimating = true;
- ViewPropertyAnimator animator = mView.animate()
- .alpha(0)
- .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
- .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable);
+ AnimationProperties animProps = new AnimationProperties()
+ .setDelay(0)
+ .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN)
+ .setAnimationEndAction(mSetInvisibleEndAction);
if (mAnimateYPos) {
float target = mView.getY() - mView.getHeight() * 0.05f;
int delay = 0;
@@ -141,21 +139,24 @@ public class KeyguardVisibilityHelper {
PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target,
mAnimationProperties,
true /* animate */);
- animator.setDuration(duration)
- .setStartDelay(delay);
+ animProps.setDuration(duration)
+ .setDelay(delay);
log("keyguardFadingAway transition w/ Y Aniamtion");
} else {
log("keyguardFadingAway transition w/o Y Animation");
}
- animator.start();
+ PropertyAnimator.setProperty(
+ mView, AnimatableProperty.ALPHA, 0f,
+ animProps,
+ true /* animate */);
} else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) {
log("ScreenOff transition");
mKeyguardViewVisibilityAnimating = true;
// Ask the screen off animation controller to animate the keyguard visibility for us
// since it may need to be cancelled due to keyguard lifecycle events.
- mKeyguardAnimatorHandle = mScreenOffAnimationController.animateInKeyguard(
- mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
+ mScreenOffAnimationController.animateInKeyguard(
+ mView, mSetVisibleEndRunnable);
} else {
log("Direct set Visibility to VISIBLE");
mView.setVisibility(View.VISIBLE);
@@ -169,19 +170,25 @@ public class KeyguardVisibilityHelper {
mLastOccludedState = isOccluded;
}
- private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
- mKeyguardViewVisibilityAnimating = false;
- mView.setVisibility(View.INVISIBLE);
- log("Callback Set Visibility to INVISIBLE");
+ private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() {
+ @Override
+ public void accept(Property property) {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.INVISIBLE);
+ log("Callback Set Visibility to INVISIBLE");
+ }
};
- private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
- mKeyguardViewVisibilityAnimating = false;
- mView.setVisibility(View.GONE);
- log("CallbackSet Visibility to GONE");
+ private final Consumer<Property> mSetGoneEndAction = new Consumer<>() {
+ @Override
+ public void accept(Property property) {
+ mKeyguardViewVisibilityAnimating = false;
+ mView.setVisibility(View.GONE);
+ log("CallbackSet Visibility to GONE");
+ }
};
- private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+ private final Runnable mSetVisibleEndRunnable = () -> {
mKeyguardViewVisibilityAnimating = false;
mView.setVisibility(View.VISIBLE);
log("Callback Set Visibility to VISIBLE");
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 235a8bca6d1e..5f2afe8f755d 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -294,6 +294,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
+ if (wasShowingFpIcon) {
+ // fp icon was shown by UdfpsView, and now we still want to animate the transition
+ // in this drawable
+ mView.updateIcon(ICON_FINGERPRINT, false);
+ }
mView.updateIcon(ICON_LOCK, false);
mView.setContentDescription(mLockedLabel);
mView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 6e98a1805d62..cde8ff78c620 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -33,6 +33,7 @@ import com.android.systemui.shared.clocks.DefaultClockProvider;
import dagger.Module;
import dagger.Provides;
+
import kotlinx.coroutines.CoroutineDispatcher;
import kotlinx.coroutines.CoroutineScope;
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt b/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt
deleted file mode 100644
index 2f03259766c0..000000000000
--- a/packages/SystemUI/src/com/android/systemui/ChooserPinMigration.kt
+++ /dev/null
@@ -1,140 +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
-
-import android.content.ComponentName
-import android.content.Context
-import android.content.Context.MODE_PRIVATE
-import android.content.Intent
-import android.content.SharedPreferences
-import android.os.Bundle
-import android.os.Environment
-import android.os.storage.StorageManager
-import android.util.Log
-import androidx.core.util.Supplier
-import com.android.internal.R
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import java.io.File
-import javax.inject.Inject
-
-/**
- * Performs a migration of pinned targets to the unbundled chooser if legacy data exists.
- *
- * Sends an explicit broadcast with the contents of the legacy pin preferences. The broadcast is
- * protected by the RECEIVE_CHOOSER_PIN_MIGRATION permission. This class requires the
- * ADD_CHOOSER_PINS permission in order to be able to send this broadcast.
- */
-class ChooserPinMigration
-@Inject
-constructor(
- private val context: Context,
- private val featureFlags: FeatureFlags,
- private val broadcastSender: BroadcastSender,
- legacyPinPrefsFileSupplier: LegacyPinPrefsFileSupplier,
-) : CoreStartable {
-
- private val legacyPinPrefsFile = legacyPinPrefsFileSupplier.get()
- private val chooserComponent =
- ComponentName.unflattenFromString(
- context.resources.getString(R.string.config_chooserActivity)
- )
-
- override fun start() {
- if (migrationIsRequired()) {
- doMigration()
- }
- }
-
- private fun migrationIsRequired(): Boolean {
- return featureFlags.isEnabled(Flags.CHOOSER_MIGRATION_ENABLED) &&
- legacyPinPrefsFile.exists() &&
- chooserComponent?.packageName != null
- }
-
- private fun doMigration() {
- Log.i(TAG, "Beginning migration")
-
- val legacyPinPrefs = context.getSharedPreferences(legacyPinPrefsFile, MODE_PRIVATE)
-
- if (legacyPinPrefs.all.isEmpty()) {
- Log.i(TAG, "No data to migrate, deleting legacy file")
- } else {
- sendSharedPreferences(legacyPinPrefs)
- Log.i(TAG, "Legacy data sent, deleting legacy preferences")
-
- val legacyPinPrefsEditor = legacyPinPrefs.edit()
- legacyPinPrefsEditor.clear()
- if (!legacyPinPrefsEditor.commit()) {
- Log.e(TAG, "Failed to delete legacy preferences")
- return
- }
- }
-
- if (!legacyPinPrefsFile.delete()) {
- Log.e(TAG, "Legacy preferences deleted, but failed to delete legacy preferences file")
- return
- }
-
- Log.i(TAG, "Legacy preference deletion complete")
- }
-
- private fun sendSharedPreferences(sharedPreferences: SharedPreferences) {
- val bundle = Bundle()
-
- sharedPreferences.all.entries.forEach { (key, value) ->
- when (value) {
- is Boolean -> bundle.putBoolean(key, value)
- else -> Log.e(TAG, "Unsupported preference type for $key: ${value?.javaClass}")
- }
- }
-
- sendBundle(bundle)
- }
-
- private fun sendBundle(bundle: Bundle) {
- val intent =
- Intent().apply {
- `package` = chooserComponent?.packageName!!
- action = BROADCAST_ACTION
- putExtras(bundle)
- }
- broadcastSender.sendBroadcast(intent, BROADCAST_PERMISSION)
- }
-
- companion object {
- private const val TAG = "PinnedShareTargetMigration"
- private const val BROADCAST_ACTION = "android.intent.action.CHOOSER_PIN_MIGRATION"
- private const val BROADCAST_PERMISSION = "android.permission.RECEIVE_CHOOSER_PIN_MIGRATION"
-
- class LegacyPinPrefsFileSupplier @Inject constructor(private val context: Context) :
- Supplier<File> {
-
- override fun get(): File {
- val packageDirectory =
- Environment.getDataUserCePackageDirectory(
- StorageManager.UUID_PRIVATE_INTERNAL,
- context.userId,
- context.packageName,
- )
- val sharedPrefsDirectory = File(packageDirectory, "shared_prefs")
- return File(sharedPrefsDirectory, "chooser_pin_settings.xml")
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 179eb391af4e..a3e7d71a92f6 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -35,6 +35,7 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
import com.android.systemui.animation.Interpolators
+import com.android.systemui.biometrics.AuthController
import com.android.systemui.log.ScreenDecorationsLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.asIndenting
@@ -52,6 +53,7 @@ class FaceScanningOverlay(
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
val mainExecutor: Executor,
val logger: ScreenDecorationsLogger,
+ val authController: AuthController,
) : ScreenDecorations.DisplayCutoutView(context, pos) {
private var showScanningAnim = false
private val rimPaint = Paint()
@@ -102,7 +104,9 @@ class FaceScanningOverlay(
}
override fun enableShowProtection(show: Boolean) {
- val showScanningAnimNow = keyguardUpdateMonitor.isFaceDetectionRunning && show
+ val animationRequired =
+ keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing
+ val showScanningAnimNow = animationRequired && show
if (showScanningAnimNow == showScanningAnim) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 12b5705190d9..f3c71da63594 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -210,8 +210,8 @@ public class SystemActions implements CoreStartable {
// Saving in instance variable since to prevent GC since
// NotificationShadeWindowController.registerCallback() only keeps weak references.
mNotificationShadeCallback =
- (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded,
- isDreaming) ->
+ (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
+ panelExpanded, isDreaming) ->
registerOrUnregisterDismissNotificationShadeAction();
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 92344dbbfe15..09992290a9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1005,9 +1005,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
* not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
*/
public boolean isRearFpsSupported() {
- for (FingerprintSensorPropertiesInternal prop: mFpProps) {
- if (prop.sensorType == TYPE_REAR) {
- return true;
+ if (mFpProps != null) {
+ for (FingerprintSensorPropertiesInternal prop: mFpProps) {
+ if (prop.sensorType == TYPE_REAR) {
+ return true;
+ }
}
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index d15a2afa0d4a..6c62a3936910 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -39,7 +39,7 @@ constructor(
private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
mainExecutor.execute {
action?.let {
- if (event.tracking || event.expanded) {
+ if (event.tracking || (event.expanded && event.fraction > 0)) {
Log.v(TAG, "Detected panel interaction, event: $event")
it.onPanelInteraction.run()
disable()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index ac30311e1f96..7a237591a212 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -151,6 +152,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final DumpManager mDumpManager;
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@@ -563,6 +565,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
(TouchProcessorResult.ProcessedTouch) result;
final NormalizedTouchData data = processedTouch.getTouchData();
+ boolean shouldPilfer = false;
mActivePointerId = processedTouch.getPointerOnSensorId();
switch (processedTouch.getEvent()) {
case DOWN:
@@ -581,8 +584,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
mStatusBarStateController.isDozing());
// Pilfer if valid overlap, don't allow following events to reach keyguard
- mInputManager.pilferPointers(
- mOverlay.getOverlayView().getViewRootImpl().getInputToken());
+ shouldPilfer = true;
break;
case UP:
@@ -621,6 +623,12 @@ public class UdfpsController implements DozeReceiver, Dumpable {
// Always pilfer pointers that are within sensor area or when alternate bouncer is showing
if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)
|| mAlternateBouncerInteractor.isVisibleState()) {
+ shouldPilfer = true;
+ }
+
+ // Execute the pilfer, never pilfer if a vertical swipe is in progress
+ if (shouldPilfer && mLockscreenShadeTransitionController.getQSDragProgress() == 0f
+ && !mPrimaryBouncerInteractor.isInTransit()) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
}
@@ -812,7 +820,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull SecureSettings secureSettings,
@NonNull InputManager inputManager,
- @NonNull UdfpsUtils udfpsUtils) {
+ @NonNull UdfpsUtils udfpsUtils,
+ @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -876,6 +885,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
}
return Unit.INSTANCE;
});
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1135,6 +1145,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
if (!mOnFingerDown) {
playStartHaptic();
+ mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index f6b71336675f..691017b220f8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -324,10 +324,6 @@ public class BrightLineFalsingManager implements FalsingManager {
@Override
public boolean isFalseLongTap(@Penalty int penalty) {
- if (!mFeatureFlags.isEnabled(Flags.FALSING_FOR_LONG_TAPS)) {
- return false;
- }
-
checkDestroyed();
if (skipFalsing(GENERIC)) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index edda87527b1d..63b4288ce055 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -21,7 +21,6 @@ import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
@@ -36,7 +35,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.flags.FeatureFlags;
import javax.inject.Inject;
import javax.inject.Provider;
@@ -59,7 +57,6 @@ public class ClipboardListener implements
private final Provider<ClipboardOverlayController> mOverlayProvider;
private final ClipboardToast mClipboardToast;
private final ClipboardManager mClipboardManager;
- private final FeatureFlags mFeatureFlags;
private final UiEventLogger mUiEventLogger;
private ClipboardOverlay mClipboardOverlay;
@@ -68,13 +65,11 @@ public class ClipboardListener implements
Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
ClipboardToast clipboardToast,
ClipboardManager clipboardManager,
- FeatureFlags featureFlags,
UiEventLogger uiEventLogger) {
mContext = context;
mOverlayProvider = clipboardOverlayControllerProvider;
mClipboardToast = clipboardToast;
mClipboardManager = clipboardManager;
- mFeatureFlags = featureFlags;
mUiEventLogger = uiEventLogger;
}
@@ -113,11 +108,7 @@ public class ClipboardListener implements
} else {
mUiEventLogger.log(CLIPBOARD_OVERLAY_UPDATED, 0, clipSource);
}
- if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
- mClipboardOverlay.setClipData(clipData, clipSource);
- } else {
- mClipboardOverlay.setClipDataLegacy(clipData, clipSource);
- }
+ mClipboardOverlay.setClipData(clipData, clipSource);
mClipboardOverlay.setOnSessionCompleteListener(() -> {
// Session is complete, free memory until it's needed again.
mClipboardOverlay = null;
@@ -160,8 +151,6 @@ public class ClipboardListener implements
}
interface ClipboardOverlay {
- void setClipDataLegacy(ClipData clipData, String clipSource);
-
void setClipData(ClipData clipData, String clipSource);
void setOnSessionCompleteListener(Runnable runnable);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index e6affb06cacc..5230159aaef4 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -32,7 +32,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
import android.animation.Animator;
@@ -40,20 +39,14 @@ import android.animation.AnimatorListenerAdapter;
import android.app.RemoteAction;
import android.content.BroadcastReceiver;
import android.content.ClipData;
-import android.content.ClipDescription;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Looper;
import android.provider.DeviceConfig;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Size;
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
@@ -72,7 +65,6 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.screenshot.TimeoutHandler;
-import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -170,9 +162,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
@Override
public void onMinimizedViewTapped() {
- if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
- animateFromMinimized();
- }
+ animateFromMinimized();
}
};
@@ -255,11 +245,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
@VisibleForTesting
void onInsetsChanged(WindowInsets insets, int orientation) {
mView.setInsets(insets, orientation);
- if (mFeatureFlags.isEnabled(CLIPBOARD_MINIMIZED_LAYOUT)) {
- if (shouldShowMinimized(insets) && !mIsMinimized) {
- mIsMinimized = true;
- mView.setMinimized(true);
- }
+ if (shouldShowMinimized(insets) && !mIsMinimized) {
+ mIsMinimized = true;
+ mView.setMinimized(true);
}
}
@@ -401,61 +389,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
});
}
- @Override // ClipboardListener.ClipboardOverlay
- public void setClipDataLegacy(ClipData clipData, String clipSource) {
- if (mExitAnimator != null && mExitAnimator.isRunning()) {
- mExitAnimator.cancel();
- }
- reset();
- mClipboardLogger.setClipSource(clipSource);
- String accessibilityAnnouncement = mContext.getString(R.string.clipboard_content_copied);
-
- boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
- && clipData.getDescription().getExtras()
- .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
- boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)
- && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource);
- if (clipData == null || clipData.getItemCount() == 0) {
- mView.showDefaultTextPreview();
- } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
- ClipData.Item item = clipData.getItemAt(0);
- if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
- if (item.getTextLinks() != null) {
- classifyText(clipData.getItemAt(0), clipSource);
- }
- }
- if (isSensitive) {
- showEditableText(mContext.getString(R.string.clipboard_asterisks), true);
- } else {
- showEditableText(item.getText(), false);
- }
- mOnShareTapped = () -> shareContent(clipData);
- mView.showShareChip();
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_text_copied);
- } else if (clipData.getItemAt(0).getUri() != null) {
- if (tryShowEditableImage(clipData.getItemAt(0).getUri(), isSensitive)) {
- accessibilityAnnouncement = mContext.getString(R.string.clipboard_image_copied);
- }
- mOnShareTapped = () -> shareContent(clipData);
- mView.showShareChip();
- } else {
- mView.showDefaultTextPreview();
- }
- if (!isRemote) {
- maybeShowRemoteCopy(clipData);
- }
- animateIn();
- mView.announceForAccessibility(accessibilityAnnouncement);
- if (isRemote) {
- mTimeoutHandler.cancelTimeout();
- mOnUiUpdate = null;
- } else {
- mOnUiUpdate = mTimeoutHandler::resetTimeout;
- mOnUiUpdate.run();
- }
- }
-
private void maybeShowRemoteCopy(ClipData clipData) {
Intent remoteCopyIntent = IntentCreator.getRemoteCopyIntent(clipData, mContext);
// Only show remote copy if it's available.
@@ -478,22 +411,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
mOnSessionCompleteListener = runnable;
}
- private void classifyText(ClipData.Item item, String source) {
- mBgExecutor.execute(() -> {
- Optional<RemoteAction> action = mClipboardUtils.getAction(item, source);
- mView.post(() -> {
- mView.resetActionChips();
- action.ifPresent(remoteAction -> {
- mView.setActionChip(remoteAction, () -> {
- mClipboardLogger.logSessionComplete(CLIPBOARD_OVERLAY_ACTION_TAPPED);
- animateOut();
- });
- mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_ACTION_SHOWN);
- });
- });
- });
- }
-
private void monitorOutsideTouches() {
InputManager inputManager = mContext.getSystemService(InputManager.class);
mInputMonitor = inputManager.monitorGestureInput("clipboard overlay", 0);
@@ -534,43 +451,6 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
animateOut();
}
- private void showEditableText(CharSequence text, boolean hidden) {
- mView.showTextPreview(text.toString(), hidden);
- mView.setEditAccessibilityAction(true);
- mOnPreviewTapped = this::editText;
- }
-
- private boolean tryShowEditableImage(Uri uri, boolean isSensitive) {
- Runnable listener = () -> editImage(uri);
- ContentResolver resolver = mContext.getContentResolver();
- String mimeType = resolver.getType(uri);
- boolean isEditableImage = mimeType != null && mimeType.startsWith("image");
- if (isSensitive) {
- mView.showImagePreview(null);
- if (isEditableImage) {
- mOnPreviewTapped = listener;
- mView.setEditAccessibilityAction(true);
- }
- } else if (isEditableImage) { // if the MIMEtype is image, try to load
- try {
- int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale);
- // The width of the view is capped, height maintains aspect ratio, so allow it to be
- // taller if needed.
- Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null);
- mView.showImagePreview(thumbnail);
- mView.setEditAccessibilityAction(true);
- mOnPreviewTapped = listener;
- } catch (IOException e) {
- Log.e(TAG, "Thumbnail loading failed", e);
- mView.showDefaultTextPreview();
- isEditableImage = false;
- }
- } else {
- mView.showDefaultTextPreview();
- }
- return isEditableImage;
- }
-
private void animateIn() {
if (mEnterAnimator != null && mEnterAnimator.isRunning()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
index 25caaeac2c38..758a6563323e 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -88,25 +88,4 @@ class ClipboardOverlayUtils {
}
return actions;
}
-
- public Optional<RemoteAction> getAction(ClipData.Item item, String source) {
- return getActions(item).stream().filter(remoteAction -> {
- ComponentName component = remoteAction.getActionIntent().getIntent().getComponent();
- return component != null && !TextUtils.equals(source, component.getPackageName());
- }).findFirst();
- }
-
- private ArrayList<RemoteAction> getActions(ClipData.Item item) {
- ArrayList<RemoteAction> actions = new ArrayList<>();
- for (TextLinks.TextLink link : item.getTextLinks().getLinks()) {
- // skip classification for incidental entities
- if (link.getEnd() - link.getStart()
- >= item.getText().length() * MINIMUM_ENTITY_PROPORTION) {
- TextClassification classification = mTextClassifier.classifyText(
- item.getText(), link.getStart(), link.getEnd(), null);
- actions.addAll(classification.getActions());
- }
- }
- return actions;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index 6a6c3eb05399..0869351e727b 100644
--- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -16,10 +16,10 @@
package com.android.systemui.common.shared.model
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
/** Models an icon with a specific tint. */
data class TintedIcon(
val icon: Icon,
- @ColorRes val tint: Int?,
+ @AttrRes val tint: Int?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
index bcc5932dcf30..5c5723fa0a5e 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.common.ui.binder
import android.widget.ImageView
+import com.android.settingslib.Utils
import com.android.systemui.common.shared.model.TintedIcon
object TintedIconViewBinder {
@@ -33,7 +34,7 @@ object TintedIconViewBinder {
IconViewBinder.bind(tintedIcon.icon, view)
view.imageTintList =
if (tintedIcon.tint != null) {
- view.resources.getColorStateList(tintedIcon.tint, view.context.theme)
+ Utils.getColorAttr(view.context, tintedIcon.tint)
} else {
null
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
new file mode 100644
index 000000000000..81ed07653f26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/MotionEventExt.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.view
+
+import android.util.MathUtils
+import android.view.MotionEvent
+
+/**
+ * Returns the distance from the raw position of this [MotionEvent] and the given coordinates.
+ * Because this is all expected to be in the coordinate space of the display and not the view,
+ * applying mutations to the view (such as scaling animations) does not affect the distance
+ * measured.
+ * @param xOnDisplay the x coordinate relative to the display
+ * @param yOnDisplay the y coordinate relative to the display
+ * @return distance from the raw position of this [MotionEvent] and the given coordinates
+ */
+fun MotionEvent.rawDistanceFrom(
+ xOnDisplay: Float,
+ yOnDisplay: Float,
+): Float {
+ return MathUtils.dist(this.rawX, this.rawY, xOnDisplay, yOnDisplay)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index df236e7f9c8f..9bf6b2a5b42b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -17,7 +17,6 @@
package com.android.systemui.dagger
import com.android.keyguard.KeyguardBiometricLockoutLogger
-import com.android.systemui.ChooserPinMigration
import com.android.systemui.ChooserSelector
import com.android.systemui.CoreStartable
import com.android.systemui.LatencyTester
@@ -76,13 +75,6 @@ abstract class SystemUICoreStartableModule {
@ClassKey(AuthController::class)
abstract fun bindAuthController(service: AuthController): CoreStartable
- /** Inject into ChooserPinMigration. */
- @Binds
- @IntoMap
- @ClassKey(ChooserPinMigration::class)
- @PerUser
- abstract fun bindChooserPinMigration(sysui: ChooserPinMigration): CoreStartable
-
/** Inject into ChooserCoreStartable. */
@Binds
@IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 63a4fd2189d8..7945470b424a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -85,6 +85,8 @@ import com.android.systemui.statusbar.notification.collection.inflation.Notifica
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
@@ -116,16 +118,16 @@ import com.android.systemui.wallet.dagger.WalletModule;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
import dagger.Binds;
import dagger.BindsOptionalOf;
import dagger.Module;
import dagger.Provides;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import javax.inject.Named;
+
/**
* A dagger module for injecting components of System UI that are required by System UI.
*
@@ -315,4 +317,11 @@ public abstract class SystemUIModule {
@Binds
abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
LargeScreenShadeInterpolatorImpl impl);
+
+ @SysUISingleton
+ @Provides
+ static VisualInterruptionDecisionProvider provideVisualInterruptionDecisionProvider(
+ NotificationInterruptStateProvider innerProvider) {
+ return new NotificationInterruptStateProviderWrapper(innerProvider);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 88c0c50d09a5..4e62104034ee 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -98,7 +98,8 @@ class FaceScanningProviderFactory @Inject constructor(
}
fun shouldShowFaceScanningAnim(): Boolean {
- return canShowFaceScanningAnim() && keyguardUpdateMonitor.isFaceDetectionRunning
+ return canShowFaceScanningAnim() &&
+ (keyguardUpdateMonitor.isFaceDetectionRunning || authController.isShowing)
}
}
@@ -142,6 +143,7 @@ class FaceScanningOverlayProviderImpl(
keyguardUpdateMonitor,
mainExecutor,
logger,
+ authController,
)
view.id = viewId
view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
index 7f44463f1191..aca621bd9e55 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java
@@ -41,6 +41,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -195,7 +196,14 @@ public class DreamOverlayTouchMonitor {
* Called by the monitor when this session is removed.
*/
private void onRemoved() {
- mCallbacks.forEach(callback -> callback.onRemoved());
+ mEventListeners.clear();
+ mGestureListeners.clear();
+ final Iterator<Callback> iter = mCallbacks.iterator();
+ while (iter.hasNext()) {
+ final Callback callback = iter.next();
+ callback.onRemoved();
+ iter.remove();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 0bc8506f272d..f01749a6c031 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -65,7 +65,7 @@ object Flags {
val FSI_ON_DND_UPDATE = releasedFlag(259130119, "fsi_on_dnd_update")
// TODO(b/254512538): Tracking Bug
- val INSTANT_VOICE_REPLY = releasedFlag(111, "instant_voice_reply")
+ val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
// TODO(b/254512425): Tracking Bug
val NOTIFICATION_MEMORY_MONITOR_ENABLED =
@@ -103,10 +103,10 @@ object Flags {
val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
releasedFlag(254647461, "filter_unseen_notifs_on_keyguard")
- // TODO(b/263414400): Tracking Bug
+ // TODO(b/277338665): Tracking Bug
@JvmField
- val NOTIFICATION_ANIMATE_BIG_PICTURE =
- releasedFlag(120, "notification_animate_big_picture")
+ val NOTIFICATION_SHELF_REFACTOR =
+ unreleasedFlag(271161129, "notification_shelf_refactor")
@JvmField
val ANIMATED_NOTIFICATION_SHADE_INSETS =
@@ -132,6 +132,11 @@ object Flags {
// TODO(b/254512676): Tracking Bug
@JvmField val LOCKSCREEN_CUSTOM_CLOCKS = releasedFlag(207, "lockscreen_custom_clocks")
+ // TODO(b/275694445): Tracking Bug
+ @JvmField
+ val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING = unreleasedFlag(208,
+ "lockscreen_without_secure_lock_when_dreaming")
+
/**
* Whether the clock on a wide lock screen should use the new "stepping" animation for moving
* the digits when the clock moves.
@@ -230,6 +235,12 @@ object Flags {
@JvmField
val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
+ /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
+ // TODO(b/277220285): Tracking bug.
+ @JvmField
+ val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
+ unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -395,7 +406,7 @@ object Flags {
val MEDIA_RETAIN_RECOMMENDATIONS = releasedFlag(916, "media_retain_recommendations")
// TODO(b/270437894): Tracking Bug
- val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
+ val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume", teamfood = true)
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
@@ -403,9 +414,6 @@ object Flags {
// TODO(b/254512758): Tracking Bug
@JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
- // TODO(b/270882464): Tracking Bug
- val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2")
-
// TODO(b/265045965): Tracking Bug
val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
@@ -592,9 +600,6 @@ object Flags {
// TODO(b/254512507): Tracking Bug
val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled")
- // TODO(b/266983432) Tracking Bug
- val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions")
-
// TODO(b/266982749) Tracking Bug
val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action")
@@ -605,13 +610,8 @@ object Flags {
val SHARESHEET_SCROLLABLE_IMAGE_PREVIEW =
releasedFlag(1504, "sharesheet_scrollable_image_preview")
- // TODO(b/274137694) Tracking Bug
- val CHOOSER_MIGRATION_ENABLED = unreleasedFlag(1505, "chooser_migration_enabled")
-
// 1700 - clipboard
@JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
- // TODO(b/267162944): Tracking bug
- @JvmField val CLIPBOARD_MINIMIZED_LAYOUT = releasedFlag(1702, "clipboard_data_model")
// 1800 - shade container
@JvmField
@@ -639,9 +639,6 @@ object Flags {
val APP_PANELS_REMOVE_APPS_ALLOWED =
unreleasedFlag(2003, "app_panels_remove_apps_allowed", teamfood = true)
- // 2100 - Falsing Manager
- @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
-
// 2200 - udfps
// TODO(b/259264861): Tracking Bug
@JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
@@ -696,6 +693,11 @@ object Flags {
val KEYBOARD_BACKLIGHT_INDICATOR =
unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true)
+ // TODO(b/277192623): Tracking Bug
+ @JvmField
+ val KEYBOARD_EDUCATION =
+ unreleasedFlag(2603, "keyboard_education", teamfood = false)
+
// TODO(b/272036292): Tracking Bug
@JvmField
val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index a173f8b914db..2ef5e19bf382 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -90,9 +90,9 @@ class KeyboardBacklightDialog(
private fun updateResources() {
context.resources.apply {
- filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
- emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
- backgroundColor = getColor(R.color.backlight_indicator_background)
+ filledRectangleColor = getColor(R.color.backlight_indicator_step_filled, context.theme)
+ emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty, context.theme)
+ backgroundColor = getColor(R.color.backlight_indicator_background, context.theme)
rootProperties =
RootProperties(
cornerRadius =
@@ -224,7 +224,6 @@ class KeyboardBacklightDialog(
private fun setWindowTitle() {
val attrs = window.attributes
- // TODO(b/271796169): check if title needs to be a translatable resource.
attrs.title = "KeyboardBacklightDialog"
attrs?.y = dialogBottomMargin
window.attributes = attrs
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index c102c5b57375..416b2379eef8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -128,6 +128,8 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -1184,6 +1186,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private Lazy<ActivityLaunchAnimator> mActivityLaunchAnimator;
private Lazy<ScrimController> mScrimControllerLazy;
+ private FeatureFlags mFeatureFlags;
+
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -1214,7 +1218,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Lazy<ShadeController> shadeControllerLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowControllerLazy,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
- Lazy<ScrimController> scrimControllerLazy) {
+ Lazy<ScrimController> scrimControllerLazy,
+ FeatureFlags featureFlags) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1269,6 +1274,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mDreamOpenAnimationDuration = (int) DREAMING_ANIMATION_DURATION_MS;
mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
+
+ mFeatureFlags = featureFlags;
}
public void userActivity() {
@@ -1682,14 +1689,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
/**
- * A dream started. We should lock after the usual screen-off lock timeout but only
- * if there is a secure lock pattern.
+ * A dream started. We should lock after the usual screen-off lock timeout regardless if
+ * there is a secure lock pattern or not
*/
public void onDreamingStarted() {
mUpdateMonitor.dispatchDreamingStarted();
synchronized (this) {
+ final boolean alwaysShowKeyguard =
+ mFeatureFlags.isEnabled(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING);
if (mDeviceInteractive
- && mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser())) {
+ && (alwaysShowKeyguard ||
+ mLockPatternUtils.isSecure(KeyguardUpdateMonitor.getCurrentUser()))) {
doKeyguardLaterLocked();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
new file mode 100644
index 000000000000..a44df7e11fa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.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.keyguard.dagger
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that provides bindings for face auth classes that are injected into SysUI components that
+ * are used across different SysUI variants, where face auth is not supported.
+ *
+ * Some variants that do not support face authentication can install this module to provide a no-op
+ * implementation of the interactor.
+ */
+@Module
+interface KeyguardFaceAuthNotSupportedModule {
+ @Binds
+ fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 6ac51cd52b49..5e71458c8bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -39,6 +39,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -119,7 +120,8 @@ public class KeyguardModule {
Lazy<ShadeController> shadeController,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
Lazy<ActivityLaunchAnimator> activityLaunchAnimator,
- Lazy<ScrimController> scrimControllerLazy) {
+ Lazy<ScrimController> scrimControllerLazy,
+ FeatureFlags featureFlags) {
return new KeyguardViewMediator(
context,
userTracker,
@@ -149,7 +151,8 @@ public class KeyguardModule {
shadeController,
notificationShadeWindowController,
activityLaunchAnimator,
- scrimControllerLazy);
+ scrimControllerLazy,
+ featureFlags);
}
/** */
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 56e73980079d..5f6098b8758f 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
@@ -34,6 +34,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus
import com.android.systemui.keyguard.shared.model.AuthenticationStatus
import com.android.systemui.keyguard.shared.model.DetectionStatus
@@ -44,6 +45,8 @@ import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
@@ -78,7 +81,7 @@ interface DeviceEntryFaceAuthRepository {
val isAuthenticated: Flow<Boolean>
/** Whether face auth can run at this point. */
- val canRunFaceAuth: Flow<Boolean>
+ val canRunFaceAuth: StateFlow<Boolean>
/** Provide the current status of face authentication. */
val authenticationStatus: Flow<AuthenticationStatus>
@@ -87,10 +90,13 @@ interface DeviceEntryFaceAuthRepository {
val detectionStatus: Flow<DetectionStatus>
/** Current state of whether face authentication is locked out or not. */
- val isLockedOut: Flow<Boolean>
+ val isLockedOut: StateFlow<Boolean>
/** Current state of whether face authentication is running. */
- val isAuthRunning: Flow<Boolean>
+ val isAuthRunning: StateFlow<Boolean>
+
+ /** Whether bypass is currently enabled */
+ val isBypassEnabled: Flow<Boolean>
/**
* Trigger face authentication.
@@ -126,6 +132,9 @@ constructor(
private val keyguardRepository: KeyguardRepository,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
+ @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -166,7 +175,7 @@ constructor(
override val isAuthenticated: Flow<Boolean>
get() = _isAuthenticated
- private val bypassEnabled: Flow<Boolean> =
+ override val isBypassEnabled: Flow<Boolean> =
keyguardBypassController?.let {
conflatedCallbackFlow {
val callback =
@@ -204,6 +213,13 @@ constructor(
observeFaceAuthGatingChecks()
observeFaceDetectGatingChecks()
observeFaceAuthResettingConditions()
+ listenForSchedulingWatchdog()
+ }
+
+ private fun listenForSchedulingWatchdog() {
+ keyguardTransitionInteractor.anyStateToGoneTransition
+ .onEach { faceManager?.scheduleWatchdog() }
+ .launchIn(applicationScope)
}
private fun observeFaceAuthResettingConditions() {
@@ -221,17 +237,19 @@ constructor(
// Face detection can run only when lockscreen bypass is enabled
// & detection is supported & biometric unlock is not allowed.
listOf(
- canFaceAuthOrDetectRun(),
- logAndObserve(bypassEnabled, "bypassEnabled"),
+ canFaceAuthOrDetectRun(faceDetectLog),
+ logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
- "nonStrongBiometricIsNotAllowed"
+ "nonStrongBiometricIsNotAllowed",
+ faceDetectLog
),
// We don't want to run face detect if it's not possible to authenticate with FP
// from the bouncer. UDFPS is the only fp sensor type that won't support this.
logAndObserve(
and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
- "udfpsAuthIsNotPossibleAnymore"
+ "udfpsAuthIsNotPossibleAnymore",
+ faceDetectLog
)
)
.reduce(::and)
@@ -243,6 +261,7 @@ constructor(
cancelDetection()
}
}
+ .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
.launchIn(applicationScope)
}
@@ -251,26 +270,34 @@ constructor(
it == BiometricType.UNDER_DISPLAY_FINGERPRINT
}
- private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+ private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
return listOf(
- logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+ logAndObserve(
+ biometricSettingsRepository.isFaceEnrolled,
+ "isFaceEnrolled",
+ tableLogBuffer
+ ),
logAndObserve(
biometricSettingsRepository.isFaceAuthenticationEnabled,
- "isFaceAuthenticationEnabled"
+ "isFaceAuthenticationEnabled",
+ tableLogBuffer
),
logAndObserve(
userRepository.userSwitchingInProgress.isFalse(),
- "userSwitchingNotInProgress"
+ "userSwitchingNotInProgress",
+ tableLogBuffer
),
logAndObserve(
keyguardRepository.isKeyguardGoingAway.isFalse(),
- "keyguardNotGoingAway"
+ "keyguardNotGoingAway",
+ tableLogBuffer
),
logAndObserve(
keyguardRepository.wakefulness
.map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
.isFalse(),
- "deviceNotSleepingOrNotStartingToSleep"
+ "deviceNotSleepingOrNotStartingToSleep",
+ tableLogBuffer
),
logAndObserve(
combine(
@@ -279,15 +306,18 @@ constructor(
) { a, b ->
!a || b
},
- "secureCameraNotActiveOrAltBouncerIsShowing"
+ "secureCameraNotActiveOrAltBouncerIsShowing",
+ tableLogBuffer
),
logAndObserve(
biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
- "isFaceAuthSupportedInCurrentPosture"
+ "isFaceAuthSupportedInCurrentPosture",
+ tableLogBuffer
),
logAndObserve(
biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
- "userHasNotLockedDownDevice"
+ "userHasNotLockedDownDevice",
+ tableLogBuffer
)
)
.reduce(::and)
@@ -296,20 +326,27 @@ constructor(
private fun observeFaceAuthGatingChecks() {
// Face auth can run only if all of the gating conditions are true.
listOf(
- canFaceAuthOrDetectRun(),
- logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+ canFaceAuthOrDetectRun(faceAuthLog),
+ logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
logAndObserve(
deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
- "fpLockedOut"
+ "fpLockedOut",
+ faceAuthLog
+ ),
+ logAndObserve(
+ trustRepository.isCurrentUserTrusted.isFalse(),
+ "currentUserTrusted",
+ faceAuthLog
),
- logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed,
- "nonStrongBiometricIsAllowed"
+ "nonStrongBiometricIsAllowed",
+ faceAuthLog
),
logAndObserve(
userRepository.selectedUserInfo.map { it.isPrimary },
- "userIsPrimaryUser"
+ "userIsPrimaryUser",
+ faceAuthLog
),
)
.reduce(::and)
@@ -323,6 +360,7 @@ constructor(
cancel()
}
}
+ .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
.launchIn(applicationScope)
}
@@ -337,7 +375,6 @@ constructor(
override fun onAuthenticationAcquired(acquireInfo: Int) {
_authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
- faceAuthLogger.authenticationAcquired(acquireInfo)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
@@ -398,7 +435,7 @@ constructor(
override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
if (_isAuthRunning.value) {
- faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+ faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
return
}
@@ -435,7 +472,16 @@ constructor(
)
}
} else if (fallbackToDetection && canRunDetection.value) {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ "face auth gating check is false, falling back to detection."
+ )
detect()
+ } else {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ "face auth & detect gating check is false"
+ )
}
}
@@ -464,7 +510,7 @@ constructor(
private val currentUserId: Int
get() = userRepository.getSelectedUserInfo().id
- fun cancelDetection() {
+ private fun cancelDetection() {
detectCancellationSignal?.cancel()
detectCancellationSignal = null
}
@@ -488,10 +534,20 @@ constructor(
_isAuthRunning.value = false
}
- private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
- return cond.distinctUntilChanged().onEach {
- faceAuthLogger.observedConditionChanged(it, loggingContext)
- }
+ private fun logAndObserve(
+ cond: Flow<Boolean>,
+ conditionName: String,
+ logBuffer: TableLogBuffer
+ ): Flow<Boolean> {
+ return cond
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ logBuffer,
+ columnName = conditionName,
+ columnPrefix = "",
+ initialValue = false
+ )
+ .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
new file mode 100644
index 000000000000..6c23032143c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.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.keyguard.data.repository
+
+import javax.inject.Qualifier
+
+/** Face auth logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceAuthTableLog
diff --git a/core/java/com/android/internal/expresslog/Utils.java b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
index d82192f51662..342064f02ef2 100644
--- a/core/java/com/android/internal/expresslog/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
@@ -14,8 +14,12 @@
* limitations under the License.
*/
-package com.android.internal.expresslog;
+package com.android.systemui.keyguard.data.repository
-final class Utils {
- static native long hashString(String stringToHash);
-}
+import javax.inject.Qualifier
+
+/** Face detect logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceDetectTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index 3c66f2424c7b..ef8b40191149 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -17,8 +17,17 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
interface KeyguardFaceAuthModule {
@@ -27,5 +36,31 @@ interface KeyguardFaceAuthModule {
impl: DeviceEntryFaceAuthRepositoryImpl
): DeviceEntryFaceAuthRepository
+ @Binds
+ @IntoMap
+ @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
+ fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+
+ @Binds
+ fun keyguardFaceAuthInteractor(
+ impl: SystemUIKeyguardFaceAuthInteractor
+ ): KeyguardFaceAuthInteractor
+
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @FaceAuthTableLog
+ fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("FaceAuthTableLog", 100)
+ }
+
+ @Provides
+ @SysUISingleton
+ @FaceDetectTableLog
+ fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("FaceDetectTableLog", 100)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 8ece3183fd56..ab4abbf3d575 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard.data.repository
import android.content.Context
import android.os.UserHandle
+import android.util.LayoutDirection
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -113,30 +114,6 @@ constructor(
initialValue = emptyMap(),
)
- private val _slotPickerRepresentations: List<KeyguardSlotPickerRepresentation> by lazy {
- fun parseSlot(unparsedSlot: String): Pair<String, Int> {
- val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
- check(split.size == 2)
- val slotId = split[0]
- val slotCapacity = split[1].toInt()
- return slotId to slotCapacity
- }
-
- val unparsedSlots =
- appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
-
- val seenSlotIds = mutableSetOf<String>()
- unparsedSlots.mapNotNull { unparsedSlot ->
- val (slotId, slotCapacity) = parseSlot(unparsedSlot)
- check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
- seenSlotIds.add(slotId)
- KeyguardSlotPickerRepresentation(
- id = slotId,
- maxSelectedAffordances = slotCapacity,
- )
- }
- }
-
init {
legacySettingSyncer.startSyncing()
dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster())
@@ -211,7 +188,30 @@ constructor(
* each slot and select which affordance(s) is/are installed in each slot on the keyguard.
*/
fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
- return _slotPickerRepresentations
+ fun parseSlot(unparsedSlot: String): Pair<String, Int> {
+ val split = unparsedSlot.split(SLOT_CONFIG_DELIMITER)
+ check(split.size == 2)
+ val slotId = split[0]
+ val slotCapacity = split[1].toInt()
+ return slotId to slotCapacity
+ }
+
+ val unparsedSlots =
+ appContext.resources.getStringArray(R.array.config_keyguardQuickAffordanceSlots)
+ if (appContext.resources.configuration.layoutDirection == LayoutDirection.RTL) {
+ unparsedSlots.reverse()
+ }
+
+ val seenSlotIds = mutableSetOf<String>()
+ return unparsedSlots.mapNotNull { unparsedSlot ->
+ val (slotId, slotCapacity) = parseSlot(unparsedSlot)
+ check(!seenSlotIds.contains(slotId)) { "Duplicate slot \"$slotId\"!" }
+ seenSlotIds.add(slotId)
+ KeyguardSlotPickerRepresentation(
+ id = slotId,
+ maxSelectedAffordances = slotCapacity,
+ )
+ }
}
private inner class Dumpster : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
new file mode 100644
index 000000000000..06ae11fe810c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.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.keyguard.domain.interactor
+
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run.
+ */
+interface KeyguardFaceAuthInteractor {
+
+ /** Current authentication status */
+ val authenticationStatus: Flow<AuthenticationStatus>
+
+ /** Current detection status */
+ val detectionStatus: Flow<DetectionStatus>
+
+ /** Can face auth be run right now */
+ fun canFaceAuthRun(): Boolean
+
+ /** Whether face auth is currently running or not. */
+ fun isRunning(): Boolean
+
+ /** Whether face auth is in lock out state. */
+ fun isLockedOut(): Boolean
+
+ /**
+ * Register listener for use from code that cannot use [authenticationStatus] or
+ * [detectionStatus]
+ */
+ fun registerListener(listener: FaceAuthenticationListener)
+
+ /** Unregister previously registered listener */
+ fun unregisterListener(listener: FaceAuthenticationListener)
+
+ /** Whether the face auth interactor is enabled or not. */
+ fun isEnabled(): Boolean
+
+ fun onUdfpsSensorTouched()
+ fun onAssistantTriggeredOnLockScreen()
+ fun onDeviceLifted()
+ fun onQsExpansionStared()
+ fun onNotificationPanelClicked()
+ fun onSwipeUpOnBouncer()
+}
+
+/**
+ * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
+ * flows.
+ */
+interface FaceAuthenticationListener {
+ /** Receive face authentication status updates */
+ fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+
+ /** Receive status updates whenever face detection runs */
+ fun onDetectionStatusChanged(status: DetectionStatus)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
index 6525a13fc44f..ea6700e92731 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
@@ -17,29 +17,29 @@
package com.android.systemui.keyguard.domain.interactor
-import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.view.accessibility.AccessibilityManager
+import androidx.annotation.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardRepository
-import com.android.systemui.keyguard.domain.model.KeyguardSettingsPopupMenuModel
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Business logic for use-cases related to the keyguard long-press feature. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -54,18 +55,16 @@ import kotlinx.coroutines.flow.stateIn
class KeyguardLongPressInteractor
@Inject
constructor(
- @Application unsafeContext: Context,
- @Application scope: CoroutineScope,
+ @Application private val scope: CoroutineScope,
transitionInteractor: KeyguardTransitionInteractor,
repository: KeyguardRepository,
- private val activityStarter: ActivityStarter,
private val logger: UiEventLogger,
private val featureFlags: FeatureFlags,
broadcastDispatcher: BroadcastDispatcher,
+ private val accessibilityManager: AccessibilityManagerWrapper,
) {
- private val appContext = unsafeContext.applicationContext
-
- private val _isLongPressHandlingEnabled: StateFlow<Boolean> =
+ /** Whether the long-press handling feature should be enabled. */
+ val isLongPressHandlingEnabled: StateFlow<Boolean> =
if (isFeatureEnabled()) {
combine(
transitionInteractor.finishedKeyguardState.map {
@@ -84,19 +83,35 @@ constructor(
initialValue = false,
)
- /** Whether the long-press handling feature should be enabled. */
- val isLongPressHandlingEnabled: Flow<Boolean> = _isLongPressHandlingEnabled
-
- private val _menu = MutableStateFlow<KeyguardSettingsPopupMenuModel?>(null)
- /** Model for a menu that should be shown; `null` when no menu should be shown. */
- val menu: Flow<KeyguardSettingsPopupMenuModel?> =
- isLongPressHandlingEnabled.flatMapLatest { isEnabled ->
- if (isEnabled) {
- _menu
- } else {
- flowOf(null)
+ private val _isMenuVisible = MutableStateFlow(false)
+ /** Model for whether the menu should be shown. */
+ val isMenuVisible: StateFlow<Boolean> =
+ isLongPressHandlingEnabled
+ .flatMapLatest { isEnabled ->
+ if (isEnabled) {
+ _isMenuVisible.asStateFlow()
+ } else {
+ // Reset the state so we don't see a menu when long-press handling is enabled
+ // again in the future.
+ _isMenuVisible.value = false
+ flowOf(false)
+ }
}
- }
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ private val _shouldOpenSettings = MutableStateFlow(false)
+ /**
+ * Whether the long-press accessible "settings" flow should be opened.
+ *
+ * Note that [onSettingsShown] must be invoked to consume this, once the settings are opened.
+ */
+ val shouldOpenSettings = _shouldOpenSettings.asStateFlow()
+
+ private var delayedHideMenuJob: Job? = null
init {
if (isFeatureEnabled()) {
@@ -110,15 +125,46 @@ constructor(
}
/** Notifies that the user has long-pressed on the lock screen. */
- fun onLongPress(x: Int, y: Int) {
- if (!_isLongPressHandlingEnabled.value) {
+ fun onLongPress() {
+ if (!isLongPressHandlingEnabled.value) {
return
}
- showMenu(
- x = x,
- y = y,
- )
+ if (featureFlags.isEnabled(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP)) {
+ showSettings()
+ } else {
+ showMenu()
+ }
+ }
+
+ /** Notifies that the user has touched outside of the pop-up. */
+ fun onTouchedOutside() {
+ hideMenu()
+ }
+
+ /** Notifies that the user has started a touch gesture on the menu. */
+ fun onMenuTouchGestureStarted() {
+ cancelAutomaticMenuHiding()
+ }
+
+ /** Notifies that the user has started a touch gesture on the menu. */
+ fun onMenuTouchGestureEnded(isClick: Boolean) {
+ if (isClick) {
+ hideMenu()
+ logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+ showSettings()
+ } else {
+ scheduleAutomaticMenuHiding()
+ }
+ }
+
+ /** Notifies that the settings UI has been shown, consuming the event to show it. */
+ fun onSettingsShown() {
+ _shouldOpenSettings.value = false
+ }
+
+ private fun showSettings() {
+ _shouldOpenSettings.value = true
}
private fun isFeatureEnabled(): Boolean {
@@ -126,51 +172,40 @@ constructor(
featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI)
}
- /** Updates application state to ask to show the menu at the given coordinates. */
- private fun showMenu(
- x: Int,
- y: Int,
- ) {
- _menu.value =
- KeyguardSettingsPopupMenuModel(
- position =
- Position(
- x = x,
- y = y,
- ),
- onClicked = {
- hideMenu()
- navigateToLockScreenSettings()
- },
- onDismissed = { hideMenu() },
- )
+ /** Updates application state to ask to show the menu. */
+ private fun showMenu() {
+ _isMenuVisible.value = true
+ scheduleAutomaticMenuHiding()
logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
}
+ private fun scheduleAutomaticMenuHiding() {
+ cancelAutomaticMenuHiding()
+ delayedHideMenuJob =
+ scope.launch {
+ delay(timeOutMs())
+ hideMenu()
+ }
+ }
+
/** Updates application state to ask to hide the menu. */
private fun hideMenu() {
- _menu.value = null
+ cancelAutomaticMenuHiding()
+ _isMenuVisible.value = false
}
- /** Opens the wallpaper picker screen after the device is unlocked by the user. */
- private fun navigateToLockScreenSettings() {
- logger.log(LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
- activityStarter.dismissKeyguardThenExecute(
- /* action= */ {
- appContext.startActivity(
- Intent(Intent.ACTION_SET_WALLPAPER).apply {
- flags = Intent.FLAG_ACTIVITY_NEW_TASK
- appContext
- .getString(R.string.config_wallpaperPickerPackage)
- .takeIf { it.isNotEmpty() }
- ?.let { packageName -> setPackage(packageName) }
- }
- )
- true
- },
- /* cancel= */ {},
- /* afterKeyguardGone= */ true,
- )
+ private fun cancelAutomaticMenuHiding() {
+ delayedHideMenuJob?.cancel()
+ delayedHideMenuJob = null
+ }
+
+ private fun timeOutMs(): Long {
+ return accessibilityManager
+ .getRecommendedTimeoutMillis(
+ DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS.toInt(),
+ AccessibilityManager.FLAG_CONTENT_ICONS or AccessibilityManager.FLAG_CONTENT_TEXT,
+ )
+ .toLong()
}
enum class LogEvents(
@@ -184,4 +219,8 @@ constructor(
override fun getId() = _id
}
+
+ companion object {
+ @VisibleForTesting const val DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS = 5000L
+ }
}
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 aabd212c1bd3..da0ada160518 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
@@ -78,6 +78,14 @@ constructor(
val primaryBouncerToGoneTransition: Flow<TransitionStep> =
repository.transition(PRIMARY_BOUNCER, GONE)
+ /** OFF->LOCKSCREEN transition information. */
+ val offToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(KeyguardState.OFF, LOCKSCREEN)
+
+ /** DOZING->LOCKSCREEN transition information. */
+ val dozingToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+
/**
* 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/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
new file mode 100644
index 000000000000..cad40aac00d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [KeyguardFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
+ override val authenticationStatus: Flow<AuthenticationStatus>
+ get() = emptyFlow()
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = emptyFlow()
+
+ override fun canFaceAuthRun(): Boolean = false
+
+ override fun isRunning(): Boolean = false
+
+ override fun isLockedOut(): Boolean = false
+
+ override fun isEnabled() = false
+
+ override fun registerListener(listener: FaceAuthenticationListener) {}
+
+ override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+ override fun onUdfpsSensorTouched() {}
+
+ override fun onAssistantTriggeredOnLockScreen() {}
+
+ override fun onDeviceLifted() {}
+
+ override fun onQsExpansionStared() {}
+
+ override fun onNotificationPanelClicked() {}
+
+ override fun onSwipeUpOnBouncer() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
new file mode 100644
index 000000000000..20ebb711c42d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.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.keyguard.domain.interactor
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic related face authentication being triggered for device entry from
+ * SystemUI Keyguard.
+ */
+@SysUISingleton
+class SystemUIKeyguardFaceAuthInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val repository: DeviceEntryFaceAuthRepository,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val featureFlags: FeatureFlags,
+ private val faceAuthenticationLogger: FaceAuthenticationLogger,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) : CoreStartable, KeyguardFaceAuthInteractor {
+
+ private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
+
+ override fun start() {
+ if (!isEnabled()) {
+ return
+ }
+ // This is required because fingerprint state required for the face auth repository is
+ // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric
+ // state which makes lazy injection not an option.
+ keyguardUpdateMonitor.setFaceAuthInteractor(this)
+ observeFaceAuthStateUpdates()
+ faceAuthenticationLogger.interactorStarted()
+ primaryBouncerInteractor.isShowing
+ .whenItFlipsToTrue()
+ .onEach {
+ faceAuthenticationLogger.bouncerVisibilityChanged()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+ fallbackToDetect = true
+ )
+ }
+ .launchIn(applicationScope)
+
+ alternateBouncerInteractor.isVisible
+ .whenItFlipsToTrue()
+ .onEach {
+ faceAuthenticationLogger.alternateBouncerVisibilityChanged()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+ fallbackToDetect = false
+ )
+ }
+ .launchIn(applicationScope)
+
+ merge(
+ keyguardTransitionInteractor.aodToLockscreenTransition,
+ keyguardTransitionInteractor.offToLockscreenTransition,
+ keyguardTransitionInteractor.dozingToLockscreenTransition
+ )
+ .filter { it.transitionState == TransitionState.STARTED }
+ .onEach {
+ faceAuthenticationLogger.lockscreenBecameVisible(it)
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
+ fallbackToDetect = true
+ )
+ }
+ .launchIn(applicationScope)
+ }
+
+ override fun onSwipeUpOnBouncer() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+ }
+
+ override fun onNotificationPanelClicked() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+ }
+
+ override fun onQsExpansionStared() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+ }
+
+ override fun onDeviceLifted() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+ }
+
+ override fun onAssistantTriggeredOnLockScreen() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+ }
+
+ override fun onUdfpsSensorTouched() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
+ }
+
+ override fun registerListener(listener: FaceAuthenticationListener) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterListener(listener: FaceAuthenticationListener) {
+ listeners.remove(listener)
+ }
+
+ override fun isLockedOut(): Boolean = repository.isLockedOut.value
+
+ override fun isRunning(): Boolean = repository.isAuthRunning.value
+
+ override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
+
+ override fun isEnabled(): Boolean {
+ return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
+ }
+
+ /** Provide the status of face authentication */
+ override val authenticationStatus = repository.authenticationStatus
+
+ /** Provide the status of face detection */
+ override val detectionStatus = repository.detectionStatus
+
+ private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
+ if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ applicationScope.launch {
+ faceAuthenticationLogger.authRequested(uiEvent)
+ repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+ }
+ } else {
+ faceAuthenticationLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ ignoredReason = "Skipping face auth request because feature flag is false"
+ )
+ }
+ }
+
+ private fun observeFaceAuthStateUpdates() {
+ authenticationStatus
+ .onEach { authStatusUpdate ->
+ listeners.forEach { it.onAuthenticationStatusChanged(authStatusUpdate) }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ detectionStatus
+ .onEach { detectionStatusUpdate ->
+ listeners.forEach { it.onDetectionStatusChanged(detectionStatusUpdate) }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
+ companion object {
+ const val TAG = "KeyguardFaceAuthInteractor"
+ }
+}
+
+// Extension method that filters a generic Boolean flow to one that emits
+// whenever there is flip from false -> true
+private fun Flow<Boolean>.whenItFlipsToTrue(): Flow<Boolean> {
+ return this.pairwise()
+ .filter { pair -> !pair.previousValue && pair.newValue }
+ .map { it.newValue }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
new file mode 100644
index 000000000000..568db2f543b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaVibrations.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.binder
+
+import android.os.VibrationEffect
+import kotlin.time.Duration.Companion.milliseconds
+
+object KeyguardBottomAreaVibrations {
+
+ val ShakeAnimationDuration = 300.milliseconds
+ const val ShakeAnimationCycles = 5f
+
+ private const val SmallVibrationScale = 0.3f
+ private const val BigVibrationScale = 0.6f
+
+ val Shake =
+ VibrationEffect.startComposition()
+ .apply {
+ val vibrationDelayMs =
+ (ShakeAnimationDuration.inWholeMilliseconds / ShakeAnimationCycles * 2).toInt()
+ val vibrationCount = ShakeAnimationCycles.toInt() * 2
+ repeat(vibrationCount) {
+ addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ SmallVibrationScale,
+ vibrationDelayMs,
+ )
+ }
+ }
+ .compose()
+
+ val Activated =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ BigVibrationScale,
+ 0,
+ )
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+ 0.1f,
+ 0,
+ )
+ .compose()
+
+ val Deactivated =
+ VibrationEffect.startComposition()
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_TICK,
+ BigVibrationScale,
+ 0,
+ )
+ .addPrimitive(
+ VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
+ 0.1f,
+ 0,
+ )
+ .compose()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index d63636c6fccc..68ac7e1c71e5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -17,41 +17,42 @@
package com.android.systemui.keyguard.ui.binder
import android.annotation.SuppressLint
+import android.content.Intent
+import android.graphics.Rect
import android.graphics.drawable.Animatable2
-import android.os.VibrationEffect
import android.util.Size
import android.util.TypedValue
-import android.view.MotionEvent
import android.view.View
-import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.ViewPropertyAnimator
import android.widget.ImageView
import android.widget.TextView
-import androidx.core.animation.CycleInterpolator
-import androidx.core.animation.ObjectAnimator
+import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.Interpolators
+import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.binder.IconViewBinder
+import com.android.systemui.common.ui.binder.TextViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
-import kotlin.math.pow
-import kotlin.math.sqrt
-import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@@ -91,15 +92,20 @@ object KeyguardBottomAreaViewBinder {
* icon
*/
fun shouldConstrainToTopOfLockIcon(): Boolean
+
+ /** Destroys this binding, releases resources, and cancels any coroutines. */
+ fun destroy()
}
/** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: ViewGroup,
viewModel: KeyguardBottomAreaViewModel,
falsingManager: FalsingManager?,
vibratorHelper: VibratorHelper?,
+ activityStarter: ActivityStarter?,
messageDisplayer: (Int) -> Unit,
): Binding {
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
@@ -110,137 +116,192 @@ object KeyguardBottomAreaViewBinder {
val indicationText: TextView = view.requireViewById(R.id.keyguard_indication_text)
val indicationTextBottom: TextView =
view.requireViewById(R.id.keyguard_indication_text_bottom)
+ val settingsMenu: LaunchableLinearLayout =
+ view.requireViewById(R.id.keyguard_settings_button)
view.clipChildren = false
view.clipToPadding = false
+ view.setOnTouchListener { _, event ->
+ if (settingsMenu.isVisible) {
+ val hitRect = Rect()
+ settingsMenu.getHitRect(hitRect)
+ if (!hitRect.contains(event.x.toInt(), event.y.toInt())) {
+ viewModel.onTouchedOutsideLockScreenSettingsMenu()
+ }
+ }
+
+ false
+ }
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
- viewModel.startButton.collect { buttonModel ->
- updateButton(
+ val disposableHandle =
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ launch {
+ viewModel.startButton.collect { buttonModel ->
+ updateButton(
+ view = startButton,
+ viewModel = buttonModel,
+ falsingManager = falsingManager,
+ messageDisplayer = messageDisplayer,
+ vibratorHelper = vibratorHelper,
+ )
+ }
+ }
+
+ launch {
+ viewModel.endButton.collect { buttonModel ->
+ updateButton(
+ view = endButton,
+ viewModel = buttonModel,
+ falsingManager = falsingManager,
+ messageDisplayer = messageDisplayer,
+ vibratorHelper = vibratorHelper,
+ )
+ }
+ }
+
+ launch {
+ viewModel.isOverlayContainerVisible.collect { isVisible ->
+ overlayContainer.visibility =
+ if (isVisible) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ }
+ }
+
+ launch {
+ viewModel.alpha.collect { alpha ->
+ view.importantForAccessibility =
+ if (alpha == 0f) {
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ } else {
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+
+ ambientIndicationArea?.alpha = alpha
+ indicationArea.alpha = alpha
+ }
+ }
+
+ launch {
+ updateButtonAlpha(
view = startButton,
- viewModel = buttonModel,
- falsingManager = falsingManager,
- messageDisplayer = messageDisplayer,
- vibratorHelper = vibratorHelper,
+ viewModel = viewModel.startButton,
+ alphaFlow = viewModel.alpha,
)
}
- }
- launch {
- viewModel.endButton.collect { buttonModel ->
- updateButton(
+ launch {
+ updateButtonAlpha(
view = endButton,
- viewModel = buttonModel,
- falsingManager = falsingManager,
- messageDisplayer = messageDisplayer,
- vibratorHelper = vibratorHelper,
+ viewModel = viewModel.endButton,
+ alphaFlow = viewModel.alpha,
)
}
- }
- launch {
- viewModel.isOverlayContainerVisible.collect { isVisible ->
- overlayContainer.visibility =
- if (isVisible) {
- View.VISIBLE
- } else {
- View.INVISIBLE
- }
+ launch {
+ viewModel.indicationAreaTranslationX.collect { translationX ->
+ indicationArea.translationX = translationX
+ ambientIndicationArea?.translationX = translationX
+ }
}
- }
- launch {
- viewModel.alpha.collect { alpha ->
- view.importantForAccessibility =
- if (alpha == 0f) {
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- } else {
- View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ launch {
+ combine(
+ viewModel.isIndicationAreaPadded,
+ configurationBasedDimensions.map { it.indicationAreaPaddingPx },
+ ) { isPadded, paddingIfPaddedPx ->
+ if (isPadded) {
+ paddingIfPaddedPx
+ } else {
+ 0
+ }
+ }
+ .collect { paddingPx ->
+ indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
}
-
- ambientIndicationArea?.alpha = alpha
- indicationArea.alpha = alpha
}
- }
-
- launch {
- updateButtonAlpha(
- view = startButton,
- viewModel = viewModel.startButton,
- alphaFlow = viewModel.alpha,
- )
- }
-
- launch {
- updateButtonAlpha(
- view = endButton,
- viewModel = viewModel.endButton,
- alphaFlow = viewModel.alpha,
- )
- }
- launch {
- viewModel.indicationAreaTranslationX.collect { translationX ->
- indicationArea.translationX = translationX
- ambientIndicationArea?.translationX = translationX
+ launch {
+ configurationBasedDimensions
+ .map { it.defaultBurnInPreventionYOffsetPx }
+ .flatMapLatest { defaultBurnInOffsetY ->
+ viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
+ }
+ .collect { translationY ->
+ indicationArea.translationY = translationY
+ ambientIndicationArea?.translationY = translationY
+ }
}
- }
- launch {
- combine(
- viewModel.isIndicationAreaPadded,
- configurationBasedDimensions.map { it.indicationAreaPaddingPx },
- ) { isPadded, paddingIfPaddedPx ->
- if (isPadded) {
- paddingIfPaddedPx
- } else {
- 0
+ launch {
+ configurationBasedDimensions.collect { dimensions ->
+ indicationText.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+ indicationTextBottom.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ dimensions.indicationTextSizePx.toFloat(),
+ )
+
+ startButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
+ }
+ endButton.updateLayoutParams<ViewGroup.LayoutParams> {
+ width = dimensions.buttonSizePx.width
+ height = dimensions.buttonSizePx.height
}
}
- .collect { paddingPx ->
- indicationArea.setPadding(paddingPx, 0, paddingPx, 0)
- }
- }
+ }
- launch {
- configurationBasedDimensions
- .map { it.defaultBurnInPreventionYOffsetPx }
- .flatMapLatest { defaultBurnInOffsetY ->
- viewModel.indicationAreaTranslationY(defaultBurnInOffsetY)
- }
- .collect { translationY ->
- indicationArea.translationY = translationY
- ambientIndicationArea?.translationY = translationY
+ launch {
+ viewModel.settingsMenuViewModel.isVisible.distinctUntilChanged().collect {
+ isVisible ->
+ settingsMenu.animateVisibility(visible = isVisible)
+ if (isVisible) {
+ vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Activated)
+ settingsMenu.setOnTouchListener(
+ KeyguardSettingsButtonOnTouchListener(
+ view = settingsMenu,
+ viewModel = viewModel.settingsMenuViewModel,
+ )
+ )
+ IconViewBinder.bind(
+ icon = viewModel.settingsMenuViewModel.icon,
+ view = settingsMenu.requireViewById(R.id.icon),
+ )
+ TextViewBinder.bind(
+ view = settingsMenu.requireViewById(R.id.text),
+ viewModel = viewModel.settingsMenuViewModel.text,
+ )
+ }
}
- }
-
- launch {
- configurationBasedDimensions.collect { dimensions ->
- indicationText.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- dimensions.indicationTextSizePx.toFloat(),
- )
- indicationTextBottom.setTextSize(
- TypedValue.COMPLEX_UNIT_PX,
- dimensions.indicationTextSizePx.toFloat(),
- )
+ }
- startButton.updateLayoutParams<ViewGroup.LayoutParams> {
- width = dimensions.buttonSizePx.width
- height = dimensions.buttonSizePx.height
- }
- endButton.updateLayoutParams<ViewGroup.LayoutParams> {
- width = dimensions.buttonSizePx.width
- height = dimensions.buttonSizePx.height
+ // activityStarter will only be null when rendering the preview that
+ // shows up in the Wallpaper Picker app. If we do that, then the
+ // settings menu should never be visible.
+ if (activityStarter != null) {
+ launch {
+ viewModel.settingsMenuViewModel.shouldOpenSettings
+ .filter { it }
+ .collect {
+ navigateToLockScreenSettings(
+ activityStarter = activityStarter,
+ view = settingsMenu,
+ )
+ viewModel.settingsMenuViewModel.onSettingsShown()
+ }
}
}
}
}
- }
return object : Binding {
override fun getIndicationAreaAnimators(): List<ViewPropertyAnimator> {
@@ -253,6 +314,10 @@ object KeyguardBottomAreaViewBinder {
override fun shouldConstrainToTopOfLockIcon(): Boolean =
viewModel.shouldConstrainToTopOfLockIcon()
+
+ override fun destroy() {
+ disposableHandle.dispose()
+ }
}
}
@@ -265,7 +330,7 @@ object KeyguardBottomAreaViewBinder {
vibratorHelper: VibratorHelper?,
) {
if (!viewModel.isVisible) {
- view.isVisible = false
+ view.isInvisible = true
return
}
@@ -342,7 +407,7 @@ object KeyguardBottomAreaViewBinder {
if (viewModel.isClickable) {
if (viewModel.useLongPress) {
view.setOnTouchListener(
- OnTouchListener(
+ KeyguardQuickAffordanceOnTouchListener(
view,
viewModel,
messageDisplayer,
@@ -372,187 +437,21 @@ object KeyguardBottomAreaViewBinder {
.collect { view.alpha = it }
}
- private class OnTouchListener(
- private val view: View,
- private val viewModel: KeyguardQuickAffordanceViewModel,
- private val messageDisplayer: (Int) -> Unit,
- private val vibratorHelper: VibratorHelper?,
- private val falsingManager: FalsingManager?,
- ) : View.OnTouchListener {
-
- private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
- private var longPressAnimator: ViewPropertyAnimator? = null
-
- @SuppressLint("ClickableViewAccessibility")
- override fun onTouch(v: View?, event: MotionEvent?): Boolean {
- return when (event?.actionMasked) {
- MotionEvent.ACTION_DOWN ->
- if (viewModel.configKey != null) {
- if (isUsingAccurateTool(event)) {
- // For accurate tool types (stylus, mouse, etc.), we don't require a
- // long-press.
- } else {
- // When not using a stylus, we require a long-press to activate the
- // quick affordance, mostly to do "falsing" (e.g. protect from false
- // clicks in the pocket/bag).
- longPressAnimator =
- view
- .animate()
- .scaleX(PRESSED_SCALE)
- .scaleY(PRESSED_SCALE)
- .setDuration(longPressDurationMs)
- .withEndAction {
- if (
- falsingManager
- ?.isFalseLongTap(
- FalsingManager.MODERATE_PENALTY
- ) == false
- ) {
- dispatchClick(viewModel.configKey)
- }
- cancel()
- }
- }
- true
- } else {
- false
- }
- MotionEvent.ACTION_MOVE -> {
- if (!isUsingAccurateTool(event)) {
- // Moving too far while performing a long-press gesture cancels that
- // gesture.
- val distanceMoved = distanceMoved(event)
- if (distanceMoved > ViewConfiguration.getTouchSlop()) {
- cancel()
- }
- }
- true
- }
- MotionEvent.ACTION_UP -> {
- if (isUsingAccurateTool(event)) {
- // When using an accurate tool type (stylus, mouse, etc.), we don't require
- // a long-press gesture to activate the quick affordance. Therefore, lifting
- // the pointer performs a click.
- if (
- viewModel.configKey != null &&
- distanceMoved(event) <= ViewConfiguration.getTouchSlop() &&
- falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false
- ) {
- dispatchClick(viewModel.configKey)
- }
- } else {
- // When not using a stylus, lifting the finger/pointer will actually cancel
- // the long-press gesture. Calling cancel after the quick affordance was
- // already long-press activated is a no-op, so it's safe to call from here.
- cancel(
- onAnimationEnd =
- if (event.eventTime - event.downTime < longPressDurationMs) {
- Runnable {
- messageDisplayer.invoke(
- R.string.keyguard_affordance_press_too_short
- )
- val amplitude =
- view.context.resources
- .getDimensionPixelSize(
- R.dimen.keyguard_affordance_shake_amplitude
- )
- .toFloat()
- val shakeAnimator =
- ObjectAnimator.ofFloat(
- view,
- "translationX",
- -amplitude / 2,
- amplitude / 2,
- )
- shakeAnimator.duration =
- ShakeAnimationDuration.inWholeMilliseconds
- shakeAnimator.interpolator =
- CycleInterpolator(ShakeAnimationCycles)
- shakeAnimator.start()
-
- vibratorHelper?.vibrate(Vibrations.Shake)
- }
- } else {
- null
- }
- )
- }
- true
- }
- MotionEvent.ACTION_CANCEL -> {
- cancel()
- true
- }
- else -> false
- }
- }
-
- private fun dispatchClick(
- configKey: String,
- ) {
- view.setOnClickListener {
- vibratorHelper?.vibrate(
- if (viewModel.isActivated) {
- Vibrations.Activated
- } else {
- Vibrations.Deactivated
- }
- )
- viewModel.onClicked(
- KeyguardQuickAffordanceViewModel.OnClickedParameters(
- configKey = configKey,
- expandable = Expandable.fromView(view),
- slotId = viewModel.slotId,
- )
- )
- }
- view.performClick()
- view.setOnClickListener(null)
- }
-
- private fun cancel(onAnimationEnd: Runnable? = null) {
- longPressAnimator?.cancel()
- longPressAnimator = null
- view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
- }
-
- companion object {
- private const val PRESSED_SCALE = 1.5f
-
- /**
- * Returns `true` if the tool type at the given pointer index is an accurate tool (like
- * stylus or mouse), which means we can trust it to not be a false click; `false`
- * otherwise.
- */
- private fun isUsingAccurateTool(
- event: MotionEvent,
- pointerIndex: Int = 0,
- ): Boolean {
- return when (event.getToolType(pointerIndex)) {
- MotionEvent.TOOL_TYPE_STYLUS -> true
- MotionEvent.TOOL_TYPE_MOUSE -> true
- else -> false
+ private fun View.animateVisibility(visible: Boolean) {
+ animate()
+ .withStartAction {
+ if (visible) {
+ alpha = 0f
+ isVisible = true
}
}
-
- /**
- * Returns the amount of distance the pointer moved since the historical record at the
- * [since] index.
- */
- private fun distanceMoved(
- event: MotionEvent,
- since: Int = 0,
- ): Float {
- return if (event.historySize > 0) {
- sqrt(
- (event.y - event.getHistoricalY(since)).pow(2) +
- (event.x - event.getHistoricalX(since)).pow(2)
- )
- } else {
- 0f
+ .alpha(if (visible) 1f else 0f)
+ .withEndAction {
+ if (!visible) {
+ isVisible = false
}
}
- }
+ .start()
}
private class OnClickListener(
@@ -594,64 +493,28 @@ object KeyguardBottomAreaViewBinder {
)
}
+ /** Opens the wallpaper picker screen after the device is unlocked by the user. */
+ private fun navigateToLockScreenSettings(
+ activityStarter: ActivityStarter,
+ view: View,
+ ) {
+ activityStarter.startActivity(
+ Intent(Intent.ACTION_SET_WALLPAPER).apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ view.context
+ .getString(R.string.config_wallpaperPickerPackage)
+ .takeIf { it.isNotEmpty() }
+ ?.let { packageName -> setPackage(packageName) }
+ },
+ /* dismissShade= */ true,
+ ActivityLaunchAnimator.Controller.fromView(view),
+ )
+ }
+
private data class ConfigurationBasedDimensions(
val defaultBurnInPreventionYOffsetPx: Int,
val indicationAreaPaddingPx: Int,
val indicationTextSizePx: Int,
val buttonSizePx: Size,
)
-
- private val ShakeAnimationDuration = 300.milliseconds
- private val ShakeAnimationCycles = 5f
-
- object Vibrations {
-
- private const val SmallVibrationScale = 0.3f
- private const val BigVibrationScale = 0.6f
-
- val Shake =
- VibrationEffect.startComposition()
- .apply {
- val vibrationDelayMs =
- (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
- .toInt()
- val vibrationCount = ShakeAnimationCycles.toInt() * 2
- repeat(vibrationCount) {
- addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_TICK,
- SmallVibrationScale,
- vibrationDelayMs,
- )
- }
- }
- .compose()
-
- val Activated =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_TICK,
- BigVibrationScale,
- 0,
- )
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
- 0.1f,
- 0,
- )
- .compose()
-
- val Deactivated =
- VibrationEffect.startComposition()
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_TICK,
- BigVibrationScale,
- 0,
- )
- .addPrimitive(
- VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
- 0.1f,
- 0,
- )
- .compose()
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
deleted file mode 100644
index d85682b3bab8..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressPopupViewBinder.kt
+++ /dev/null
@@ -1,88 +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.keyguard.ui.binder
-
-import android.annotation.SuppressLint
-import android.view.Gravity
-import android.view.LayoutInflater
-import android.view.View
-import android.view.WindowManager
-import android.widget.PopupWindow
-import com.android.systemui.R
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.common.ui.binder.TextViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsPopupMenuViewModel
-
-object KeyguardLongPressPopupViewBinder {
- @SuppressLint("InflateParams") // We don't care that the parent is null.
- fun createAndShow(
- container: View,
- viewModel: KeyguardSettingsPopupMenuViewModel,
- onDismissed: () -> Unit,
- ): () -> Unit {
- val contentView: View =
- LayoutInflater.from(container.context)
- .inflate(
- R.layout.keyguard_settings_popup_menu,
- null,
- )
-
- contentView.setOnClickListener { viewModel.onClicked() }
- IconViewBinder.bind(
- icon = viewModel.icon,
- view = contentView.requireViewById(R.id.icon),
- )
- TextViewBinder.bind(
- view = contentView.requireViewById(R.id.text),
- viewModel = viewModel.text,
- )
-
- val popupWindow =
- PopupWindow(container.context).apply {
- windowLayoutType = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG
- setBackgroundDrawable(null)
- animationStyle = com.android.internal.R.style.Animation_Dialog
- isOutsideTouchable = true
- isFocusable = true
- setContentView(contentView)
- setOnDismissListener { onDismissed() }
- contentView.measure(
- View.MeasureSpec.makeMeasureSpec(
- 0,
- View.MeasureSpec.UNSPECIFIED,
- ),
- View.MeasureSpec.makeMeasureSpec(
- 0,
- View.MeasureSpec.UNSPECIFIED,
- ),
- )
- showAtLocation(
- container,
- Gravity.NO_GRAVITY,
- viewModel.position.x - contentView.measuredWidth / 2,
- viewModel.position.y -
- contentView.measuredHeight -
- container.context.resources.getDimensionPixelSize(
- R.dimen.keyguard_long_press_settings_popup_vertical_offset
- ),
- )
- }
-
- return { popupWindow.dismiss() }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index 86717537efd3..9cc503c07955 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -50,10 +50,7 @@ object KeyguardLongPressViewBinder {
return
}
- viewModel.onLongPress(
- x = x,
- y = y,
- )
+ viewModel.onLongPress()
}
override fun onSingleTapDetected(view: View) {
@@ -72,23 +69,6 @@ object KeyguardLongPressViewBinder {
view.setLongPressHandlingEnabled(isEnabled)
}
}
-
- launch {
- var dismissMenu: (() -> Unit)? = null
-
- viewModel.menu.collect { menuOrNull ->
- if (menuOrNull != null) {
- dismissMenu =
- KeyguardLongPressPopupViewBinder.createAndShow(
- container = view,
- viewModel = menuOrNull,
- onDismissed = menuOrNull.onDismissed,
- )
- } else {
- dismissMenu?.invoke()
- }
- }
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
new file mode 100644
index 000000000000..5745d6ae077e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceOnTouchListener.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.binder
+
+import android.annotation.SuppressLint
+import android.graphics.PointF
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.ViewPropertyAnimator
+import androidx.core.animation.CycleInterpolator
+import androidx.core.animation.ObjectAnimator
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.ui.view.rawDistanceFrom
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+
+class KeyguardQuickAffordanceOnTouchListener(
+ private val view: View,
+ private val viewModel: KeyguardQuickAffordanceViewModel,
+ private val messageDisplayer: (Int) -> Unit,
+ private val vibratorHelper: VibratorHelper?,
+ private val falsingManager: FalsingManager?,
+) : View.OnTouchListener {
+
+ private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
+ private var longPressAnimator: ViewPropertyAnimator? = null
+ private val downDisplayCoords: PointF by lazy { PointF() }
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ return when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN ->
+ if (viewModel.configKey != null) {
+ downDisplayCoords.set(event.rawX, event.rawY)
+ if (isUsingAccurateTool(event)) {
+ // For accurate tool types (stylus, mouse, etc.), we don't require a
+ // long-press.
+ } else {
+ // When not using a stylus, we require a long-press to activate the
+ // quick affordance, mostly to do "falsing" (e.g. protect from false
+ // clicks in the pocket/bag).
+ longPressAnimator =
+ view
+ .animate()
+ .scaleX(PRESSED_SCALE)
+ .scaleY(PRESSED_SCALE)
+ .setDuration(longPressDurationMs)
+ .withEndAction {
+ if (
+ falsingManager?.isFalseLongTap(
+ FalsingManager.MODERATE_PENALTY
+ ) == false
+ ) {
+ dispatchClick(viewModel.configKey)
+ }
+ cancel()
+ }
+ }
+ true
+ } else {
+ false
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (!isUsingAccurateTool(event)) {
+ // Moving too far while performing a long-press gesture cancels that
+ // gesture.
+ if (
+ event
+ .rawDistanceFrom(
+ downDisplayCoords.x,
+ downDisplayCoords.y,
+ ) > ViewConfiguration.getTouchSlop()
+ ) {
+ cancel()
+ }
+ }
+ true
+ }
+ MotionEvent.ACTION_UP -> {
+ if (isUsingAccurateTool(event)) {
+ // When using an accurate tool type (stylus, mouse, etc.), we don't require
+ // a long-press gesture to activate the quick affordance. Therefore, lifting
+ // the pointer performs a click.
+ if (
+ viewModel.configKey != null &&
+ event.rawDistanceFrom(downDisplayCoords.x, downDisplayCoords.y) <=
+ ViewConfiguration.getTouchSlop() &&
+ falsingManager?.isFalseTap(FalsingManager.NO_PENALTY) == false
+ ) {
+ dispatchClick(viewModel.configKey)
+ }
+ } else {
+ // When not using a stylus, lifting the finger/pointer will actually cancel
+ // the long-press gesture. Calling cancel after the quick affordance was
+ // already long-press activated is a no-op, so it's safe to call from here.
+ cancel(
+ onAnimationEnd =
+ if (event.eventTime - event.downTime < longPressDurationMs) {
+ Runnable {
+ messageDisplayer.invoke(
+ R.string.keyguard_affordance_press_too_short
+ )
+ val amplitude =
+ view.context.resources
+ .getDimensionPixelSize(
+ R.dimen.keyguard_affordance_shake_amplitude
+ )
+ .toFloat()
+ val shakeAnimator =
+ ObjectAnimator.ofFloat(
+ view,
+ "translationX",
+ -amplitude / 2,
+ amplitude / 2,
+ )
+ shakeAnimator.duration =
+ KeyguardBottomAreaVibrations.ShakeAnimationDuration
+ .inWholeMilliseconds
+ shakeAnimator.interpolator =
+ CycleInterpolator(
+ KeyguardBottomAreaVibrations.ShakeAnimationCycles
+ )
+ shakeAnimator.start()
+
+ vibratorHelper?.vibrate(KeyguardBottomAreaVibrations.Shake)
+ }
+ } else {
+ null
+ }
+ )
+ }
+ true
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ cancel()
+ true
+ }
+ else -> false
+ }
+ }
+
+ private fun dispatchClick(
+ configKey: String,
+ ) {
+ view.setOnClickListener {
+ vibratorHelper?.vibrate(
+ if (viewModel.isActivated) {
+ KeyguardBottomAreaVibrations.Activated
+ } else {
+ KeyguardBottomAreaVibrations.Deactivated
+ }
+ )
+ viewModel.onClicked(
+ KeyguardQuickAffordanceViewModel.OnClickedParameters(
+ configKey = configKey,
+ expandable = Expandable.fromView(view),
+ slotId = viewModel.slotId,
+ )
+ )
+ }
+ view.performClick()
+ view.setOnClickListener(null)
+ }
+
+ private fun cancel(onAnimationEnd: Runnable? = null) {
+ longPressAnimator?.cancel()
+ longPressAnimator = null
+ view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
+ }
+
+ companion object {
+ private const val PRESSED_SCALE = 1.5f
+
+ /**
+ * Returns `true` if the tool type at the given pointer index is an accurate tool (like
+ * stylus or mouse), which means we can trust it to not be a false click; `false` otherwise.
+ */
+ private fun isUsingAccurateTool(
+ event: MotionEvent,
+ pointerIndex: Int = 0,
+ ): Boolean {
+ return when (event.getToolType(pointerIndex)) {
+ MotionEvent.TOOL_TYPE_STYLUS -> true
+ MotionEvent.TOOL_TYPE_MOUSE -> true
+ else -> false
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.kt
new file mode 100644
index 000000000000..c54203c97013
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsButtonOnTouchListener.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.keyguard.ui.binder
+
+import android.graphics.PointF
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewConfiguration
+import com.android.systemui.animation.view.LaunchableLinearLayout
+import com.android.systemui.common.ui.view.rawDistanceFrom
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+
+class KeyguardSettingsButtonOnTouchListener(
+ private val view: LaunchableLinearLayout,
+ private val viewModel: KeyguardSettingsMenuViewModel,
+) : View.OnTouchListener {
+
+ private val downPositionDisplayCoords = PointF()
+
+ override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
+ when (motionEvent.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ view.isPressed = true
+ downPositionDisplayCoords.set(motionEvent.rawX, motionEvent.rawY)
+ viewModel.onTouchGestureStarted()
+ }
+ MotionEvent.ACTION_UP -> {
+ view.isPressed = false
+ val distanceMoved =
+ motionEvent
+ .rawDistanceFrom(downPositionDisplayCoords.x, downPositionDisplayCoords.y)
+ val isClick = distanceMoved < ViewConfiguration.getTouchSlop()
+ viewModel.onTouchGestureEnded(isClick)
+ if (isClick) {
+ view.performClick()
+ }
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ view.isPressed = false
+ viewModel.onTouchGestureEnded(/* isClick= */ false)
+ }
+ }
+
+ return true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index a8e346477690..2d83be95183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -44,6 +44,8 @@ constructor(
private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper,
+ private val longPressViewModel: KeyguardLongPressViewModel,
+ val settingsMenuViewModel: KeyguardSettingsMenuViewModel,
) {
data class PreviewMode(
val isInPreviewMode: Boolean = false,
@@ -161,6 +163,14 @@ constructor(
selectedPreviewSlotId.value = slotId
}
+ /**
+ * Notifies that some input gesture has started somewhere in the bottom area that's outside of
+ * the lock screen settings menu item pop-up.
+ */
+ fun onTouchedOutsideLockScreenSettingsMenu() {
+ longPressViewModel.onTouchedOutside()
+ }
+
private fun button(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> {
@@ -225,9 +235,10 @@ constructor(
isDimmed = isDimmed,
slotId = slotId,
)
- is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel(
- slotId = slotId,
- )
+ is KeyguardQuickAffordanceModel.Hidden ->
+ KeyguardQuickAffordanceViewModel(
+ slotId = slotId,
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
index d896390fd471..c73931a12455 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
@@ -17,15 +17,13 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.R
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.map
/** Models UI state to support the lock screen long-press feature. */
+@SysUISingleton
class KeyguardLongPressViewModel
@Inject
constructor(
@@ -35,35 +33,16 @@ constructor(
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: Flow<Boolean> = interactor.isLongPressHandlingEnabled
- /** View-model for a menu that should be shown; `null` when no menu should be shown. */
- val menu: Flow<KeyguardSettingsPopupMenuViewModel?> =
- interactor.menu.map { model ->
- model?.let {
- KeyguardSettingsPopupMenuViewModel(
- icon =
- Icon.Resource(
- res = R.drawable.ic_settings,
- contentDescription = null,
- ),
- text =
- Text.Resource(
- res = R.string.lock_screen_settings,
- ),
- position = model.position,
- onClicked = model.onClicked,
- onDismissed = model.onDismissed,
- )
- }
- }
-
/** Notifies that the user has long-pressed on the lock screen. */
- fun onLongPress(
- x: Int,
- y: Int,
- ) {
- interactor.onLongPress(
- x = x,
- y = y,
- )
+ fun onLongPress() {
+ interactor.onLongPress()
+ }
+
+ /**
+ * Notifies that some input gesture has started somewhere outside of the lock screen settings
+ * menu item pop-up.
+ */
+ fun onTouchedOutside() {
+ interactor.onTouchedOutside()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt
new file mode 100644
index 000000000000..c36da9da58a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.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.keyguard.ui.viewmodel
+
+import com.android.systemui.R
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+
+/** Models the UI state of a keyguard settings popup menu. */
+class KeyguardSettingsMenuViewModel
+@Inject
+constructor(
+ private val interactor: KeyguardLongPressInteractor,
+) {
+ val isVisible: Flow<Boolean> = interactor.isMenuVisible
+ val shouldOpenSettings: Flow<Boolean> = interactor.shouldOpenSettings
+
+ val icon: Icon =
+ Icon.Resource(
+ res = R.drawable.ic_palette,
+ contentDescription = null,
+ )
+
+ val text: Text =
+ Text.Resource(
+ res = R.string.lock_screen_settings,
+ )
+
+ fun onTouchGestureStarted() {
+ interactor.onMenuTouchGestureStarted()
+ }
+
+ fun onTouchGestureEnded(isClick: Boolean) {
+ interactor.onMenuTouchGestureEnded(
+ isClick = isClick,
+ )
+ }
+
+ fun onSettingsShown() {
+ interactor.onSettingsShown()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
deleted file mode 100644
index 0571b05b4751..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsPopupMenuViewModel.kt
+++ /dev/null
@@ -1,34 +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.keyguard.ui.viewmodel
-
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Position
-import com.android.systemui.common.shared.model.Text
-
-/** Models the UI state of a keyguard settings popup menu. */
-data class KeyguardSettingsPopupMenuViewModel(
- val icon: Icon,
- val text: Text,
- /** Where the menu should be anchored, roughly in screen space. */
- val position: Position,
- /** Callback to invoke when the menu gets clicked by the user. */
- val onClicked: () -> Unit,
- /** Callback to invoke when the menu gets dismissed by the user. */
- val onDismissed: () -> Unit,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index f7355d5c11e2..7f6e4a903d76 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,6 +4,7 @@ import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -27,15 +28,15 @@ class FaceAuthenticationLogger
constructor(
@FaceAuthLog private val logBuffer: LogBuffer,
) {
- fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) {
+ fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
logBuffer.log(
TAG,
DEBUG,
- { str1 = uiEvent.reason },
{
- "Ignoring trigger because face auth is currently running. " +
- "Trigger reason: $str1"
- }
+ str1 = uiEvent.reason
+ str2 = ignoredReason
+ },
+ { "Ignoring trigger because $str2, Trigger reason: $str1" }
)
}
@@ -135,15 +136,6 @@ constructor(
logBuffer.log(TAG, DEBUG, "Face authentication failed")
}
- fun authenticationAcquired(acquireInfo: Int) {
- logBuffer.log(
- TAG,
- DEBUG,
- { int1 = acquireInfo },
- { "Face acquired during face authentication: acquireInfo: $int1 " }
- )
- }
-
fun authenticationError(
errorCode: Int,
errString: CharSequence?,
@@ -217,4 +209,34 @@ constructor(
fun cancellingFaceAuth() {
logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
}
+
+ fun interactorStarted() {
+ logBuffer.log(TAG, DEBUG, "KeyguardFaceAuthInteractor started")
+ }
+
+ fun bouncerVisibilityChanged() {
+ logBuffer.log(TAG, DEBUG, "Triggering face auth because primary bouncer is visible")
+ }
+
+ fun alternateBouncerVisibilityChanged() {
+ logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible")
+ }
+
+ fun lockscreenBecameVisible(transitionStep: TransitionStep?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = "$transitionStep" },
+ { "Triggering face auth because lockscreen became visible due to transition: $str1" }
+ )
+ }
+
+ fun authRequested(uiEvent: FaceAuthUiEvent) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = "$uiEvent" },
+ { "Requesting face auth for trigger: $str1" }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 9d2d3553db6d..faaa205b15c2 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -18,7 +18,7 @@ package com.android.systemui.log.table
import android.os.Trace
import com.android.systemui.Dumpable
-import com.android.systemui.plugins.util.RingBuffer
+import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import java.text.SimpleDateFormat
diff --git a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
index 4f5bbb797516..81de607f24f8 100644
--- a/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/logcat/LogAccessDialogActivity.java
@@ -239,7 +239,7 @@ public class LogAccessDialogActivity extends Activity implements
if (view.getId() == R.id.log_access_dialog_allow_button) {
mCallback.approveAccessForClient(mUid, mPackageName);
finish();
- } else if (view.getId() == R.id.log_access_dialog_allow_button) {
+ } else if (view.getId() == R.id.log_access_dialog_deny_button) {
declineLogAccess();
finish();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index c4e76b203f19..ccddd1d359b7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -60,7 +60,7 @@ import javax.inject.Inject;
import dagger.Lazy;
public class MediaProjectionPermissionActivity extends Activity
- implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ implements DialogInterface.OnClickListener {
private static final String TAG = "MediaProjectionPermissionActivity";
private static final float MAX_APP_NAME_SIZE_PX = 500f;
private static final String ELLIPSIS = "\u2026";
@@ -215,7 +215,8 @@ public class MediaProjectionPermissionActivity extends Activity
SystemUIDialog.applyFlags(dialog);
SystemUIDialog.setDialogSize(dialog);
- dialog.setOnCancelListener(this);
+ dialog.setOnCancelListener(this::onDialogDismissedOrCancelled);
+ dialog.setOnDismissListener(this::onDialogDismissedOrCancelled);
dialog.create();
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
@@ -283,9 +284,10 @@ public class MediaProjectionPermissionActivity extends Activity
return intent;
}
- @Override
- public void onCancel(DialogInterface dialog) {
- finish();
+ private void onDialogDismissedOrCancelled(DialogInterface dialogInterface) {
+ if (!isFinishing()) {
+ finish();
+ }
}
private boolean isPartialScreenSharingEnabled() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 4cc041074a8d..fa4211487d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -49,6 +49,7 @@ import android.service.notification.StatusBarNotification
import android.support.v4.media.MediaMetadataCompat
import android.text.TextUtils
import android.util.Log
+import android.util.Pair as APair
import androidx.media.utils.MediaConstants
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -217,6 +218,13 @@ class MediaDataManager(
private var smartspaceSession: SmartspaceSession? = null
private var allowMediaRecommendations = allowMediaRecommendations(context)
+ private val artworkWidth =
+ context.resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize
+ )
+ private val artworkHeight =
+ context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded)
+
/** Check whether this notification is an RCN */
private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean {
return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE)
@@ -1250,9 +1258,21 @@ class MediaDataManager(
return null
}
- val source = ImageDecoder.createSource(context.getContentResolver(), uri)
+ val source = ImageDecoder.createSource(context.contentResolver, uri)
return try {
- ImageDecoder.decodeBitmap(source) { decoder, _, _ ->
+ ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
+ val width = info.size.width
+ val height = info.size.height
+ val scale =
+ MediaDataUtils.getScaleFactor(
+ APair(width, height),
+ APair(artworkWidth, artworkHeight)
+ )
+
+ // Downscale if needed
+ if (scale != 0f && scale < 1) {
+ decoder.setTargetSize((scale * width).toInt(), (scale * height).toInt())
+ }
decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
}
} catch (e: IOException) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index cb1f12cf412f..40027a1d8f2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -56,6 +56,7 @@ import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Pair;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
@@ -122,6 +123,11 @@ import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
+import dagger.Lazy;
+
+import kotlin.Triple;
+import kotlin.Unit;
+
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
@@ -129,10 +135,6 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-import kotlin.Triple;
-import kotlin.Unit;
-
/**
* A view controller used for Media Playback.
*/
@@ -1000,18 +1002,9 @@ public class MediaControlPanel {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
- if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) {
- return;
- }
-
- float scale;
- if ((width / (float) height) > (targetWidth / (float) targetHeight)) {
- // Drawable is wider than target view, scale to match height
- scale = targetHeight / (float) height;
- } else {
- // Drawable is taller than target view, scale to match width
- scale = targetWidth / (float) width;
- }
+ float scale = MediaDataUtils.getScaleFactor(new Pair(width, height),
+ new Pair(targetWidth, targetHeight));
+ if (scale == 0) return;
transitionDrawable.setLayerSize(layer, (int) (scale * width), (int) (scale * height));
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
index e95106e0987a..0239d367e52d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java
@@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.text.TextUtils;
+import android.util.Pair;
import androidx.core.math.MathUtils;
import androidx.media.utils.MediaConstants;
@@ -87,4 +88,32 @@ public class MediaDataUtils {
}
return null;
}
+
+ /**
+ * Calculate a scale factor that will allow the input to fill the target size.
+ *
+ * @param input width, height of the input view
+ * @param target width, height of the target view
+ * @return the scale factor; 0 if any given dimension is 0
+ */
+ public static float getScaleFactor(Pair<Integer, Integer> input,
+ Pair<Integer, Integer> target) {
+ float width = (float) input.first;
+ float height = (float) input.second;
+
+ float targetWidth = (float) target.first;
+ float targetHeight = (float) target.second;
+
+ if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) {
+ return 0f;
+ }
+
+ if ((width / height) > (targetWidth / targetHeight)) {
+ // Input is wider than target view, scale to match height
+ return targetHeight / height;
+ } else {
+ // Input is taller than target view, scale to match width
+ return targetWidth / width;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index dbc2a5ec4e0a..b29b5887545a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,7 +19,7 @@ package com.android.systemui.media.taptotransfer.common
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
@@ -108,7 +108,7 @@ class MediaTttUtils {
data class IconInfo(
val contentDescription: ContentDescription,
val icon: MediaTttIcon,
- @ColorRes val tint: Int?,
+ @AttrRes val tint: Int?,
/**
* True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 26b0e8dd9895..b9ef916eebdf 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -55,6 +55,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
+import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -173,7 +174,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
};
-
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final Context mContext;
private final UserTracker mUserTracker;
private final OverviewProxyService mOverviewProxyService;
@@ -901,6 +902,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
}
+ // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
+ // ACTION_DOWN, in that case we should just reuse the old instance.
+ mVelocityTracker.clear();
+
// Verify if this is in within the touch region and we aren't in immersive mode, and
// either the bouncer is showing or the notification panel is hidden
mInputEventReceiver.setBatchingEnabled(false);
@@ -1027,11 +1032,30 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private void dispatchToBackAnimation(MotionEvent event) {
if (mBackAnimation != null) {
+ mVelocityTracker.addMovement(event);
+
+ final float velocityX;
+ final float velocityY;
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ // Compute the current velocity is expensive (see computeCurrentVelocity), so we
+ // are only doing it when the user completes the gesture.
+ int unitPixelPerSecond = 1000;
+ int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity();
+ mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity);
+ velocityX = mVelocityTracker.getXVelocity();
+ velocityY = mVelocityTracker.getYVelocity();
+ } else {
+ velocityX = Float.NaN;
+ velocityY = Float.NaN;
+ }
+
mBackAnimation.onBackMotion(
- event.getX(),
- event.getY(),
- event.getActionMasked(),
- mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
+ /* touchX = */ event.getX(),
+ /* touchY = */ event.getY(),
+ /* velocityX = */ velocityX,
+ /* velocityY = */ velocityY,
+ /* keyAction = */ event.getActionMasked(),
+ /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
index 44855fb7c8cc..0f38d32e0b64 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -18,9 +18,11 @@ package com.android.systemui.notetask.shortcut
import android.content.Context
import android.content.Intent
-import android.content.pm.UserInfo
+import android.os.Build
import android.os.Bundle
+import android.os.UserHandle
import android.os.UserManager
+import android.util.Log
import androidx.activity.ComponentActivity
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
@@ -63,9 +65,13 @@ constructor(
// | Bubble#showOrHideAppBubble | <--------------
// | (with WP user ID) |
// ----------------------------
- val mainUser: UserInfo? = userTracker.userProfiles.firstOrNull { it.isMain }
- if (userManager.isManagedProfile && mainUser != null) {
- controller.startNoteTaskProxyActivityForUser(mainUser.userHandle)
+ val mainUser: UserHandle? = userManager.mainUser
+ if (userManager.isManagedProfile) {
+ if (mainUser == null) {
+ logDebug { "Can't find the main user. Skipping the notes app launch." }
+ } else {
+ controller.startNoteTaskProxyActivityForUser(mainUser)
+ }
} else {
controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
}
@@ -83,3 +89,8 @@ constructor(
}
}
}
+
+/** [Log.println] a [Log.DEBUG] message, only when [Build.IS_DEBUGGABLE]. */
+private inline fun Any.logDebug(message: () -> String) {
+ if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index c2c1306d2a32..a765702a95b2 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -18,6 +18,10 @@ package com.android.systemui.power;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason;
+
import android.app.Dialog;
import android.app.KeyguardManager;
import android.app.Notification;
@@ -691,7 +695,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
d.setTitle(R.string.battery_saver_confirmation_title);
d.setPositiveButton(R.string.battery_saver_confirmation_ok,
(dialog, which) -> {
- setSaverMode(true, false);
+ setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION);
logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK);
});
d.setNegativeButton(android.R.string.cancel, (dialog, which) ->
@@ -790,8 +794,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
return builder;
}
- private void setSaverMode(boolean mode, boolean needFirstTimeWarning) {
- BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning);
+ private void setSaverMode(boolean mode, boolean needFirstTimeWarning,
+ @SaverManualEnabledReason int reason) {
+ BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason);
}
private void startBatterySaverSchedulePage() {
@@ -839,7 +844,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
} else if (action.equals(ACTION_START_SAVER)) {
logEvent(BatteryWarningEvents
.LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON);
- setSaverMode(true, true);
+ setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING);
dismissLowBatteryNotification();
} else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
dismissLowBatteryNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index ce690e239da0..856c64a52290 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -20,14 +20,10 @@ import android.content.res.Resources;
import android.os.Build;
import android.provider.Settings;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.util.leak.GarbageMonitor;
import java.util.ArrayList;
@@ -35,7 +31,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
+public interface QSHost {
String TILES_SETTING = Settings.Secure.QS_TILES;
int POSITION_AT_END = -1;
@@ -57,11 +53,9 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
return tiles;
}
- void warn(String message, Throwable t);
Context getContext();
Context getUserContext();
int getUserId();
- UiEventLogger getUiEventLogger();
Collection<QSTile> getTiles();
void addCallback(Callback callback);
void removeCallback(Callback callback);
@@ -75,7 +69,11 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
* @see QSFactory#createTileView
*/
QSTileView createTileView(Context themedContext, QSTile tile, boolean collapsedView);
- /** Create a {@link QSTile} of a {@code tileSpec} type. */
+ /** Create a {@link QSTile} of a {@code tileSpec} type.
+ *
+ * This should only be called by classes that need to create one-off instances of tiles.
+ * Do not use to create {@code custom} tiles without explicitly taking care of its lifecycle.
+ */
QSTile createTile(String tileSpec);
/**
@@ -105,8 +103,6 @@ public interface QSHost extends PanelInteractor, CustomTileAddedRepository {
int indexOf(String tileSpec);
- InstanceId getNewInstanceId();
-
interface Callback {
void onTilesChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
new file mode 100644
index 000000000000..67927a4153b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -0,0 +1,202 @@
+/*
+ * 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
+
+import android.content.ComponentName
+import android.content.Context
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTileView
+import com.android.systemui.qs.external.TileServiceRequestController
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+
+/**
+ * Adapter to determine what real class to use for classes that depend on [QSHost].
+ * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost].
+ * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be
+ * routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to
+ * [QSTileHost].
+ *
+ * This routing also includes dumps.
+ */
+@SysUISingleton
+class QSHostAdapter
+@Inject
+constructor(
+ private val qsTileHost: QSTileHost,
+ private val interactor: CurrentTilesInteractor,
+ private val context: Context,
+ private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
+ @Application private val scope: CoroutineScope,
+ private val featureFlags: FeatureFlags,
+ dumpManager: DumpManager,
+) : QSHost {
+
+ companion object {
+ private const val TAG = "QSTileHost"
+ }
+
+ private val useNewHost = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)
+
+ @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
+
+ init {
+ scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
+ // Redirect dump to the correct host (needed for CTS tests)
+ dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
+ }
+
+ override fun getTiles(): Collection<QSTile> {
+ return if (useNewHost) {
+ interactor.currentQSTiles
+ } else {
+ qsTileHost.getTiles()
+ }
+ }
+
+ override fun getSpecs(): List<String> {
+ return if (useNewHost) {
+ interactor.currentTilesSpecs.map { it.spec }
+ } else {
+ qsTileHost.getSpecs()
+ }
+ }
+
+ override fun removeTile(spec: String) {
+ if (useNewHost) {
+ interactor.removeTiles(listOf(TileSpec.create(spec)))
+ } else {
+ qsTileHost.removeTile(spec)
+ }
+ }
+
+ override fun addCallback(callback: QSHost.Callback) {
+ if (useNewHost) {
+ val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
+ synchronized(callbacksMap) { callbacksMap.put(callback, job) }
+ } else {
+ qsTileHost.addCallback(callback)
+ }
+ }
+
+ override fun removeCallback(callback: QSHost.Callback) {
+ if (useNewHost) {
+ synchronized(callbacksMap) { callbacksMap.get(callback)?.cancel() }
+ } else {
+ qsTileHost.removeCallback(callback)
+ }
+ }
+
+ override fun removeTiles(specs: Collection<String>) {
+ if (useNewHost) {
+ interactor.removeTiles(specs.map(TileSpec::create))
+ } else {
+ qsTileHost.removeTiles(specs)
+ }
+ }
+
+ override fun removeTileByUser(component: ComponentName) {
+ if (useNewHost) {
+ interactor.removeTiles(listOf(TileSpec.create(component)))
+ } else {
+ qsTileHost.removeTileByUser(component)
+ }
+ }
+
+ override fun addTile(spec: String, position: Int) {
+ if (useNewHost) {
+ interactor.addTile(TileSpec.create(spec), position)
+ } else {
+ qsTileHost.addTile(spec, position)
+ }
+ }
+
+ override fun addTile(component: ComponentName, end: Boolean) {
+ if (useNewHost) {
+ interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
+ } else {
+ qsTileHost.addTile(component, end)
+ }
+ }
+
+ override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
+ if (useNewHost) {
+ interactor.setTiles(newTiles.map(TileSpec::create))
+ } else {
+ qsTileHost.changeTilesByUser(previousTiles, newTiles)
+ }
+ }
+
+ override fun getContext(): Context {
+ return if (useNewHost) {
+ context
+ } else {
+ qsTileHost.context
+ }
+ }
+
+ override fun getUserContext(): Context {
+ return if (useNewHost) {
+ interactor.userContext.value
+ } else {
+ qsTileHost.userContext
+ }
+ }
+
+ override fun getUserId(): Int {
+ return if (useNewHost) {
+ interactor.userId.value
+ } else {
+ qsTileHost.userId
+ }
+ }
+
+ override fun createTileView(
+ themedContext: Context?,
+ tile: QSTile?,
+ collapsedView: Boolean
+ ): QSTileView {
+ return qsTileHost.createTileView(themedContext, tile, collapsedView)
+ }
+
+ override fun createTile(tileSpec: String): QSTile? {
+ return qsTileHost.createTile(tileSpec)
+ }
+
+ override fun addTile(spec: String) {
+ return addTile(spec, QSHost.POSITION_AT_END)
+ }
+
+ override fun addTile(tile: ComponentName) {
+ return addTile(tile, false)
+ }
+
+ override fun indexOf(tileSpec: String): Int {
+ return specs.indexOf(tileSpec)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 8bbdeeda356c..59b94b7c4bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -29,16 +29,14 @@ import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.ProtoDumpable;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -48,9 +46,10 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.external.CustomTileStatePersister;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.nano.QsTileState;
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
+import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.AutoTileManager;
@@ -85,10 +84,10 @@ import javax.inject.Provider;
* This class also provides the interface for adding/removing/changing tiles.
*/
@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable {
+public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
+ PanelInteractor, CustomTileAddedRepository {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int MAX_QS_INSTANCE_ID = 1 << 20;
// Shared prefs that hold tile lifecycle info.
@VisibleForTesting
@@ -99,10 +98,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
private final ArrayList<String> mTileSpecs = new ArrayList<>();
private final TunerService mTunerService;
private final PluginManager mPluginManager;
- private final DumpManager mDumpManager;
private final QSLogger mQSLogger;
- private final UiEventLogger mUiEventLogger;
- private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
private final Executor mMainExecutor;
private final UserFileManager mUserFileManager;
@@ -122,9 +118,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
// This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
private boolean mTilesListDirty = true;
- private final TileServiceRequestController mTileServiceRequestController;
private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
+ private final FeatureFlags mFeatureFlags;
+
@Inject
public QSTileHost(Context context,
QSFactory defaultFactory,
@@ -132,35 +129,29 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
- DumpManager dumpManager,
Optional<CentralSurfaces> centralSurfacesOptional,
QSLogger qsLogger,
- UiEventLogger uiEventLogger,
UserTracker userTracker,
SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
- TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager
+ UserFileManager userFileManager,
+ FeatureFlags featureFlags
) {
mContext = context;
mUserContext = context;
mTunerService = tunerService;
mPluginManager = pluginManager;
- mDumpManager = dumpManager;
mQSLogger = qsLogger;
- mUiEventLogger = uiEventLogger;
mMainExecutor = mainExecutor;
- mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
mUserFileManager = userFileManager;
+ mFeatureFlags = featureFlags;
- mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
mCentralSurfacesOptional = centralSurfacesOptional;
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
- mDumpManager.registerDumpable(TAG, this);
mUserTracker = userTracker;
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
@@ -172,22 +163,14 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
tunerService.addTunable(this, TILES_SETTING);
// AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
mAutoTiles = autoTiles.get();
- mTileServiceRequestController.init();
});
}
- @Override
- public InstanceId getNewInstanceId() {
- return mInstanceIdSequence.newInstanceId();
- }
-
public void destroy() {
mTiles.values().forEach(tile -> tile.destroy());
mAutoTiles.destroy();
mTunerService.removeTunable(this);
mPluginManager.removePluginListener(this);
- mDumpManager.unregisterDumpable(TAG);
- mTileServiceRequestController.destroy();
}
@Override
@@ -210,11 +193,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
}
@Override
- public UiEventLogger getUiEventLogger() {
- return mUiEventLogger;
- }
-
- @Override
public void addCallback(Callback callback) {
mCallbacks.add(callback);
}
@@ -230,11 +208,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
}
@Override
- public void warn(String message, Throwable t) {
- // already logged
- }
-
- @Override
public void collapsePanels() {
mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
}
@@ -300,6 +273,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
if (!TILES_SETTING.equals(key)) {
return;
}
+ // Do not process tiles if the flag is enabled.
+ if (mFeatureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ return;
+ }
if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt
new file mode 100644
index 000000000000..fc739edc69d2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.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.qs
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.InstanceIdSequence
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+interface QsEventLogger : UiEventLogger {
+ fun getNewInstanceId(): InstanceId
+}
+
+@SysUISingleton
+class QsEventLoggerImpl
+@Inject
+constructor(
+ uiEventLogger: UiEventLogger,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+ companion object {
+ private const val MAX_QS_INSTANCE_ID = 1 shl 20
+ }
+
+ val sequence = InstanceIdSequence(MAX_QS_INSTANCE_ID)
+ override fun getNewInstanceId(): InstanceId {
+ return sequence.newInstanceId()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 964fe7104324..1f63f5da2f2b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -19,7 +19,10 @@ package com.android.systemui.qs.dagger
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QSHostAdapter
import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.QsEventLoggerImpl
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
@@ -31,15 +34,19 @@ import dagger.Provides
@Module
interface QSHostModule {
- @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost
+ @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost
+
+ @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
@Module
companion object {
+ private const val MAX_QS_INSTANCE_ID = 1 shl 20
+
@Provides
@JvmStatic
fun providePanelInteractor(
featureFlags: FeatureFlags,
- qsHost: QSHost,
+ qsHost: QSTileHost,
panelInteractorImpl: PanelInteractorImpl
): PanelInteractor {
return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
@@ -53,7 +60,7 @@ interface QSHostModule {
@JvmStatic
fun provideCustomTileAddedRepository(
featureFlags: FeatureFlags,
- qsHost: QSHost,
+ qsHost: QSTileHost,
customTileAddedRepository: CustomTileAddedSharedPrefsRepository
): CustomTileAddedRepository {
return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
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 d4854e1a7daf..897b0e73dca0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -59,17 +59,20 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.DisplayTracker;
+import dagger.Lazy;
+
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
-import dagger.Lazy;
+
public class CustomTile extends QSTileImpl<State> implements TileChangeListener {
public static final String PREFIX = "custom(";
@@ -111,6 +114,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
private CustomTile(
QSHost host,
+ QsEventLogger uiEventLogger,
Looper backgroundLooper,
Handler mainHandler,
FalsingManager falsingManager,
@@ -124,7 +128,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
TileServices tileServices,
DisplayTracker displayTracker
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mTileServices = tileServices;
mWindowManager = WindowManagerGlobal.getWindowManagerService();
@@ -561,6 +565,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
public static class Builder {
final Lazy<QSHost> mQSHostLazy;
+ final QsEventLogger mUiEventLogger;
final Looper mBackgroundLooper;
final Handler mMainHandler;
private final FalsingManager mFalsingManager;
@@ -578,6 +583,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
@Inject
public Builder(
Lazy<QSHost> hostLazy,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -590,6 +596,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
DisplayTracker displayTracker
) {
mQSHostLazy = hostLazy;
+ mUiEventLogger = uiEventLogger;
mBackgroundLooper = backgroundLooper;
mMainHandler = mainHandler;
mFalsingManager = falsingManager;
@@ -620,6 +627,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
String action = getAction(mSpec);
return new CustomTile(
mQSHostLazy.get(),
+ mUiEventLogger,
mBackgroundLooper,
mMainHandler,
mFalsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index 00f0a67dbe22..e212bc4e7f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -22,6 +22,8 @@ import com.android.systemui.log.LogBufferFactory
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
import com.android.systemui.qs.pipeline.data.repository.TileSpecSettingsRepository
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor
+import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl
import com.android.systemui.qs.pipeline.prototyping.PrototypeCoreStartable
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import dagger.Binds
@@ -38,6 +40,11 @@ abstract class QSPipelineModule {
abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
@Binds
+ abstract fun bindCurrentTilesInteractor(
+ impl: CurrentTilesInteractorImpl
+ ): CurrentTilesInteractor
+
+ @Binds
@IntoMap
@ClassKey(PrototypeCoreStartable::class)
abstract fun providePrototypeCoreStartable(startable: PrototypeCoreStartable): CoreStartable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index d254e1b3d0d7..595b29a9dcb8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -32,6 +32,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
@@ -53,6 +54,8 @@ interface TileSpecRepository {
* at the end of the list.
*
* Passing [TileSpec.Invalid] is a noop.
+ *
+ * Trying to add a tile beyond the end of the list will add it at the end.
*/
suspend fun addTile(@UserIdInt userId: Int, tile: TileSpec, position: Int = POSITION_AT_END)
@@ -61,7 +64,7 @@ interface TileSpecRepository {
*
* Passing [TileSpec.Invalid] or a non present tile is a noop.
*/
- suspend fun removeTile(@UserIdInt userId: Int, tile: TileSpec)
+ suspend fun removeTiles(@UserIdInt userId: Int, tiles: Collection<TileSpec>)
/**
* Sets the list of current [tiles] for a given [userId].
@@ -106,6 +109,7 @@ constructor(
}
.onStart { emit(Unit) }
.map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
+ .distinctUntilChanged()
.onEach { logger.logTilesChangedInSettings(it, userId) }
.map { parseTileSpecs(it, userId) }
.flowOn(backgroundDispatcher)
@@ -117,7 +121,7 @@ constructor(
}
val tilesList = loadTiles(userId).toMutableList()
if (tile !in tilesList) {
- if (position < 0) {
+ if (position < 0 || position >= tilesList.size) {
tilesList.add(tile)
} else {
tilesList.add(position, tile)
@@ -126,12 +130,12 @@ constructor(
}
}
- override suspend fun removeTile(userId: Int, tile: TileSpec) {
- if (tile == TileSpec.Invalid) {
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+ if (tiles.all { it == TileSpec.Invalid }) {
return
}
val tilesList = loadTiles(userId).toMutableList()
- if (tilesList.remove(tile)) {
+ if (tilesList.removeAll(tiles)) {
storeTiles(userId, tilesList.toList())
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
new file mode 100644
index 000000000000..91c6e8b5fcb6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -0,0 +1,344 @@
+/*
+ * 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.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.UserHandle
+import com.android.systemui.Dumpable
+import com.android.systemui.ProtoDumpable
+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.dump.nano.SystemUIProtoDump
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.toProto
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.kotlin.pairwise
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * Interactor for retrieving the list of current QS tiles, as well as making changes to this list
+ *
+ * It is [ProtoDumpable] as it needs to be able to dump state for CTS tests.
+ */
+interface CurrentTilesInteractor : ProtoDumpable {
+ /** Current list of tiles with their corresponding spec. */
+ val currentTiles: StateFlow<List<TileModel>>
+
+ /** User for the [currentTiles]. */
+ val userId: StateFlow<Int>
+
+ /** [Context] corresponding to [userId] */
+ val userContext: StateFlow<Context>
+
+ /** List of specs corresponding to the last value of [currentTiles] */
+ val currentTilesSpecs: List<TileSpec>
+ get() = currentTiles.value.map(TileModel::spec)
+
+ /** List of tiles corresponding to the last value of [currentTiles] */
+ val currentQSTiles: List<QSTile>
+ get() = currentTiles.value.map(TileModel::tile)
+
+ /**
+ * Requests that a tile be added in the list of tiles for the current user.
+ *
+ * @see TileSpecRepository.addTile
+ */
+ fun addTile(spec: TileSpec, position: Int = TileSpecRepository.POSITION_AT_END)
+
+ /**
+ * Requests that tiles be removed from the list of tiles for the current user
+ *
+ * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and
+ * marked as removed.
+ *
+ * @see TileSpecRepository.removeTiles
+ */
+ fun removeTiles(specs: Collection<TileSpec>)
+
+ /**
+ * Requests that the list of tiles for the current user is changed to [specs].
+ *
+ * If tiles with [TileSpec.CustomTileSpec] are removed, their lifecycle will be terminated and
+ * marked as removed.
+ *
+ * @see TileSpecRepository.setTiles
+ */
+ fun setTiles(specs: List<TileSpec>)
+}
+
+/**
+ * This implementation of [CurrentTilesInteractor] will try to re-use existing [QSTile] objects when
+ * possible, in particular:
+ * * It will only destroy tiles when they are not part of the list of tiles anymore
+ * * Platform tiles will be kept between users, with a call to [QSTile.userSwitch]
+ * * [CustomTile]s will only be destroyed if the user changes.
+ */
+@SysUISingleton
+class CurrentTilesInteractorImpl
+@Inject
+constructor(
+ private val tileSpecRepository: TileSpecRepository,
+ private val userRepository: UserRepository,
+ private val customTileStatePersister: CustomTileStatePersister,
+ private val tileFactory: QSFactory,
+ private val customTileAddedRepository: CustomTileAddedRepository,
+ private val tileLifecycleManagerFactory: TileLifecycleManager.Factory,
+ private val userTracker: UserTracker,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Application private val scope: CoroutineScope,
+ private val logger: QSPipelineLogger,
+ featureFlags: FeatureFlags,
+) : CurrentTilesInteractor {
+
+ private val _currentSpecsAndTiles: MutableStateFlow<List<TileModel>> =
+ MutableStateFlow(emptyList())
+
+ override val currentTiles: StateFlow<List<TileModel>> = _currentSpecsAndTiles.asStateFlow()
+
+ // This variable should only be accessed inside the collect of `startTileCollection`.
+ private val specsToTiles = mutableMapOf<TileSpec, QSTile>()
+
+ private val currentUser = MutableStateFlow(userTracker.userId)
+ override val userId = currentUser.asStateFlow()
+
+ private val _userContext = MutableStateFlow(userTracker.userContext)
+ override val userContext = _userContext.asStateFlow()
+
+ init {
+ if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) {
+ startTileCollection()
+ }
+ }
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private fun startTileCollection() {
+ scope.launch {
+ userRepository.selectedUserInfo
+ .flatMapLatest { user ->
+ currentUser.value = user.id
+ _userContext.value = userTracker.userContext
+ tileSpecRepository.tilesSpecs(user.id).map { user.id to it }
+ }
+ .distinctUntilChanged()
+ .pairwise(-1 to emptyList())
+ .flowOn(backgroundDispatcher)
+ .collect { (old, new) ->
+ val newTileList = new.second
+ val userChanged = old.first != new.first
+ val newUser = new.first
+
+ // Destroy all tiles that are not in the new set
+ specsToTiles
+ .filter { it.key !in newTileList }
+ .forEach { entry ->
+ logger.logTileDestroyed(
+ entry.key,
+ if (userChanged) {
+ QSPipelineLogger.TileDestroyedReason
+ .TILE_NOT_PRESENT_IN_NEW_USER
+ } else {
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ }
+ )
+ entry.value.destroy()
+ }
+ // MutableMap will keep the insertion order
+ val newTileMap = mutableMapOf<TileSpec, QSTile>()
+
+ newTileList.forEach { tileSpec ->
+ if (tileSpec !in newTileMap) {
+ val newTile =
+ if (tileSpec in specsToTiles) {
+ processExistingTile(
+ tileSpec,
+ specsToTiles.getValue(tileSpec),
+ userChanged,
+ newUser
+ )
+ ?: createTile(tileSpec)
+ } else {
+ createTile(tileSpec)
+ }
+ if (newTile != null) {
+ newTileMap[tileSpec] = newTile
+ }
+ }
+ }
+
+ val resolvedSpecs = newTileMap.keys.toList()
+ specsToTiles.clear()
+ specsToTiles.putAll(newTileMap)
+ _currentSpecsAndTiles.value = newTileMap.map { TileModel(it.key, it.value) }
+ if (resolvedSpecs != newTileList) {
+ // There were some tiles that couldn't be created. Change the value in the
+ // repository
+ launch { tileSpecRepository.setTiles(currentUser.value, resolvedSpecs) }
+ }
+ }
+ }
+ }
+
+ override fun addTile(spec: TileSpec, position: Int) {
+ scope.launch {
+ tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position)
+ }
+ }
+
+ override fun removeTiles(specs: Collection<TileSpec>) {
+ val currentSpecsCopy = currentTilesSpecs.toSet()
+ val user = currentUser.value
+ // intersect: tiles that are there and are being removed
+ val toFree = currentSpecsCopy.intersect(specs).filterIsInstance<TileSpec.CustomTileSpec>()
+ toFree.forEach { onCustomTileRemoved(it.componentName, user) }
+ if (currentSpecsCopy.intersect(specs).isNotEmpty()) {
+ // We don't want to do the call to set in case getCurrentTileSpecs is not the most
+ // up to date for this user.
+ scope.launch { tileSpecRepository.removeTiles(user, specs) }
+ }
+ }
+
+ override fun setTiles(specs: List<TileSpec>) {
+ val currentSpecsCopy = currentTilesSpecs
+ val user = currentUser.value
+ if (currentSpecsCopy != specs) {
+ // minus: tiles that were there but are not there anymore
+ val toFree = currentSpecsCopy.minus(specs).filterIsInstance<TileSpec.CustomTileSpec>()
+ toFree.forEach { onCustomTileRemoved(it.componentName, user) }
+ scope.launch { tileSpecRepository.setTiles(user, specs) }
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("CurrentTileInteractorImpl:")
+ pw.println("User: ${userId.value}")
+ currentTiles.value
+ .map { it.tile }
+ .filterIsInstance<Dumpable>()
+ .forEach { it.dump(pw, args) }
+ }
+
+ override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) {
+ val data =
+ currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray()
+ systemUIProtoDump.tiles = data
+ }
+
+ private fun onCustomTileRemoved(componentName: ComponentName, userId: Int) {
+ val intent = Intent().setComponent(componentName)
+ val lifecycleManager = tileLifecycleManagerFactory.create(intent, UserHandle.of(userId))
+ lifecycleManager.onStopListening()
+ lifecycleManager.onTileRemoved()
+ customTileStatePersister.removeState(TileServiceKey(componentName, userId))
+ customTileAddedRepository.setTileAdded(componentName, userId, false)
+ lifecycleManager.flushMessagesAndUnbind()
+ }
+
+ private suspend fun createTile(spec: TileSpec): QSTile? {
+ val tile = withContext(mainDispatcher) { tileFactory.createTile(spec.spec) }
+ if (tile == null) {
+ logger.logTileNotFoundInFactory(spec)
+ return null
+ } else {
+ tile.tileSpec = spec.spec
+ return if (!tile.isAvailable) {
+ logger.logTileDestroyed(
+ spec,
+ QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE,
+ )
+ tile.destroy()
+ null
+ } else {
+ logger.logTileCreated(spec)
+ tile
+ }
+ }
+ }
+
+ private fun processExistingTile(
+ tileSpec: TileSpec,
+ qsTile: QSTile,
+ userChanged: Boolean,
+ user: Int,
+ ): QSTile? {
+ return when {
+ !qsTile.isAvailable -> {
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+ qsTile.destroy()
+ null
+ }
+ // Tile is in the current list of tiles and available.
+ // We have a handful of different cases
+ qsTile !is CustomTile -> {
+ // The tile is not a custom tile. Make sure they are reset to the correct user
+ qsTile.removeCallbacks()
+ if (userChanged) {
+ qsTile.userSwitch(user)
+ logger.logTileUserChanged(tileSpec, user)
+ }
+ qsTile
+ }
+ qsTile.user == user -> {
+ // The tile is a custom tile for the same user, just return it
+ qsTile.removeCallbacks()
+ qsTile
+ }
+ else -> {
+ // The tile is a custom tile and the user has changed. Destroy it
+ qsTile.destroy()
+ logger.logTileDestroyed(
+ tileSpec,
+ QSPipelineLogger.TileDestroyedReason.CUSTOM_TILE_USER_CHANGED
+ )
+ null
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.kt
new file mode 100644
index 000000000000..e2381ecd8b97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/TileModel.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.qs.pipeline.domain.model
+
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+/**
+ * Container for a [tile] and its [spec]. The following must be true:
+ * ```
+ * spec.spec == tile.tileSpec
+ * ```
+ */
+data class TileModel(val spec: TileSpec, val tile: QSTile) {
+ init {
+ check(spec.spec == tile.tileSpec)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
index 69d8248a11f5..89408006c300 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt
@@ -93,7 +93,7 @@ constructor(
private fun performRemove(args: List<String>, spec: TileSpec) {
val user = args.getOrNull(2)?.toInt() ?: userRepository.getSelectedUserInfo().id
- scope.launch { tileSpecRepository.removeTile(user, spec) }
+ scope.launch { tileSpecRepository.removeTiles(user, listOf(spec)) }
}
override fun help(pw: PrintWriter) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
index c691c2f668ad..af1cd0995a21 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/TileSpec.kt
@@ -66,6 +66,10 @@ sealed class TileSpec private constructor(open val spec: String) {
}
}
+ fun create(component: ComponentName): CustomTileSpec {
+ return CustomTileSpec(CustomTile.toSpec(component), component)
+ }
+
private val String.isCustomTileSpec: Boolean
get() = startsWith(CustomTile.PREFIX)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index 200f7431e906..767ce919d027 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -73,4 +73,59 @@ constructor(
{ "Tiles changed in settings for user $int1: $str1" }
)
}
+
+ /** Log when a tile is destroyed and its reason for destroying. */
+ fun logTileDestroyed(spec: TileSpec, reason: TileDestroyedReason) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = spec.toString()
+ str2 = reason.readable
+ },
+ { "Tile $str1 destroyed. Reason: $str2" }
+ )
+ }
+
+ /** Log when a tile is created. */
+ fun logTileCreated(spec: TileSpec) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.DEBUG,
+ { str1 = spec.toString() },
+ { "Tile $str1 created" }
+ )
+ }
+
+ /** Ĺog when trying to create a tile, but it's not found in the factory. */
+ fun logTileNotFoundInFactory(spec: TileSpec) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.VERBOSE,
+ { str1 = spec.toString() },
+ { "Tile $str1 not found in factory" }
+ )
+ }
+
+ /** Log when the user is changed for a platform tile. */
+ fun logTileUserChanged(spec: TileSpec, user: Int) {
+ tileListLogBuffer.log(
+ TILE_LIST_TAG,
+ LogLevel.VERBOSE,
+ {
+ str1 = spec.toString()
+ int1 = user
+ },
+ { "User changed to $int1 for tile $str1" }
+ )
+ }
+
+ /** Reasons for destroying an existing tile. */
+ enum class TileDestroyedReason(val readable: String) {
+ TILE_REMOVED("Tile removed from current set"),
+ CUSTOM_TILE_USER_CHANGED("User changed for custom tile"),
+ NEW_TILE_NOT_AVAILABLE("New tile not available"),
+ EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"),
+ TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"),
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 49ba5086f64d..2a9e7d05c187 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -66,6 +66,7 @@ import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SideLabelTileLayout;
import com.android.systemui.qs.logging.QSLogger;
@@ -179,6 +180,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
protected QSTileImpl(
QSHost host,
+ QsEventLogger uiEventLogger,
Looper backgroundLooper,
Handler mainHandler,
FalsingManager falsingManager,
@@ -189,8 +191,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
) {
mHost = host;
mContext = host.getContext();
- mInstanceId = host.getNewInstanceId();
- mUiEventLogger = host.getUiEventLogger();
+ mInstanceId = uiEventLogger.getNewInstanceId();
+ mUiEventLogger = uiEventLogger;
mUiHandler = mainHandler;
mHandler = new H(backgroundLooper);
@@ -633,7 +635,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
} catch (Throwable t) {
final String error = "Error in " + name;
Log.w(TAG, error, t);
- mHost.warn(error, t);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 92a83bba8a68..30765f7f974d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -45,15 +45,18 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.GlobalSettings;
+import dagger.Lazy;
+
import javax.inject.Inject;
-import dagger.Lazy;
+
/** Quick settings tile: Airplane mode **/
public class AirplaneModeTile extends QSTileImpl<BooleanState> {
@@ -69,6 +72,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> {
@Inject
public AirplaneModeTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -81,7 +85,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> {
GlobalSettings globalSettings,
UserTracker userTracker
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBroadcastDispatcher = broadcastDispatcher;
mLazyConnectivityManager = lazyConnectivityManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
index 2ca452e45ecf..c709969ec6e2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -22,6 +22,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.settings.UserTracker
@@ -31,6 +32,7 @@ import javax.inject.Inject
class AlarmTile @Inject constructor(
host: QSHost,
+ uiEventLogger: QsEventLogger,
@Background backgroundLooper: Looper,
@Main mainHandler: Handler,
falsingManager: FalsingManager,
@@ -42,6 +44,7 @@ class AlarmTile @Inject constructor(
nextAlarmController: NextAlarmController
) : QSTileImpl<QSTile.State>(
host,
+ uiEventLogger,
backgroundLooper,
mainHandler,
falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 027a464251c9..a444e7631527 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -62,6 +63,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
@Inject
public BatterySaverTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -72,7 +74,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
BatteryController batteryController,
SecureSettings secureSettings
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBatteryController = batteryController;
mBatteryController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 08fe2709b810..30218a6a3180 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -47,6 +47,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BluetoothController;
@@ -74,6 +75,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
@Inject
public BluetoothTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -83,7 +85,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
QSLogger qsLogger,
BluetoothController bluetoothController
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = bluetoothController;
mController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index 93e5f1efbdc8..65ef6b9c31a5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
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.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@ public class CameraToggleTile extends SensorPrivacyToggleTile {
public static final String TILE_SPEC = "cameratoggle";
@Inject
- protected CameraToggleTile(QSHost host,
+ protected CameraToggleTile(
+ QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile {
QSLogger qsLogger,
IndividualSensorPrivacyController sensorPrivacyController,
KeyguardStateController keyguardStateController) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
keyguardStateController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 8d984817ba77..54376fe604df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -47,6 +47,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -84,6 +85,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
@Inject
public CastTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -97,7 +99,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
HotspotController hotspotController,
DialogLaunchAnimator dialogLaunchAnimator
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = castController;
mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
index b6205d5df63d..cf9e3468c8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -56,6 +57,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> {
@Inject
public ColorCorrectionTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -66,7 +68,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> {
UserTracker userTracker,
SecureSettings secureSettings
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 9a44e83ad9a1..4ecde6123dd5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -55,6 +56,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> {
@Inject
public ColorInversionTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -65,7 +67,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> {
UserTracker userTracker,
SecureSettings secureSettings
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mSetting = new SettingObserver(secureSettings, mHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index add517e18516..e769b5ef9989 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -58,6 +59,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
@Inject
public DataSaverTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -68,7 +70,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
DataSaverController dataSaverController,
DialogLaunchAnimator dialogLaunchAnimator
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mDataSaverController = dataSaverController;
mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
index 01164fb0a009..ddaff3bb9a64 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt
@@ -40,6 +40,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import java.util.concurrent.atomic.AtomicBoolean
@@ -47,6 +48,7 @@ import javax.inject.Inject
class DeviceControlsTile @Inject constructor(
host: QSHost,
+ uiEventLogger: QsEventLogger,
@Background backgroundLooper: Looper,
@Main mainHandler: Handler,
falsingManager: FalsingManager,
@@ -56,14 +58,15 @@ class DeviceControlsTile @Inject constructor(
qsLogger: QSLogger,
private val controlsComponent: ControlsComponent
) : QSTileImpl<QSTile.State>(
- host,
- backgroundLooper,
- mainHandler,
- falsingManager,
- metricsLogger,
- statusBarStateController,
- activityStarter,
- qsLogger
+ host,
+ uiEventLogger,
+ backgroundLooper,
+ mainHandler,
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger
) {
private var hasControlsApps = AtomicBoolean(false)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 434fe45f47c0..3e7bdd1a1444 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -54,6 +54,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -88,6 +89,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
@Inject
public DndTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -100,7 +102,7 @@ public class DndTile extends QSTileImpl<BooleanState> {
SecureSettings secureSettings,
DialogLaunchAnimator dialogLaunchAnimator
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = zenModeController;
mSharedPreferences = sharedPreferences;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
index f913326a6a67..eef4c1dd4436 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -48,6 +48,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -90,6 +91,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
@Inject
public DreamTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -105,7 +107,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
@Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER)
boolean dreamOnlyEnabledForDockUser
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mDreamManager = dreamManager;
mBroadcastDispatcher = broadcastDispatcher;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
index e091a750fbec..2c986da8370a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -55,6 +56,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements
@Inject
public FlashlightTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -64,7 +66,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements
QSLogger qsLogger,
FlashlightController flashlightController
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mFlashlightController = flashlightController;
mFlashlightController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
index 3f514344cee1..12d98473ff07 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt
@@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -47,6 +48,7 @@ class FontScalingTile
@Inject
constructor(
host: QSHost,
+ uiEventLogger: QsEventLogger,
@Background backgroundLooper: Looper,
@Main mainHandler: Handler,
falsingManager: FalsingManager,
@@ -61,6 +63,7 @@ constructor(
) :
QSTileImpl<QSTile.State?>(
host,
+ uiEventLogger,
backgroundLooper,
mainHandler,
falsingManager,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 6bf8b7666054..4c3699ced6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -61,6 +62,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> {
@Inject
public HotspotTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -71,7 +73,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> {
HotspotController hotspotController,
DataSaverController dataSaverController
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mHotspotController = hotspotController;
mDataSaverController = dataSaverController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 75d01723667d..f16f0dcc5dba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,6 +51,7 @@ import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.AlphaControlledSignalTileView;
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.qs.tiles.dialog.InternetDialogFactory;
@@ -90,6 +91,7 @@ public class InternetTile extends QSTileImpl<SignalState> {
@Inject
public InternetTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -101,7 +103,7 @@ public class InternetTile extends QSTileImpl<SignalState> {
AccessPointController accessPointController,
InternetDialogFactory internetDialogFactory
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mInternetDialogFactory = internetDialogFactory;
mHandler = mainHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index 27f58269722a..83c568878c94 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@ public class LocationTile extends QSTileImpl<BooleanState> {
@Inject
public LocationTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -70,7 +72,7 @@ public class LocationTile extends QSTileImpl<BooleanState> {
KeyguardStateController keyguardStateController,
PanelInteractor panelInteractor
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = locationController;
mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index 2e475d40d55f..86a6a8c6a2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
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.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -48,7 +49,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile {
public static final String TILE_SPEC = "mictoggle";
@Inject
- protected MicrophoneToggleTile(QSHost host,
+ protected MicrophoneToggleTile(
+ QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
MetricsLogger metricsLogger,
@@ -58,7 +61,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile {
QSLogger qsLogger,
IndividualSensorPrivacyController sensorPrivacyController,
KeyguardStateController keyguardStateController) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger, sensorPrivacyController,
keyguardStateController);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
index e189f80a7c23..29ccb76de820 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -43,6 +43,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
@@ -65,6 +66,7 @@ public class NfcTile extends QSTileImpl<BooleanState> {
@Inject
public NfcTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -74,7 +76,7 @@ public class NfcTile extends QSTileImpl<BooleanState> {
QSLogger qsLogger,
BroadcastDispatcher broadcastDispatcher
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBroadcastDispatcher = broadcastDispatcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index aacd53bde51c..405e1394871c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -45,6 +45,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.LocationController;
@@ -80,6 +81,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements
@Inject
public NightDisplayTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -91,7 +93,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements
ColorDisplayManager colorDisplayManager,
NightDisplayListenerModule.Builder nightDisplayListenerBuilder
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mLocationController = locationController;
mManager = colorDisplayManager;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
index ae67d99ea2fb..1eb317aaeae8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java
@@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -57,6 +58,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> {
@Inject
public OneHandedModeTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -66,7 +68,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> {
QSLogger qsLogger,
UserTracker userTracker,
SecureSettings secureSettings) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mSetting = new SettingObserver(secureSettings, mHandler,
Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
index 92f52724ca0c..9e365d34bed9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
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;
@@ -62,6 +63,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> {
@Inject
public QRCodeScannerTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -70,7 +72,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> {
ActivityStarter activityStarter,
QSLogger qsLogger,
QRCodeScannerController qrCodeScannerController) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mQRCodeScannerController = qrCodeScannerController;
mQRCodeScannerController.observe(getLifecycle(), mCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
index 4a3c56328006..e026bdbc2e08 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java
@@ -49,6 +49,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -83,6 +84,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
@Inject
public QuickAccessWalletTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -94,7 +96,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> {
PackageManager packageManager,
SecureSettings secureSettings,
QuickAccessWalletController quickAccessWalletController) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = quickAccessWalletController;
mKeyguardStateController = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
index 10f1ce4946c8..2e04afb0048a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -59,6 +60,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
@Named(RBC_AVAILABLE) boolean isAvailable,
ReduceBrightColorsController reduceBrightColorsController,
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -67,7 +69,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState>
ActivityStarter activityStarter,
QSLogger qsLogger
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mReduceBrightColorsController = reduceBrightColorsController;
mReduceBrightColorsController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 8888c733c3c1..7f7f8ad6a4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -44,6 +44,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -71,6 +72,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
@Inject
public RotationLockTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -83,7 +85,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
BatteryController batteryController,
SecureSettings secureSettings
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = rotationLockController;
mController.observe(this, mCallback);
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 65592a717565..2d4652db6b80 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
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.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -74,6 +75,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
@Inject
public ScreenRecordTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -88,7 +90,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
DialogLaunchAnimator dialogLaunchAnimator,
PanelInteractor panelInteractor
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = controller;
mController.observe(this, mCallback);
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 d99c1d1daf7e..7c4f097a1724 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java
@@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
@@ -68,7 +69,9 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
*/
public abstract String getRestriction();
- protected SensorPrivacyToggleTile(QSHost host,
+ protected SensorPrivacyToggleTile(
+ QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -78,7 +81,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS
QSLogger qsLogger,
IndividualSensorPrivacyController sensorPrivacyController,
KeyguardStateController keyguardStateController) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mSensorPrivacyController = sensorPrivacyController;
mKeyguard = keyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
index 809689cb806d..a60d1ad448b4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java
@@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -69,6 +70,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
@Inject
public UiModeNightTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -80,7 +82,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements
BatteryController batteryController,
LocationController locationController
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mBatteryController = batteryController;
mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 6a5c99032457..17e72e597e58 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -40,6 +40,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.phone.ManagedProfileController;
@@ -59,6 +60,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
@Inject
public WorkModeTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -68,7 +70,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements
QSLogger qsLogger,
ManagedProfileController managedProfileController
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mProfileController = managedProfileController;
mProfileController.observe(getLifecycle(), this);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index f62e93976d71..0a188e0e7d0b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -16,6 +16,8 @@
package com.android.systemui.recents;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -32,6 +34,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
@@ -46,6 +49,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -112,7 +116,9 @@ import dagger.Lazy;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.function.Supplier;
@@ -390,19 +396,36 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateEnabledState();
+ // If adding, bind immediately
+ if (Objects.equals(intent.getAction(), ACTION_PACKAGE_ADDED)) {
+ updateEnabledAndBinding();
+ return;
+ }
+
+ // ACTION_PACKAGE_CHANGED
+ String[] compsList = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ if (compsList == null) {
+ return;
+ }
- // Reconnect immediately, instead of waiting for resume to arrive.
- startConnectionToCurrentUser();
+ // Only rebind for TouchInteractionService component from launcher
+ ResolveInfo ri = context.getPackageManager()
+ .resolveService(new Intent(ACTION_QUICKSTEP), 0);
+ String interestingComponent = ri.serviceInfo.name;
+ for (String component : compsList) {
+ if (interestingComponent.equals(component)) {
+ Log.i(TAG_OPS, "Rebinding for component [" + component + "] change");
+ updateEnabledAndBinding();
+ return;
+ }
+ }
}
};
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- if (SysUiState.DEBUG) {
- Log.d(TAG_OPS, "Overview proxy service connected");
- }
+ Log.d(TAG_OPS, "Overview proxy service connected");
mConnectionBackoffAttempts = 0;
mHandler.removeCallbacks(mDeferredConnectionCallback);
try {
@@ -447,6 +470,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
notifySystemUiStateFlags(mSysUiState.getFlags());
notifyConnectionChanged();
+ if (mLatchForOnUserChanging != null) {
+ mLatchForOnUserChanging.countDown();
+ mLatchForOnUserChanging = null;
+ }
}
@Override
@@ -501,11 +528,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
};
+ private CountDownLatch mLatchForOnUserChanging;
private final UserTracker.Callback mUserChangedCallback =
new UserTracker.Callback() {
@Override
- public void onUserChanged(int newUser, @NonNull Context userContext) {
+ public void onUserChanging(int newUser, @NonNull Context userContext,
+ CountDownLatch latch) {
mConnectionBackoffAttempts = 0;
+ mLatchForOnUserChanging = latch;
internalConnectToCurrentUser("User changed");
}
};
@@ -603,8 +633,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
screenLifecycle.addObserver(mScreenLifecycleObserver);
wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver);
// Connect to the service
- updateEnabledState();
- startConnectionToCurrentUser();
+ updateEnabledAndBinding();
// Listen for assistant changes
assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener);
@@ -626,6 +655,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
private void dispatchNavigationBarSurface() {
try {
if (mOverviewProxy != null) {
+ // Catch all for cases where the surface is no longer valid
+ if (mNavigationBarSurface != null && !mNavigationBarSurface.isValid()) {
+ mNavigationBarSurface = null;
+ }
mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
}
} catch (RemoteException e) {
@@ -633,6 +666,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
}
+ private void updateEnabledAndBinding() {
+ updateEnabledState();
+ startConnectionToCurrentUser();
+ }
+
private void updateSystemUiStateFlags() {
final NavigationBar navBarFragment =
mNavBarControllerLazy.get().getDefaultNavigationBar();
@@ -676,11 +714,14 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
- boolean bouncerShowing, boolean isDozing, boolean panelExpanded, boolean isDreaming) {
+ boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
+ boolean panelExpanded, boolean isDreaming) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
keyguardShowing && keyguardOccluded)
+ .setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
+ keyguardGoingAway)
.setFlag(SYSUI_STATE_BOUNCER_SHOWING, bouncerShowing)
.setFlag(SYSUI_STATE_DEVICE_DOZING, isDozing)
.setFlag(SYSUI_STATE_DEVICE_DREAMING, isDreaming)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 6f85c45a6614..c9d1da38b196 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -289,7 +289,7 @@ public class ScreenshotController {
if (DEBUG_INPUT) {
Log.d(TAG, "Predictive Back callback dispatched");
}
- respondToBack();
+ respondToKeyDismissal();
};
private ScreenshotView mScreenshotView;
@@ -581,7 +581,7 @@ public class ScreenshotController {
}
}
- private void respondToBack() {
+ private void respondToKeyDismissal() {
dismissScreenshot(SCREENSHOT_DISMISSED_OTHER);
}
@@ -641,11 +641,11 @@ public class ScreenshotController {
mScreenshotView.setDefaultTimeoutMillis(mScreenshotHandler.getDefaultTimeoutMillis());
mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
if (DEBUG_INPUT) {
- Log.d(TAG, "onKeyEvent: KeyEvent.KEYCODE_BACK");
+ Log.d(TAG, "onKeyEvent: " + keyCode);
}
- respondToBack();
+ respondToKeyDismissal();
return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index d0b7ad3e9dd5..394949297d6d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -27,9 +27,15 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.infra.ServiceConnector;
import com.android.internal.statusbar.IAppClipsService;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Application;
@@ -37,6 +43,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
+import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
@@ -46,21 +53,63 @@ import javax.inject.Inject;
*/
public class AppClipsService extends Service {
+ private static final String TAG = AppClipsService.class.getSimpleName();
+
@Application private final Context mContext;
private final FeatureFlags mFeatureFlags;
private final Optional<Bubbles> mOptionalBubbles;
private final DevicePolicyManager mDevicePolicyManager;
+ private final UserManager mUserManager;
+
private final boolean mAreTaskAndTimeIndependentPrerequisitesMet;
+ @VisibleForTesting()
+ @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile;
+
@Inject
public AppClipsService(@Application Context context, FeatureFlags featureFlags,
- Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) {
+ Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager,
+ UserManager userManager) {
mContext = context;
mFeatureFlags = featureFlags;
mOptionalBubbles = optionalBubbles;
mDevicePolicyManager = devicePolicyManager;
+ mUserManager = userManager;
+
+ // The consumer of this service are apps that call through StatusBarManager API to query if
+ // it can use app clips API. Since these apps can be launched as work profile users, this
+ // service will start as work profile user. SysUI doesn't share injected instances for
+ // different users. This is why the bubbles instance injected will be incorrect. As the apps
+ // don't generally have permission to connect to a service running as different user, we
+ // start a proxy connection to communicate with the main user's version of this service.
+ if (mUserManager.isManagedProfile()) {
+ // No need to check for prerequisites in this case as those are incorrect for work
+ // profile user instance of the service and the main user version of the service will
+ // take care of this check.
+ mAreTaskAndTimeIndependentPrerequisitesMet = false;
+
+ // Get the main user so that we can connect to the main user's version of the service.
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (mainUser == null) {
+ // If main user is not available there isn't much we can do, no apps can use app
+ // clips.
+ return;
+ }
+
+ // Set up the connection to be used later during onBind callback.
+ mProxyConnectorToMainProfile =
+ new ServiceConnector.Impl<>(
+ context,
+ new Intent(context, AppClipsService.class),
+ Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY
+ | Context.BIND_NOT_VISIBLE,
+ mainUser.getIdentifier(),
+ IAppClipsService.Stub::asInterface);
+ return;
+ }
mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables();
+ mProxyConnectorToMainProfile = null;
}
private boolean checkIndependentVariables() {
@@ -95,6 +144,13 @@ public class AppClipsService extends Service {
return new IAppClipsService.Stub() {
@Override
public boolean canLaunchCaptureContentActivityForNote(int taskId) {
+ // In case of managed profile, use the main user's instance of the service. Callers
+ // cannot directly connect to the main user's instance as they may not have the
+ // permission to interact across users.
+ if (mUserManager.isManagedProfile()) {
+ return canLaunchCaptureContentActivityForNoteFromMainUser(taskId);
+ }
+
if (!mAreTaskAndTimeIndependentPrerequisitesMet) {
return false;
}
@@ -107,4 +163,21 @@ public class AppClipsService extends Service {
}
};
}
+
+ /** Returns whether the app clips API can be used by querying the service as the main user. */
+ private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) {
+ if (mProxyConnectorToMainProfile == null) {
+ return false;
+ }
+
+ try {
+ AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult(
+ service -> service.canLaunchCaptureContentActivityForNote(taskId));
+ return future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ Log.d(TAG, "Exception from service\n" + e);
+ }
+
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 3cb1a34a921c..0487cbc995dd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -40,6 +40,8 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Parcel;
import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.Nullable;
@@ -79,13 +81,10 @@ public class AppClipsTrampolineActivity extends Activity {
private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+ static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
- public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
+ static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
+ static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0);
private final DevicePolicyManager mDevicePolicyManager;
@@ -95,6 +94,7 @@ public class AppClipsTrampolineActivity extends Activity {
private final PackageManager mPackageManager;
private final UserTracker mUserTracker;
private final UiEventLogger mUiEventLogger;
+ private final UserManager mUserManager;
private final ResultReceiver mResultReceiver;
private Intent mKillAppClipsBroadcastIntent;
@@ -103,7 +103,7 @@ public class AppClipsTrampolineActivity extends Activity {
public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags,
Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController,
PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger,
- @Main Handler mainHandler) {
+ UserManager userManager, @Main Handler mainHandler) {
mDevicePolicyManager = devicePolicyManager;
mFeatureFlags = flags;
mOptionalBubbles = optionalBubbles;
@@ -111,6 +111,7 @@ public class AppClipsTrampolineActivity extends Activity {
mPackageManager = packageManager;
mUserTracker = userTracker;
mUiEventLogger = uiEventLogger;
+ mUserManager = userManager;
mResultReceiver = createResultReceiver(mainHandler);
}
@@ -123,6 +124,12 @@ public class AppClipsTrampolineActivity extends Activity {
return;
}
+ if (mUserManager.isManagedProfile()) {
+ maybeStartActivityForWPUser();
+ finish();
+ return;
+ }
+
if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) {
finish();
return;
@@ -191,6 +198,19 @@ public class AppClipsTrampolineActivity extends Activity {
}
}
+ private void maybeStartActivityForWPUser() {
+ UserHandle mainUser = mUserManager.getMainUser();
+ if (mainUser == null) {
+ setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ return;
+ }
+
+ // Start the activity as the main user with activity result forwarding.
+ startActivityAsUser(
+ new Intent(this, AppClipsTrampolineActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser);
+ }
+
private void setErrorResultAndFinish(int errorCode) {
setResult(RESULT_OK,
new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode));
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 72286f175671..3711a2f39b7b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -162,7 +162,7 @@ open class UserTrackerImpl internal constructor(
private fun registerUserSwitchObserver() {
iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() {
override fun onBeforeUserSwitching(newUserId: Int) {
- setUserIdInternal(newUserId)
+ handleBeforeUserSwitching(newUserId)
}
override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) {
@@ -180,6 +180,10 @@ open class UserTrackerImpl internal constructor(
}, TAG)
}
+ protected open fun handleBeforeUserSwitching(newUserId: Int) {
+ setUserIdInternal(newUserId)
+ }
+
@WorkerThread
protected open fun handleUserSwitching(newUserId: Int) {
Assert.isNotMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 754036d3baa9..b8bd95c89ec8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -14,9 +14,9 @@
package com.android.systemui.shade
import android.view.MotionEvent
+import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
import java.text.SimpleDateFormat
import java.util.Locale
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 79d3b26e01c7..a4a7d4cd76e5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -22,10 +22,6 @@ import static android.view.MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
-import static androidx.constraintlayout.widget.ConstraintSet.END;
-import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION;
import static com.android.keyguard.KeyguardClockSwitch.LARGE;
import static com.android.keyguard.KeyguardClockSwitch.SMALL;
import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE;
@@ -73,12 +69,6 @@ import android.os.Trace;
import android.os.UserManager;
import android.os.VibrationEffect;
import android.provider.Settings;
-import android.transition.ChangeBounds;
-import android.transition.Transition;
-import android.transition.TransitionListenerAdapter;
-import android.transition.TransitionManager;
-import android.transition.TransitionSet;
-import android.transition.TransitionValues;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
@@ -100,8 +90,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import androidx.constraintlayout.widget.ConstraintSet;
-
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
@@ -141,6 +129,7 @@ import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
@@ -162,7 +151,7 @@ import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.plugins.ClockController;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.FalsingManager.FalsingTapListener;
import com.android.systemui.plugins.qs.QS;
@@ -299,11 +288,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
/**
- * Duration to use for the animator when the keyguard status view alignment changes, and a
- * custom clock animation is in use.
- */
- private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000;
- /**
* Whether the Shade should animate to reflect Back gesture progress.
* To minimize latency at runtime, we cache this, else we'd be reading it every time
* updateQsExpansion() is called... and it's called very often.
@@ -347,6 +331,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final PulseExpansionHandler mPulseExpansionHandler;
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final ConversationNotificationManager mConversationNotificationManager;
private final AuthController mAuthController;
private final MediaHierarchyManager mMediaHierarchyManager;
@@ -550,8 +535,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final KeyguardMediaController mKeyguardMediaController;
- private boolean mStatusViewCentered = true;
-
private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
private final Optional<NotificationPanelUnfoldAnimationController>
mNotificationPanelUnfoldAnimationController;
@@ -682,35 +665,29 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
step.getTransitionState() == TransitionState.RUNNING;
};
- private final TransitionListenerAdapter mKeyguardStatusAlignmentTransitionListener =
- new TransitionListenerAdapter() {
- @Override
- public void onTransitionCancel(Transition transition) {
- mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
- }
-
- @Override
- public void onTransitionEnd(Transition transition) {
- mInteractionJankMonitor.end(CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
- }
- };
+ private final ActivityStarter mActivityStarter;
@Inject
public NotificationPanelViewController(NotificationPanelView view,
@Main Handler handler,
LayoutInflater layoutInflater,
FeatureFlags featureFlags,
- NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
+ NotificationWakeUpCoordinator coordinator,
+ PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
- KeyguardBypassController bypassController, FalsingManager falsingManager,
+ KeyguardBypassController bypassController,
+ FalsingManager falsingManager,
FalsingCollector falsingCollector,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarWindowStateController statusBarWindowStateController,
NotificationShadeWindowController notificationShadeWindowController,
DozeLog dozeLog,
- DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
- LatencyTracker latencyTracker, PowerManager powerManager,
+ DozeParameters dozeParameters,
+ CommandQueue commandQueue,
+ VibratorHelper vibratorHelper,
+ LatencyTracker latencyTracker,
+ PowerManager powerManager,
AccessibilityManager accessibilityManager, @DisplayId int displayId,
KeyguardUpdateMonitor keyguardUpdateMonitor,
MetricsLogger metricsLogger,
@@ -771,7 +748,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
Provider<MultiShadeInteractor> multiShadeInteractorProvider,
DumpManager dumpManager,
KeyguardLongPressViewModel keyguardLongPressViewModel,
- KeyguardInteractor keyguardInteractor) {
+ KeyguardInteractor keyguardInteractor,
+ ActivityStarter activityStarter,
+ KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
mInteractionJankMonitor = interactionJankMonitor;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
@@ -915,6 +894,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mScreenOffAnimationController = screenOffAnimationController;
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE);
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
int currentMode = navigationModeController.addListener(
mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode));
@@ -952,6 +932,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return Unit.INSTANCE;
},
mFalsingManager);
+ mActivityStarter = activityStarter;
onFinishInflate();
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@@ -1313,9 +1294,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
R.layout.keyguard_status_view, mNotificationContainerParent, false);
mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
- // When it's reinflated, this is centered by default. If it shouldn't be, this will update
- // below when resources are updated.
- mStatusViewCentered = true;
attachSplitShadeMediaPlayerContainer(
keyguardStatusView.findViewById(R.id.status_view_media_container));
@@ -1394,7 +1372,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mLockIconViewController,
stringResourceId ->
mKeyguardIndicationController.showTransientIndication(stringResourceId),
- mVibratorHelper);
+ mVibratorHelper,
+ mActivityStarter);
}
@VisibleForTesting
@@ -1609,68 +1588,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void updateKeyguardStatusViewAlignment(boolean animate) {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
- if (mStatusViewCentered != shouldBeCentered) {
- mStatusViewCentered = shouldBeCentered;
- ConstraintSet constraintSet = new ConstraintSet();
- constraintSet.clone(mNotificationContainerParent);
- int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
- constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
- if (animate) {
- mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION);
- ChangeBounds transition = new ChangeBounds();
- if (mSplitShadeEnabled) {
- // Excluding media from the transition on split-shade, as it doesn't transition
- // horizontally properly.
- transition.excludeTarget(R.id.status_view_media_container, true);
- }
-
- transition.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- transition.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-
- ClockController clock = mKeyguardStatusViewController.getClockController();
- boolean customClockAnimation = clock != null
- && clock.getConfig().getHasCustomPositionUpdatedAnimation();
-
- if (mFeatureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION) && customClockAnimation) {
- // Find the clock, so we can exclude it from this transition.
- FrameLayout clockContainerView =
- mView.findViewById(R.id.lockscreen_clock_view_large);
-
- // The clock container can sometimes be null. If it is, just fall back to the
- // old animation rather than setting up the custom animations.
- if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
- transition.addListener(mKeyguardStatusAlignmentTransitionListener);
- TransitionManager.beginDelayedTransition(
- mNotificationContainerParent, transition);
- } else {
- View clockView = clockContainerView.getChildAt(0);
-
- transition.excludeTarget(clockView, /* exclude= */ true);
-
- TransitionSet set = new TransitionSet();
- set.addTransition(transition);
-
- SplitShadeTransitionAdapter adapter =
- new SplitShadeTransitionAdapter(mKeyguardStatusViewController);
-
- // Use linear here, so the actual clock can pick its own interpolator.
- adapter.setInterpolator(Interpolators.LINEAR);
- adapter.setDuration(KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION);
- adapter.addTarget(clockView);
- set.addTransition(adapter);
- set.addListener(mKeyguardStatusAlignmentTransitionListener);
- TransitionManager.beginDelayedTransition(mNotificationContainerParent, set);
- }
- } else {
- transition.addListener(mKeyguardStatusAlignmentTransitionListener);
- TransitionManager.beginDelayedTransition(
- mNotificationContainerParent, transition);
- }
- }
-
- constraintSet.applyTo(mNotificationContainerParent);
- }
- mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(mStatusViewCentered));
+ mKeyguardStatusViewController.updateAlignment(
+ mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate);
+ mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
}
private boolean shouldKeyguardStatusViewBeCentered() {
@@ -2848,6 +2768,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
// Try triggering face auth, this "might" run. Check
// KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
+ mKeyguardFaceAuthInteractor.onNotificationPanelClicked();
boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
@@ -3324,7 +3245,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
ipw.print("mIsGestureNavigation="); ipw.println(mIsGestureNavigation);
ipw.print("mOldLayoutDirection="); ipw.println(mOldLayoutDirection);
ipw.print("mMinFraction="); ipw.println(mMinFraction);
- ipw.print("mStatusViewCentered="); ipw.println(mStatusViewCentered);
ipw.print("mSplitShadeFullTransitionDistance=");
ipw.println(mSplitShadeFullTransitionDistance);
ipw.print("mSplitShadeScrimTransitionDistance=");
@@ -3418,7 +3338,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mGestureRecorder = recorder;
mHideExpandedRunnable = hideExpandedRunnable;
- mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
+ if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ mNotificationStackScrollLayoutController.setShelfController(
+ notificationShelfController);
+ }
mNotificationShelfController = notificationShelfController;
mLockscreenShadeTransitionController.bindController(notificationShelfController);
updateMaxDisplayedNotifications(true);
@@ -4925,6 +4848,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
handled |= handleTouch(event);
+ mShadeLog.logOnTouchEventLastReturn(event, !mDozing, handled);
return !mDozing || handled;
}
@@ -5107,6 +5031,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
break;
}
+ mShadeLog.logHandleTouchLastReturn(event, !mGestureWaitForTouchSlop, mTracking);
return !mGestureWaitForTouchSlop || mTracking;
}
@@ -5117,65 +5042,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
}
- static class SplitShadeTransitionAdapter extends Transition {
- private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
- private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
-
- private final KeyguardStatusViewController mController;
-
- SplitShadeTransitionAdapter(KeyguardStatusViewController controller) {
- mController = controller;
- }
-
- private void captureValues(TransitionValues transitionValues) {
- Rect boundsRect = new Rect();
- boundsRect.left = transitionValues.view.getLeft();
- boundsRect.top = transitionValues.view.getTop();
- boundsRect.right = transitionValues.view.getRight();
- boundsRect.bottom = transitionValues.view.getBottom();
- transitionValues.values.put(PROP_BOUNDS, boundsRect);
- }
-
- @Override
- public void captureEndValues(TransitionValues transitionValues) {
- captureValues(transitionValues);
- }
-
- @Override
- public void captureStartValues(TransitionValues transitionValues) {
- captureValues(transitionValues);
- }
-
- @Nullable
- @Override
- public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
- @Nullable TransitionValues endValues) {
- if (startValues == null || endValues == null) {
- return null;
- }
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-
- Rect from = (Rect) startValues.values.get(PROP_BOUNDS);
- Rect to = (Rect) endValues.values.get(PROP_BOUNDS);
-
- anim.addUpdateListener(animation -> {
- ClockController clock = mController.getClockController();
- if (clock == null) {
- return;
- }
-
- clock.getAnimations().onPositionUpdated(from, to, animation.getAnimatedFraction());
- });
-
- return anim;
- }
-
- @Override
- public String[] getTransitionProperties() {
- return TRANSITION_PROPERTIES;
- }
- }
-
private final class HeadsUpNotificationViewControllerImpl implements
HeadsUpTouchHelper.HeadsUpNotificationViewController {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 156e4fd1889f..af74c2793cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -561,6 +561,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
for (StatusBarWindowCallback cb : activeCallbacks) {
cb.onStateChanged(mCurrentState.keyguardShowing,
mCurrentState.keyguardOccluded,
+ mCurrentState.keyguardGoingAway,
mCurrentState.bouncerShowing,
mCurrentState.dozing,
mCurrentState.panelExpanded,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index fed9b8469c4b..7812f07fc59c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -16,9 +16,9 @@
package com.android.systemui.shade
+import com.android.systemui.common.buffer.RingBuffer
import com.android.systemui.dump.DumpsysTableLogger
import com.android.systemui.dump.Row
-import com.android.systemui.plugins.util.RingBuffer
import com.android.systemui.shade.NotificationShadeWindowState.Buffer
import com.android.systemui.statusbar.StatusBarState
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index b31ec3319781..ef14d1cb7f63 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -17,6 +17,8 @@
package com.android.systemui.shade;
+import static android.view.WindowInsets.Type.ime;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
import static com.android.systemui.shade.NotificationPanelViewController.COUNTER_PANEL_OPEN_QS;
@@ -62,6 +64,7 @@ import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -130,6 +133,7 @@ public class QuickSettingsController {
private final FalsingCollector mFalsingCollector;
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final ShadeLogger mShadeLog;
+ private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final FeatureFlags mFeatureFlags;
private final InteractionJankMonitor mInteractionJankMonitor;
private final FalsingManager mFalsingManager;
@@ -316,7 +320,8 @@ public class QuickSettingsController {
MetricsLogger metricsLogger,
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
- ShadeLogger shadeLog
+ ShadeLogger shadeLog,
+ KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
) {
mPanelViewControllerLazy = panelViewControllerLazy;
mPanelView = panelView;
@@ -355,6 +360,7 @@ public class QuickSettingsController {
mLockscreenGestureLogger = lockscreenGestureLogger;
mMetricsLogger = metricsLogger;
mShadeLog = shadeLog;
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mFeatureFlags = featureFlags;
mInteractionJankMonitor = interactionJankMonitor;
@@ -463,9 +469,17 @@ public class QuickSettingsController {
return (mQs != null ? mQs.getHeader().getHeight() : 0) + mPeekHeight;
}
+ private boolean isRemoteInputActiveWithKeyboardUp() {
+ //TODO(b/227115380) remove the isVisible(ime()) check once isRemoteInputActive is fixed.
+ // The check for keyboard visibility is a temporary workaround that allows QS to expand
+ // even when isRemoteInputActive is mistakenly returning true.
+ return mRemoteInputManager.isRemoteInputActive()
+ && mPanelView.getRootWindowInsets().isVisible(ime());
+ }
+
public boolean isExpansionEnabled() {
return mExpansionEnabledPolicy && mExpansionEnabledAmbient
- && !mRemoteInputManager.isRemoteInputActive();
+ && !isRemoteInputActiveWithKeyboardUp();
}
public float getTransitioningToFullShadeProgress() {
@@ -927,6 +941,7 @@ public class QuickSettingsController {
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+ mKeyguardFaceAuthInteractor.onQsExpansionStared();
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index b79f32a6eae1..b4653bef766d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -43,13 +43,13 @@ import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.qs.ChipVisibilityListener
import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.shade.carrier.ShadeCarrierGroup
+import com.android.systemui.shade.carrier.ShadeCarrierGroupController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusBarLocation
@@ -87,7 +87,7 @@ constructor(
private val variableDateViewControllerFactory: VariableDateViewController.Factory,
@Named(SHADE_HEADER) private val batteryMeterViewController: BatteryMeterViewController,
private val dumpManager: DumpManager,
- private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
+ private val shadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder,
private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager,
private val demoModeController: DemoModeController,
private val qsBatteryModeController: QsBatteryModeController,
@@ -114,13 +114,13 @@ constructor(
private lateinit var iconManager: StatusBarIconController.TintedIconManager
private lateinit var carrierIconSlots: List<String>
- private lateinit var qsCarrierGroupController: QSCarrierGroupController
+ private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
private val clock: Clock = header.findViewById(R.id.clock)
private val date: TextView = header.findViewById(R.id.date)
private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
- private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
+ private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
private var roundedCorners = 0
private var cutout: DisplayCutout? = null
@@ -243,7 +243,7 @@ constructor(
override fun onDensityOrFontScaleChanged() {
clock.setTextAppearance(R.style.TextAppearance_QS_Status)
date.setTextAppearance(R.style.TextAppearance_QS_Status)
- qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+ mShadeCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
loadConstraints()
header.minHeight =
resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_min_height)
@@ -266,8 +266,8 @@ constructor(
carrierIconSlots =
listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
- qsCarrierGroupController =
- qsCarrierGroupControllerBuilder.setQSCarrierGroup(qsCarrierGroup).build()
+ mShadeCarrierGroupController =
+ shadeCarrierGroupControllerBuilder.setShadeCarrierGroup(mShadeCarrierGroup).build()
privacyIconsController.onParentVisible()
}
@@ -284,7 +284,7 @@ constructor(
v.pivotX = newPivot
v.pivotY = v.height.toFloat() / 2
- qsCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
+ mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0)
}
dumpManager.registerDumpable(this)
@@ -439,12 +439,14 @@ constructor(
}
private fun updateListeners() {
- qsCarrierGroupController.setListening(visible)
+ mShadeCarrierGroupController.setListening(visible)
if (visible) {
- updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
- qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
+ updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier)
+ mShadeCarrierGroupController.setOnSingleCarrierChangedListener {
+ updateSingleCarrier(it)
+ }
} else {
- qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
+ mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index da4944c20f6e..a93183865a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -316,4 +316,80 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) {
{ "QSC NotificationsClippingTopBound set to $int1 - $int2" }
)
}
+
+ fun logOnTouchEventLastReturn(
+ event: MotionEvent,
+ dozing: Boolean,
+ handled: Boolean,
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = dozing
+ bool2 = handled
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ double1 = event.y.toDouble()
+ },
+ {
+ "NPVC onTouchEvent last return: !mDozing: $bool1 || handled: $bool2 " +
+ "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+ }
+ )
+ }
+
+ fun logHandleTouchLastReturn(
+ event: MotionEvent,
+ gestureWaitForTouchSlop: Boolean,
+ tracking: Boolean,
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = gestureWaitForTouchSlop
+ bool2 = tracking
+ long1 = event.eventTime
+ long2 = event.downTime
+ int1 = event.action
+ int2 = event.classification
+ double1 = event.y.toDouble()
+ },
+ {
+ "NPVC handleTouch last return: !mGestureWaitForTouchSlop: $bool1 " +
+ "|| mTracking: $bool2 " +
+ "\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
+ }
+ )
+ }
+
+ fun logUpdateNotificationPanelTouchState(
+ disabled: Boolean,
+ isGoingToSleep: Boolean,
+ shouldControlScreenOff: Boolean,
+ deviceInteractive: Boolean,
+ isPulsing: Boolean,
+ isFrpActive: Boolean,
+ ) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ {
+ bool1 = disabled
+ bool2 = isGoingToSleep
+ bool3 = shouldControlScreenOff
+ bool4 = deviceInteractive
+ str1 = isPulsing.toString()
+ str2 = isFrpActive.toString()
+ },
+ {
+ "CentralSurfaces updateNotificationPanelTouchState set disabled to: $bool1\n" +
+ "isGoingToSleep: $bool2, !shouldControlScreenOff: $bool3," +
+ "!mDeviceInteractive: $bool4, !isPulsing: $str1, isFrpActive: $str2"
+ }
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt
index e925b5472c27..958230bbef99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/CellSignalState.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,12 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier
+package com.android.systemui.shade.carrier
/**
* Represents the state of cell signal for a particular slot.
*
- * To be used between [QSCarrierGroupController] and [QSCarrier].
+ * To be used between [ShadeCarrierGroupController] and [ShadeCarrier].
*/
data class CellSignalState(
@JvmField val visible: Boolean = false,
@@ -37,7 +37,6 @@ data class CellSignalState(
* @return `this` if `this.visible == visible`. Else, a new copy with the visibility changed.
*/
fun changeVisibility(visible: Boolean): CellSignalState {
- if (this.visible == visible) return this
- else return copy(visible = visible)
+ if (this.visible == visible) return this else return copy(visible = visible)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java
index b5ceeaed4904..8586828af0cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrier.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
import android.annotation.StyleRes;
import android.content.Context;
@@ -38,7 +38,7 @@ import com.android.systemui.util.LargeScreenUtils;
import java.util.Objects;
-public class QSCarrier extends LinearLayout {
+public class ShadeCarrier extends LinearLayout {
private View mMobileGroup;
private TextView mCarrierText;
@@ -50,19 +50,19 @@ public class QSCarrier extends LinearLayout {
private boolean mMobileSignalInitialized = false;
private boolean mIsSingleCarrier;
- public QSCarrier(Context context) {
+ public ShadeCarrier(Context context) {
super(context);
}
- public QSCarrier(Context context, AttributeSet attrs) {
+ public ShadeCarrier(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ShadeCarrier(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public QSCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public ShadeCarrier(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -72,7 +72,7 @@ public class QSCarrier extends LinearLayout {
mMobileGroup = findViewById(R.id.mobile_combo);
mMobileRoaming = findViewById(R.id.mobile_roaming);
mMobileSignal = findViewById(R.id.mobile_signal);
- mCarrierText = findViewById(R.id.qs_carrier_text);
+ mCarrierText = findViewById(R.id.shade_carrier_text);
mSpacer = findViewById(R.id.spacer);
updateResources();
}
@@ -158,7 +158,7 @@ public class QSCarrier extends LinearLayout {
mCarrierText.setMaxEms(
useLargeScreenHeader
? Integer.MAX_VALUE
- : getResources().getInteger(R.integer.qs_carrier_max_em)
+ : getResources().getInteger(R.integer.shade_carrier_max_em)
);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java
index a36035b99b4f..68561d1cfd0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroup.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroup.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
import android.annotation.StyleRes;
import android.content.Context;
@@ -27,10 +27,10 @@ import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
/**
- * Displays Carrier name and network status in QS
+ * Displays Carrier name and network status in the shade header
*/
-public class QSCarrierGroup extends LinearLayout {
- public QSCarrierGroup(Context context, AttributeSet attrs) {
+public class ShadeCarrierGroup extends LinearLayout {
+ public ShadeCarrierGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -38,24 +38,24 @@ public class QSCarrierGroup extends LinearLayout {
return findViewById(R.id.no_carrier_text);
}
- QSCarrier getCarrier1View() {
+ ShadeCarrier getCarrier1View() {
return findViewById(R.id.carrier1);
}
- QSCarrier getCarrier2View() {
+ ShadeCarrier getCarrier2View() {
return findViewById(R.id.carrier2);
}
- QSCarrier getCarrier3View() {
+ ShadeCarrier getCarrier3View() {
return findViewById(R.id.carrier3);
}
View getCarrierDivider1() {
- return findViewById(R.id.qs_carrier_divider1);
+ return findViewById(R.id.shade_carrier_divider1);
}
View getCarrierDivider2() {
- return findViewById(R.id.qs_carrier_divider2);
+ return findViewById(R.id.shade_carrier_divider2);
}
public void updateTextAppearance(@StyleRes int resId) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 6a8bf759a849..0ebcfa2a1876 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
@@ -52,8 +52,8 @@ import java.util.function.Consumer;
import javax.inject.Inject;
-public class QSCarrierGroupController {
- private static final String TAG = "QSCarrierGroup";
+public class ShadeCarrierGroupController {
+ private static final String TAG = "ShadeCarrierGroup";
/**
* Support up to 3 slots which is what's supported by {@link TelephonyManager#getPhoneCount}
@@ -72,7 +72,7 @@ public class QSCarrierGroupController {
private final CellSignalState[] mInfos =
new CellSignalState[SIM_SLOTS];
private View[] mCarrierDividers = new View[SIM_SLOTS - 1];
- private QSCarrier[] mCarrierGroups = new QSCarrier[SIM_SLOTS];
+ private ShadeCarrier[] mCarrierGroups = new ShadeCarrier[SIM_SLOTS];
private int[] mLastSignalLevel = new int[SIM_SLOTS];
private String[] mLastSignalLevelDescription = new String[SIM_SLOTS];
private final CarrierConfigTracker mCarrierConfigTracker;
@@ -129,7 +129,7 @@ public class QSCarrierGroupController {
}
}
- private QSCarrierGroupController(QSCarrierGroup view, ActivityStarter activityStarter,
+ private ShadeCarrierGroupController(ShadeCarrierGroup view, ActivityStarter activityStarter,
@Background Handler bgHandler, @Main Looper mainLooper,
NetworkController networkController,
CarrierTextManager.Builder carrierTextManagerBuilder, Context context,
@@ -167,7 +167,7 @@ public class QSCarrierGroupController {
for (int i = 0; i < SIM_SLOTS; i++) {
mInfos[i] = new CellSignalState(
true,
- R.drawable.ic_qs_no_calling_sms,
+ R.drawable.ic_shade_no_calling_sms,
context.getText(AccessibilityContentDescriptions.NO_CALLING).toString(),
"",
false);
@@ -257,7 +257,7 @@ public class QSCarrierGroupController {
if (singleCarrier) {
for (int i = 0; i < SIM_SLOTS; i++) {
if (mInfos[i].visible
- && mInfos[i].mobileSignalIconId == R.drawable.ic_qs_sim_card) {
+ && mInfos[i].mobileSignalIconId == R.drawable.ic_shade_sim_card) {
mInfos[i] = new CellSignalState(true, R.drawable.ic_blank, "", "", false);
}
}
@@ -322,8 +322,8 @@ public class QSCarrierGroupController {
Log.e(TAG, "Carrier information arrays not of same length");
}
} else {
- // No sims or airplane mode (but not WFC). Do not show QSCarrierGroup, instead just show
- // info.carrierText in a different view.
+ // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup,
+ // instead just show info.carrierText in a different view.
for (int i = 0; i < SIM_SLOTS; i++) {
mInfos[i] = mInfos[i].changeVisibility(false);
mCarrierGroups[i].setCarrierText("");
@@ -368,7 +368,7 @@ public class QSCarrierGroupController {
}
public static class Builder {
- private QSCarrierGroup mView;
+ private ShadeCarrierGroup mView;
private final ActivityStarter mActivityStarter;
private final Handler mHandler;
private final Looper mLooper;
@@ -393,13 +393,13 @@ public class QSCarrierGroupController {
mSlotIndexResolver = slotIndexResolver;
}
- public Builder setQSCarrierGroup(QSCarrierGroup view) {
+ public Builder setShadeCarrierGroup(ShadeCarrierGroup view) {
mView = view;
return this;
}
- public QSCarrierGroupController build() {
- return new QSCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
+ public ShadeCarrierGroupController build() {
+ return new ShadeCarrierGroupController(mView, mActivityStarter, mHandler, mLooper,
mNetworkController, mCarrierTextControllerBuilder, mContext,
mCarrierConfigTracker, mSlotIndexResolver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index c84894fc81ee..06f43f1eeaa5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -841,6 +841,19 @@ public final class KeyboardShortcutListSearch {
BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
behavior.setSkipCollapsed(true);
+ behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet, int newState) {
+ if (newState == BottomSheetBehavior.STATE_DRAGGING) {
+ behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+ // Do nothing.
+ }
+ });
mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true);
Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 765c93ed209b..142689e88b51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -1127,13 +1127,7 @@ public class KeyguardIndicationController {
final boolean faceAuthUnavailable = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
- // TODO(b/141025588): refactor to reduce repetition of code/comments
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed
- if (!mKeyguardUpdateMonitor
- .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
+ if (isPrimaryAuthRequired()
&& !faceAuthUnavailable) {
return;
}
@@ -1234,7 +1228,7 @@ public class KeyguardIndicationController {
private void onFaceAuthError(int msgId, String errString) {
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
- if (shouldSuppressFaceError(msgId, mKeyguardUpdateMonitor)) {
+ if (shouldSuppressFaceError(msgId)) {
mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
return;
}
@@ -1248,7 +1242,7 @@ public class KeyguardIndicationController {
}
private void onFingerprintAuthError(int msgId, String errString) {
- if (shouldSuppressFingerprintError(msgId, mKeyguardUpdateMonitor)) {
+ if (shouldSuppressFingerprintError(msgId)) {
mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
msgId,
errString);
@@ -1257,31 +1251,19 @@ public class KeyguardIndicationController {
}
}
- private boolean shouldSuppressFingerprintError(int msgId,
- KeyguardUpdateMonitor updateMonitor) {
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed
- return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- && !isLockoutError(msgId))
+ private boolean shouldSuppressFingerprintError(int msgId) {
+ return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
}
- private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
- // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
- // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
- // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
- // check of whether non-strong biometric is allowed
- return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
- && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
+ private boolean shouldSuppressFaceError(int msgId) {
+ return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
|| msgId == FaceManager.FACE_ERROR_CANCELED
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
}
-
@Override
public void onTrustChanged(int userId) {
if (!isCurrentUser(userId)) return;
@@ -1355,6 +1337,16 @@ public class KeyguardIndicationController {
}
}
+ private boolean isPrimaryAuthRequired() {
+ // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
+ // as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
+ // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
+ // check of whether non-strong biometric is allowed since strong biometrics can still be
+ // used.
+ return !mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ true /* isStrongBiometric */);
+ }
+
protected boolean isPluggedInAndCharging() {
return mPowerPluggedIn;
}
@@ -1431,7 +1423,7 @@ public class KeyguardIndicationController {
private boolean canUnlockWithFingerprint() {
return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser());
+ getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed();
}
private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
index cb4ae286d5c3..f7d37e6b1058 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LegacyNotificationShelfControllerImpl.java
@@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.row.ActivatableNotificationVi
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
@@ -35,7 +34,7 @@ import javax.inject.Inject;
* Controller class for {@link NotificationShelf}.
*/
@NotificationRowScope
-public class NotificationShelfController {
+public class LegacyNotificationShelfControllerImpl implements NotificationShelfController {
private final NotificationShelf mView;
private final ActivatableNotificationViewController mActivatableNotificationViewController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -44,7 +43,7 @@ public class NotificationShelfController {
private AmbientState mAmbientState;
@Inject
- public NotificationShelfController(
+ public LegacyNotificationShelfControllerImpl(
NotificationShelf notificationShelf,
ActivatableNotificationViewController activatableNotificationViewController,
KeyguardBypassController keyguardBypassController,
@@ -79,56 +78,42 @@ public class NotificationShelfController {
}
}
+ @Override
public NotificationShelf getView() {
return mView;
}
+ @Override
public boolean canModifyColorOfNotifications() {
return mAmbientState.isShadeExpanded()
&& !(mAmbientState.isOnKeyguard() && mKeyguardBypassController.getBypassEnabled());
}
+ @Override
public NotificationIconContainer getShelfIcons() {
return mView.getShelfIcons();
}
- public @View.Visibility int getVisibility() {
- return mView.getVisibility();
- }
-
- public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
- mView.setCollapsedIcons(notificationIcons);
- }
-
+ @Override
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
mView.bind(ambientState, notificationStackScrollLayoutController);
mAmbientState = ambientState;
}
- public int getHeight() {
- return mView.getHeight();
- }
-
- public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
- AmbientState ambientState) {
- mAmbientState = ambientState;
- mView.updateState(algorithmState, ambientState);
- }
-
+ @Override
public int getIntrinsicHeight() {
return mView.getIntrinsicHeight();
}
+ @Override
public void setOnActivatedListener(ActivatableNotificationView.OnActivatedListener listener) {
mView.setOnActivatedListener(listener);
}
+ @Override
public void setOnClickListener(View.OnClickListener onClickListener) {
mView.setOnClickListener(onClickListener);
}
- public int getNotGoneIndex() {
- return mView.getNotGoneIndex();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 4873c9dae89a..7eb63da38028 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.IndentingPrintWriter;
+import android.util.Log;
import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
@@ -52,6 +53,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.notification.stack.ViewState;
@@ -64,8 +67,7 @@ import java.io.PrintWriter;
* A notification shelf view that is placed inside the notification scroller. It manages the
* overflow icons that don't fit into the regular list anymore.
*/
-public class NotificationShelf extends ActivatableNotificationView implements
- View.OnLayoutChangeListener, StateListener {
+public class NotificationShelf extends ActivatableNotificationView implements StateListener {
private static final int TAG_CONTINUOUS_CLIPPING = R.id.continuous_clipping_tag;
private static final String TAG = "NotificationShelf";
@@ -78,7 +80,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
private NotificationIconContainer mShelfIcons;
- private int[] mTmp = new int[2];
private boolean mHideBackground;
private int mStatusBarHeight;
private boolean mEnableNotificationClipping;
@@ -87,7 +88,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
private int mPaddingBetweenElements;
private int mNotGoneIndex;
private boolean mHasItemsInStableShelf;
- private NotificationIconContainer mCollapsedIcons;
private int mScrollFastThreshold;
private int mStatusBarState;
private boolean mInteractive;
@@ -99,6 +99,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
private NotificationShelfController mController;
private float mActualWidth = -1;
private boolean mSensitiveRevealAnimEndabled;
+ private boolean mShelfRefactorFlagEnabled;
+ private boolean mCanModifyColorOfNotifications;
+ private boolean mCanInteract;
+ private NotificationStackScrollLayout mHostLayout;
+ private NotificationRoundnessManager mRoundnessManager;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -135,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
public void bind(AmbientState ambientState,
NotificationStackScrollLayoutController hostLayoutController) {
+ assertRefactorFlagDisabled();
mAmbientState = ambientState;
mHostLayoutController = hostLayoutController;
hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
@@ -142,6 +148,14 @@ public class NotificationShelf extends ActivatableNotificationView implements
});
}
+ public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout,
+ NotificationRoundnessManager roundnessManager) {
+ if (!checkRefactorFlagEnabled()) return;
+ mAmbientState = ambientState;
+ mHostLayout = hostLayout;
+ mRoundnessManager = roundnessManager;
+ }
+
private void updateResources() {
Resources res = getResources();
mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
@@ -233,7 +247,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
} else {
viewState.setAlpha(1f - ambientState.getHideAmount());
}
- viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0;
+ viewState.belowSpeedBump = getSpeedBumpIndex() == 0;
viewState.hideSensitive = false;
viewState.setXTranslation(getTranslationX());
viewState.hasItemsInStableShelf = lastViewState.inShelf;
@@ -276,6 +290,14 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
+ private int getSpeedBumpIndex() {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getSpeedBumpIndex();
+ } else {
+ return mHostLayoutController.getSpeedBumpIndex();
+ }
+ }
+
/**
* @param fractionToShade Fraction of lockscreen to shade transition
* @param shortestWidth Shortest width to use for lockscreen shelf
@@ -388,8 +410,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
int baseZHeight = mAmbientState.getBaseZHeight();
int clipTopAmount = 0;
- for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
- ExpandableView child = mHostLayoutController.getChildAt(i);
+ for (int i = 0; i < getHostLayoutChildCount(); i++) {
+ ExpandableView child = getHostLayoutChildAt(i);
if (!child.needsClippingToShelf() || child.getVisibility() == GONE) {
continue;
}
@@ -428,7 +450,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
transitionAmount = inShelfAmount;
}
// We don't want to modify the color if the notification is hun'd
- if (isLastChild && mController.canModifyColorOfNotifications()) {
+ if (isLastChild && canModifyColorOfNotifications()) {
if (colorOfViewBeforeLast == NO_COLOR) {
colorOfViewBeforeLast = ownColorUntinted;
}
@@ -474,11 +496,11 @@ public class NotificationShelf extends ActivatableNotificationView implements
// TODO(b/172289889) transition last icon in shelf to notification icon and vice versa.
setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE);
- mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex());
+ mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex());
mShelfIcons.calculateIconXTranslations();
mShelfIcons.applyIconStates();
- for (int i = 0; i < mHostLayoutController.getChildCount(); i++) {
- View child = mHostLayoutController.getChildAt(i);
+ for (int i = 0; i < getHostLayoutChildCount(); i++) {
+ View child = getHostLayoutChildAt(i);
if (!(child instanceof ExpandableNotificationRow)
|| child.getVisibility() == GONE) {
continue;
@@ -493,6 +515,30 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
+ private ExpandableView getHostLayoutChildAt(int index) {
+ if (mShelfRefactorFlagEnabled) {
+ return (ExpandableView) mHostLayout.getChildAt(index);
+ } else {
+ return mHostLayoutController.getChildAt(index);
+ }
+ }
+
+ private int getHostLayoutChildCount() {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getChildCount();
+ } else {
+ return mHostLayoutController.getChildCount();
+ }
+ }
+
+ private boolean canModifyColorOfNotifications() {
+ if (mShelfRefactorFlagEnabled) {
+ return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded();
+ } else {
+ return mController.canModifyColorOfNotifications();
+ }
+ }
+
private void updateCornerRoundnessOnScroll(
ActivatableNotificationView anv,
float viewStart,
@@ -507,7 +553,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
&& anv == mAmbientState.getTrackedHeadsUpRow();
final boolean shouldUpdateCornerRoundness = viewStart < shelfStart
- && !mHostLayoutController.isViewAffectedBySwipe(anv)
+ && !isViewAffectedBySwipe(anv)
&& !isUnlockedHeadsUp
&& !isHunGoingToShade
&& !anv.isAboveShelf()
@@ -559,6 +605,14 @@ public class NotificationShelf extends ActivatableNotificationView implements
anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
}
+ private boolean isViewAffectedBySwipe(ExpandableView expandableView) {
+ if (!mShelfRefactorFlagEnabled) {
+ return mHostLayoutController.isViewAffectedBySwipe(expandableView);
+ } else {
+ return mRoundnessManager.isViewAffectedBySwipe(expandableView);
+ }
+ }
+
/**
* Clips transient views to the top of the shelf - Transient views are only used for
* disappearing views/animations and need to be clipped correctly by the shelf to ensure they
@@ -566,8 +620,8 @@ public class NotificationShelf extends ActivatableNotificationView implements
* swipes quickly.
*/
private void clipTransientViews() {
- for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) {
- View transientView = mHostLayoutController.getTransientView(i);
+ for (int i = 0; i < getHostLayoutTransientViewCount(); i++) {
+ View transientView = getHostLayoutTransientView(i);
if (transientView instanceof ExpandableView) {
ExpandableView transientExpandableView = (ExpandableView) transientView;
updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1);
@@ -575,6 +629,22 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
+ private View getHostLayoutTransientView(int index) {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getTransientView(index);
+ } else {
+ return mHostLayoutController.getTransientView(index);
+ }
+ }
+
+ private int getHostLayoutTransientViewCount() {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.getTransientViewCount();
+ } else {
+ return mHostLayoutController.getTransientViewCount();
+ }
+ }
+
private void updateIconClipAmount(ExpandableNotificationRow row) {
float maxTop = row.getTranslationY();
if (getClipTopAmount() != 0) {
@@ -868,10 +938,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
return mShelfIcons.getIconState(icon);
}
- private float getFullyClosedTranslation() {
- return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
- }
-
@Override
public boolean hasNoContentHeight() {
return true;
@@ -893,7 +959,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- updateRelativeOffset();
// we always want to clip to our sides, such that nothing can draw outside of these bounds
int height = getResources().getDisplayMetrics().heightPixels;
@@ -903,13 +968,6 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
- private void updateRelativeOffset() {
- if (mCollapsedIcons != null) {
- mCollapsedIcons.getLocationOnScreen(mTmp);
- }
- getLocationOnScreen(mTmp);
- }
-
/**
* @return the index of the notification at which the shelf visually resides
*/
@@ -924,33 +982,29 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
}
- /**
- * @return whether the shelf has any icons in it when a potential animation has finished, i.e
- * if the current state would be applied right now
- */
- public boolean hasItemsInStableShelf() {
- return mHasItemsInStableShelf;
- }
-
- public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
- mCollapsedIcons = collapsedIcons;
- mCollapsedIcons.addOnLayoutChangeListener(this);
- }
-
@Override
public void onStateChanged(int newState) {
+ assertRefactorFlagDisabled();
mStatusBarState = newState;
updateInteractiveness();
}
private void updateInteractiveness() {
- mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf;
+ mInteractive = canInteract() && mHasItemsInStableShelf;
setClickable(mInteractive);
setFocusable(mInteractive);
setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES
: View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
+ private boolean canInteract() {
+ if (mShelfRefactorFlagEnabled) {
+ return mCanInteract;
+ } else {
+ return mStatusBarState == StatusBarState.KEYGUARD;
+ }
+ }
+
@Override
protected boolean isInteractive() {
return mInteractive;
@@ -983,22 +1037,50 @@ public class NotificationShelf extends ActivatableNotificationView implements
}
@Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- updateRelativeOffset();
- }
-
- @Override
public boolean needsClippingToShelf() {
return false;
}
+ private void assertRefactorFlagDisabled() {
+ if (mShelfRefactorFlagEnabled) {
+ NotificationShelfController.throwIllegalFlagStateError(false);
+ }
+ }
+
+ private boolean checkRefactorFlagEnabled() {
+ if (!mShelfRefactorFlagEnabled) {
+ Log.wtf(TAG,
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled.");
+ }
+ return mShelfRefactorFlagEnabled;
+ }
+
public void setController(NotificationShelfController notificationShelfController) {
+ assertRefactorFlagDisabled();
mController = notificationShelfController;
}
+ public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) {
+ if (!checkRefactorFlagEnabled()) return;
+ mCanModifyColorOfNotifications = canModifyColorOfNotifications;
+ }
+
+ public void setCanInteract(boolean canInteract) {
+ if (!checkRefactorFlagEnabled()) return;
+ mCanInteract = canInteract;
+ updateInteractiveness();
+ }
+
public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) {
- mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
+ mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf);
+ }
+
+ private int getIndexOfViewInHostLayout(ExpandableView child) {
+ if (mShelfRefactorFlagEnabled) {
+ return mHostLayout.indexOfChild(child);
+ } else {
+ return mHostLayoutController.indexOfChild(child);
+ }
}
/**
@@ -1009,6 +1091,15 @@ public class NotificationShelf extends ActivatableNotificationView implements
mSensitiveRevealAnimEndabled = enabled;
}
+ public void setRefactorFlagEnabled(boolean enabled) {
+ mShelfRefactorFlagEnabled = enabled;
+ }
+
+ public void requestRoundnessResetFor(ExpandableView child) {
+ if (!checkRefactorFlagEnabled()) return;
+ child.requestRoundnessReset(SHELF_SCROLL);
+ }
+
/**
* This method resets the OnScroll roundness of a view to 0f
* <p>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt
new file mode 100644
index 000000000000..07cfd0d8019c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.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.statusbar
+
+import android.util.Log
+import android.view.View
+import android.view.View.OnClickListener
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+
+/** Controller interface for [NotificationShelf]. */
+interface NotificationShelfController {
+ /** The [NotificationShelf] controlled by this Controller. */
+ val view: NotificationShelf
+
+ /** @see ExpandableView.getIntrinsicHeight */
+ val intrinsicHeight: Int
+
+ /** Container view for icons displayed in the shelf. */
+ val shelfIcons: NotificationIconContainer
+
+ /** Whether or not the shelf can modify the color of notifications in the shade. */
+ fun canModifyColorOfNotifications(): Boolean
+
+ /** @see ActivatableNotificationView.setOnActivatedListener */
+ fun setOnActivatedListener(listener: OnActivatedListener)
+
+ /** Binds the shelf to the host [NotificationStackScrollLayout], via its Controller. */
+ fun bind(
+ ambientState: AmbientState,
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ )
+
+ /** @see View.setOnClickListener */
+ fun setOnClickListener(listener: OnClickListener)
+
+ companion object {
+ @JvmStatic
+ fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) {
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ throwIllegalFlagStateError(expected = false)
+ }
+ }
+
+ @JvmStatic
+ fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean =
+ featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled ->
+ if (!enabled) {
+ Log.wtf("NotifShelf", getErrorMessage(expected = true))
+ }
+ }
+
+ @JvmStatic
+ fun throwIllegalFlagStateError(expected: Boolean): Nothing =
+ error(getErrorMessage(expected))
+
+ private fun getErrorMessage(expected: Boolean): String =
+ "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " +
+ if (expected) "disabled" else "enabled"
+ }
+}
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 565c0a9426ab..34300c731343 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -38,8 +38,8 @@ import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteracto
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
import com.android.systemui.statusbar.ActionClickLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.MediaArtworkProcessor;
@@ -78,14 +78,14 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
/**
* This module provides instances needed to construct {@link CentralSurfacesImpl}. These are moved to
* this separate from {@link CentralSurfacesModule} module so that components that wish to build
@@ -271,8 +271,8 @@ public interface CentralSurfacesDependenciesModule {
/** */
@Binds
- QSCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
- QSCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
+ ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
+ ShadeCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
/**
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index ecd0c41ff82d..fcff4376811e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -48,6 +48,10 @@ public abstract class AnimatableProperty {
View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag,
R.id.scale_y_animator_end_value_tag);
+ public static final AnimatableProperty ALPHA = AnimatableProperty.from(
+ View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag,
+ R.id.alpha_animator_end_value_tag);
+
/**
* Similar to X, however this doesn't allow for any other modifications other than from this
* property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
index 15ad312b413e..1631ae28bf5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.dagger.PeopleHeader
import com.android.systemui.statusbar.notification.icon.ConversationIconManager
@@ -40,27 +41,28 @@ import javax.inject.Inject
*/
@CoordinatorScope
class ConversationCoordinator @Inject constructor(
- private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
- private val conversationIconManager: ConversationIconManager,
- @PeopleHeader peopleHeaderController: NodeController
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ private val conversationIconManager: ConversationIconManager,
+ private val highPriorityProvider: HighPriorityProvider,
+ @PeopleHeader private val peopleHeaderController: NodeController,
) : Coordinator {
private val promotedEntriesToSummaryOfSameChannel =
- mutableMapOf<NotificationEntry, NotificationEntry>()
+ mutableMapOf<NotificationEntry, NotificationEntry>()
private val onBeforeRenderListListener = OnBeforeRenderListListener { _ ->
val unimportantSummaries = promotedEntriesToSummaryOfSameChannel
- .mapNotNull { (promoted, summary) ->
- val originalGroup = summary.parent
- when {
- originalGroup == null -> null
- originalGroup == promoted.parent -> null
- originalGroup.parent == null -> null
- originalGroup.summary != summary -> null
- originalGroup.children.any { it.channel == summary.channel } -> null
- else -> summary.key
+ .mapNotNull { (promoted, summary) ->
+ val originalGroup = summary.parent
+ when {
+ originalGroup == null -> null
+ originalGroup == promoted.parent -> null
+ originalGroup.parent == null -> null
+ originalGroup.summary != summary -> null
+ originalGroup.children.any { it.channel == summary.channel } -> null
+ else -> summary.key
+ }
}
- }
conversationIconManager.setUnimportantConversations(unimportantSummaries)
promotedEntriesToSummaryOfSameChannel.clear()
}
@@ -78,21 +80,23 @@ class ConversationCoordinator @Inject constructor(
}
}
- val sectioner = object : NotifSectioner("People", BUCKET_PEOPLE) {
+ val peopleAlertingSectioner = object : NotifSectioner("People(alerting)", BUCKET_PEOPLE) {
override fun isInSection(entry: ListEntry): Boolean =
- isConversation(entry)
+ highPriorityProvider.isHighPriorityConversation(entry)
- override fun getComparator() = object : NotifComparator("People") {
- override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
- val type1 = getPeopleType(entry1)
- val type2 = getPeopleType(entry2)
- return type2.compareTo(type1)
- }
- }
+ override fun getComparator(): NotifComparator = notifComparator
- override fun getHeaderNodeController() =
- // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
- if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
+ override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
+ }
+
+ val peopleSilentSectioner = object : NotifSectioner("People(silent)", BUCKET_PEOPLE) {
+ // Because the peopleAlertingSectioner is above this one, it will claim all conversations that are alerting.
+ // All remaining conversations must be silent.
+ override fun isInSection(entry: ListEntry): Boolean = isConversation(entry)
+
+ override fun getComparator(): NotifComparator = notifComparator
+
+ override fun getHeaderNodeController(): NodeController? = conversationHeaderNodeController
}
override fun attach(pipeline: NotifPipeline) {
@@ -101,15 +105,27 @@ class ConversationCoordinator @Inject constructor(
}
private fun isConversation(entry: ListEntry): Boolean =
- getPeopleType(entry) != TYPE_NON_PERSON
+ getPeopleType(entry) != TYPE_NON_PERSON
@PeopleNotificationType
private fun getPeopleType(entry: ListEntry): Int =
- entry.representativeEntry?.let {
- peopleNotificationIdentifier.getPeopleNotificationType(it)
- } ?: TYPE_NON_PERSON
+ entry.representativeEntry?.let {
+ peopleNotificationIdentifier.getPeopleNotificationType(it)
+ } ?: TYPE_NON_PERSON
+
+ private val notifComparator: NotifComparator = object : NotifComparator("People") {
+ override fun compare(entry1: ListEntry, entry2: ListEntry): Int {
+ val type1 = getPeopleType(entry1)
+ val type2 = getPeopleType(entry2)
+ return type2.compareTo(type1)
+ }
+ }
+
+ // TODO: remove SHOW_ALL_SECTIONS, this redundant method, and peopleHeaderController
+ private val conversationHeaderNodeController: NodeController? =
+ if (RankingCoordinator.SHOW_ALL_SECTIONS) peopleHeaderController else null
- companion object {
+ private companion object {
private const val TAG = "ConversationCoordinator"
}
}
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 82bd45ce2279..6322edf5a1b4 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
@@ -24,6 +24,10 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+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.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
@@ -40,22 +44,26 @@ import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.headsUpEvents
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import javax.inject.Inject
-import kotlin.time.Duration
-import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
+import kotlinx.coroutines.yield
+import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
/**
* Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
@@ -69,6 +77,7 @@ constructor(
private val headsUpManager: HeadsUpManager,
private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider,
private val keyguardRepository: KeyguardRepository,
+ private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val notifPipelineFlags: NotifPipelineFlags,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
@@ -99,21 +108,46 @@ constructor(
}
private suspend fun trackUnseenNotificationsWhileUnlocked() {
+ // Whether or not we're actively tracking unseen notifications to mark them as seen when
+ // appropriate.
+ val isTrackingUnseen: Flow<Boolean> =
+ keyguardRepository.isKeyguardShowing
+ // transformLatest so that we can cancel listening to keyguard transitions once
+ // isKeyguardShowing changes (after a successful transition to the keyguard).
+ .transformLatest { isShowing ->
+ if (isShowing) {
+ // If the keyguard is showing, we're not tracking unseen.
+ emit(false)
+ } else {
+ // If the keyguard stops showing, then start tracking unseen notifications.
+ emit(true)
+ // If the screen is turning off, stop tracking, but if that transition is
+ // cancelled, then start again.
+ emitAll(
+ keyguardTransitionRepository.transitions
+ .map { step -> !step.isScreenTurningOff }
+ )
+ }
+ }
+ // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
+ // showing
+ .distinctUntilChanged()
+
// Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
// showing again
- var clearUnseenOnUnlock = false
- keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing ->
- if (isKeyguardShowing) {
+ var clearUnseenOnBeginTracking = false
+ isTrackingUnseen.collectLatest { trackingUnseen ->
+ if (!trackingUnseen) {
// Wait for the user to spend enough time on the lock screen before clearing unseen
// set when unlocked
awaitTimeSpentNotDozing(SEEN_TIMEOUT)
- clearUnseenOnUnlock = true
+ clearUnseenOnBeginTracking = true
} else {
- unseenNotifFilter.invalidateList("keyguard no longer showing")
- if (clearUnseenOnUnlock) {
- clearUnseenOnUnlock = false
+ if (clearUnseenOnBeginTracking) {
+ clearUnseenOnBeginTracking = false
unseenNotifications.clear()
}
+ unseenNotifFilter.invalidateList("keyguard no longer showing")
trackUnseenNotifications()
}
}
@@ -142,7 +176,10 @@ constructor(
}
private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() {
- statusBarStateController.expansionChanges.collect { isExpanded ->
+ statusBarStateController.expansionChanges.collectLatest { isExpanded ->
+ // Give keyguard events time to propagate, in case this expansion is part of the
+ // keyguard transition and not the user expanding the shade
+ yield()
if (isExpanded) {
unseenNotifications.clear()
}
@@ -276,3 +313,6 @@ constructor(
private val SEEN_TIMEOUT = 5.seconds
}
}
+
+private val TransitionStep.isScreenTurningOff: Boolean get() =
+ transitionState == TransitionState.STARTED && to != KeyguardState.GONE \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 6bb5b9218ed7..02ce0d46ead8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -21,6 +21,7 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import javax.inject.Inject
/**
@@ -32,6 +33,7 @@ interface NotifCoordinators : Coordinator, PipelineDumpable
@CoordinatorScope
class NotifCoordinatorsImpl @Inject constructor(
notifPipelineFlags: NotifPipelineFlags,
+ sectionStyleProvider: SectionStyleProvider,
dataStoreCoordinator: DataStoreCoordinator,
hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
@@ -56,7 +58,7 @@ class NotifCoordinatorsImpl @Inject constructor(
viewConfigCoordinator: ViewConfigCoordinator,
visualStabilityCoordinator: VisualStabilityCoordinator,
sensitiveContentCoordinator: SensitiveContentCoordinator,
- dismissibilityCoordinator: DismissibilityCoordinator
+ dismissibilityCoordinator: DismissibilityCoordinator,
) : NotifCoordinators {
private val mCoordinators: MutableList<Coordinator> = ArrayList()
@@ -99,13 +101,20 @@ class NotifCoordinatorsImpl @Inject constructor(
mCoordinators.add(dismissibilityCoordinator)
// Manually add Ordered Sections
- // HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
- mOrderedSections.add(headsUpCoordinator.sectioner)
+ mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
- mOrderedSections.add(conversationCoordinator.sectioner) // People
+ mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
+ mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
mOrderedSections.add(rankingCoordinator.silentSectioner) // Silent
mOrderedSections.add(rankingCoordinator.minimizedSectioner) // Minimized
+
+ sectionStyleProvider.setMinimizedSections(setOf(rankingCoordinator.minimizedSectioner))
+ sectionStyleProvider.setSilentSections(listOf(
+ conversationCoordinator.peopleSilentSectioner,
+ rankingCoordinator.silentSectioner,
+ rankingCoordinator.minimizedSectioner,
+ ))
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index ea5cb308a2d0..1d37dcf13037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -27,15 +27,12 @@ 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.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
@@ -52,7 +49,6 @@ public class RankingCoordinator implements Coordinator {
public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
- private final SectionStyleProvider mSectionStyleProvider;
private final NodeController mSilentNodeController;
private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@@ -63,13 +59,11 @@ public class RankingCoordinator implements Coordinator {
public RankingCoordinator(
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
- SectionStyleProvider sectionStyleProvider,
@AlertingHeader NodeController alertingHeaderController,
@SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
- mSectionStyleProvider = sectionStyleProvider;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
@@ -78,9 +72,6 @@ public class RankingCoordinator implements Coordinator {
@Override
public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
- mSectionStyleProvider.setSilentSections(
- Arrays.asList(mSilentNotifSectioner, mMinimizedNotifSectioner));
pipeline.addPreGroupFilter(mSuspendedFilter);
pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
index e7ef2ec084b7..731ec80817ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.notification.collection.provider;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -63,7 +66,7 @@ public class HighPriorityProvider {
* A GroupEntry is considered high priority if its representativeEntry (summary) or children are
* high priority
*/
- public boolean isHighPriority(ListEntry entry) {
+ public boolean isHighPriority(@Nullable ListEntry entry) {
if (entry == null) {
return false;
}
@@ -78,6 +81,36 @@ public class HighPriorityProvider {
|| hasHighPriorityChild(entry);
}
+ /**
+ * @return true if the ListEntry is high priority conversation, else false
+ */
+ public boolean isHighPriorityConversation(@NonNull ListEntry entry) {
+ final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ if (notifEntry == null) {
+ return false;
+ }
+
+ if (!isPeopleNotification(notifEntry)) {
+ return false;
+ }
+
+ if (notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT) {
+ return true;
+ }
+
+ return isNotificationEntryWithAtLeastOneImportantChild(entry);
+ }
+
+ private boolean isNotificationEntryWithAtLeastOneImportantChild(@NonNull ListEntry entry) {
+ if (!(entry instanceof GroupEntry)) {
+ return false;
+ }
+ final GroupEntry groupEntry = (GroupEntry) entry;
+ return groupEntry.getChildren().stream().anyMatch(
+ childEntry ->
+ childEntry.getRanking().getImportance()
+ >= NotificationManager.IMPORTANCE_DEFAULT);
+ }
private boolean hasHighPriorityChild(ListEntry entry) {
if (entry instanceof NotificationEntry
@@ -93,7 +126,6 @@ public class HighPriorityProvider {
}
}
}
-
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.kt
new file mode 100644
index 000000000000..f2216fce6fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapper.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.statusbar.notification.interruption
+
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+
+/**
+ * Wraps a [NotificationInterruptStateProvider] to convert it to the new
+ * [VisualInterruptionDecisionProvider] interface.
+ */
+@SysUISingleton
+class NotificationInterruptStateProviderWrapper(
+ private val wrapped: NotificationInterruptStateProvider
+) : VisualInterruptionDecisionProvider {
+
+ @VisibleForTesting
+ enum class DecisionImpl(override val shouldInterrupt: Boolean) : Decision {
+ SHOULD_INTERRUPT(shouldInterrupt = true),
+ SHOULD_NOT_INTERRUPT(shouldInterrupt = false);
+
+ companion object {
+ fun of(booleanDecision: Boolean) =
+ if (booleanDecision) SHOULD_INTERRUPT else SHOULD_NOT_INTERRUPT
+ }
+ }
+
+ @VisibleForTesting
+ class FullScreenIntentDecisionImpl(
+ val originalEntry: NotificationEntry,
+ val originalDecision: NotificationInterruptStateProvider.FullScreenIntentDecision
+ ) : FullScreenIntentDecision {
+ override val shouldInterrupt = originalDecision.shouldLaunch
+ override val wouldInterruptWithoutDnd = originalDecision == NO_FSI_SUPPRESSED_ONLY_BY_DND
+ }
+
+ override fun addSuppressor(suppressor: NotificationInterruptSuppressor) {
+ wrapped.addSuppressor(suppressor)
+ }
+
+ override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision =
+ wrapped.checkHeadsUp(entry, /* log= */ false).let { DecisionImpl.of(it) }
+
+ override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision =
+ wrapped.checkHeadsUp(entry, /* log= */ true).let { DecisionImpl.of(it) }
+
+ override fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry) =
+ wrapped.getFullScreenIntentDecision(entry).let { FullScreenIntentDecisionImpl(entry, it) }
+
+ override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
+ (decision as FullScreenIntentDecisionImpl).let {
+ wrapped.logFullScreenIntentDecision(it.originalEntry, it.originalDecision)
+ }
+ }
+
+ override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision =
+ wrapped.shouldBubbleUp(entry).let { DecisionImpl.of(it) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
new file mode 100644
index 000000000000..c0f4fcda56bb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.interruption
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+
+/**
+ * Decides whether a notification should visually interrupt the user in various ways.
+ *
+ * These include displaying the notification as heads-up (peeking while the device is awake or
+ * pulsing while the device is dozing), displaying the notification as a bubble, and launching a
+ * full-screen intent for the notification.
+ */
+interface VisualInterruptionDecisionProvider {
+ /**
+ * Represents the decision to visually interrupt or not.
+ *
+ * Used for heads-up and bubble decisions; subclassed by [FullScreenIntentDecision] for
+ * full-screen intent decisions.
+ *
+ * @property[shouldInterrupt] whether a visual interruption should be triggered
+ */
+ interface Decision {
+ val shouldInterrupt: Boolean
+ }
+
+ /**
+ * Represents the decision to launch a full-screen intent for a notification or not.
+ *
+ * @property[wouldInterruptWithoutDnd] whether a full-screen intent should not be launched only
+ * because Do Not Disturb has suppressed it
+ */
+ interface FullScreenIntentDecision : Decision {
+ val wouldInterruptWithoutDnd: Boolean
+ }
+
+ /**
+ * Adds a [component][suppressor] that can suppress visual interruptions.
+ *
+ * This class may call suppressors in any order.
+ *
+ * @param[suppressor] the suppressor to add
+ */
+ fun addSuppressor(suppressor: NotificationInterruptSuppressor)
+
+ /**
+ * Decides whether a [notification][entry] should display as heads-up or not, but does not log
+ * that decision.
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to display that notification as heads-up or not
+ */
+ fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision
+
+ /**
+ * Decides whether a [notification][entry] should display as heads-up or not, and logs that
+ * decision.
+ *
+ * If the device is awake, the decision will consider whether the notification should "peek"
+ * (slide in from the top of the screen over the current activity).
+ *
+ * If the device is dozing, the decision will consider whether the notification should "pulse"
+ * (wake the screen up and display the ambient view of the notification).
+ *
+ * @see[makeUnloggedHeadsUpDecision]
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to display that notification as heads-up or not
+ */
+ fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision
+
+ /**
+ * Decides whether a [notification][entry] should launch a full-screen intent or not, but does
+ * not log that decision.
+ *
+ * The returned decision can be logged by passing it to [logFullScreenIntentDecision].
+ *
+ * @see[makeAndLogHeadsUpDecision]
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to launch a full-screen intent for that notification or not
+ */
+ fun makeUnloggedFullScreenIntentDecision(entry: NotificationEntry): FullScreenIntentDecision
+
+ /**
+ * Logs a previous [decision] to launch a full-screen intent or not.
+ *
+ * @param[decision] the decision to log
+ */
+ fun logFullScreenIntentDecision(decision: FullScreenIntentDecision)
+
+ /**
+ * Decides whether a [notification][entry] should display as a bubble or not.
+ *
+ * @param[entry] the notification that this decision is about
+ * @return the decision to display that notification as a bubble or not
+ */
+ fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a9d125508397..950ab5d2f5ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -299,6 +299,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
private boolean mShowGroupBackgroundWhenExpanded;
+ /**
+ * True if we always show the collapsed layout on lockscreen because vertical space is low.
+ */
+ private boolean mSaveSpaceOnLockscreen;
+
+ /**
+ * True if we use intrinsic height regardless of vertical space available on lockscreen.
+ */
+ private boolean mIgnoreLockscreenConstraints;
+
private OnClickListener mExpandClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
@@ -394,6 +404,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGroupExpansionChanging;
}
+ public void setSaveSpaceOnLockscreen(boolean saveSpace) {
+ mSaveSpaceOnLockscreen = saveSpace;
+ }
+
+ public boolean getSaveSpaceOnLockscreen() {
+ return mSaveSpaceOnLockscreen;
+ }
+
public void setGroupExpansionChanging(boolean changing) {
mGroupExpansionChanging = changing;
}
@@ -419,15 +437,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
public void setAnimationRunning(boolean running) {
// Sets animations running in the private/public layouts.
- if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) {
- for (NotificationContentView l : mLayouts) {
- if (l != null) {
- l.setContentAnimationRunning(running);
- setIconAnimationRunning(running, l);
- }
- }
- } else {
- for (NotificationContentView l : mLayouts) {
+ for (NotificationContentView l : mLayouts) {
+ if (l != null) {
+ l.setContentAnimationRunning(running);
setIconAnimationRunning(running, l);
}
}
@@ -2553,11 +2565,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
@Override
+ public int getHeightWithoutLockscreenConstraints() {
+ mIgnoreLockscreenConstraints = true;
+ final int height = getIntrinsicHeight();
+ mIgnoreLockscreenConstraints = false;
+ return height;
+ }
+
+ @Override
public int getIntrinsicHeight() {
if (isUserLocked()) {
return getActualHeight();
- }
- if (mGuts != null && mGuts.isExposed()) {
+ } else if (mGuts != null && mGuts.isExposed()) {
return mGuts.getIntrinsicHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
@@ -2579,13 +2598,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return getCollapsedHeight();
}
}
-
/**
* @return {@code true} if the notification can show it's heads up layout. This is mostly true
* except for legacy use cases.
*/
public boolean canShowHeadsUp() {
- if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) {
+ if (mOnKeyguard && !isDozing() && !isBypassEnabled() &&
+ (!mEntry.isStickyAndNotDemoted()
+ || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) {
return false;
}
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 9df6ba9910cc..5edff5f4e5d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -291,6 +291,11 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro
long duration) {
}
+ public int getHeightWithoutLockscreenConstraints() {
+ // ExpandableNotificationRow overrides this.
+ return getHeight();
+ }
+
/**
* @return The desired notification height.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
index af8d6ec727d1..98cd84dde199 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/NotificationShelfComponent.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.notification.row.dagger;
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import dagger.Binds;
@@ -46,7 +46,8 @@ public interface NotificationShelfComponent {
* Creates a NotificationShelfController.
*/
@NotificationRowScope
- NotificationShelfController getNotificationShelfController();
+ LegacyNotificationShelfControllerImpl getNotificationShelfController();
+
/**
* Dagger Module that extracts interesting properties from a NotificationShelf.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
new file mode 100644
index 000000000000..8ba65f7a1418
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.shelf.domain.interactor
+
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+/** Interactor for the [NotificationShelf] */
+@CentralSurfacesComponent.CentralSurfacesScope
+class NotificationShelfInteractor
+@Inject
+constructor(
+ private val keyguardRepository: KeyguardRepository,
+ private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository,
+) {
+ /** Is the shelf showing on the keyguard? */
+ val isShowingOnKeyguard: Flow<Boolean>
+ get() = keyguardRepository.isKeyguardShowing
+
+ /** Is the system in a state where the shelf is just a static display of notification icons? */
+ val isShelfStatic: Flow<Boolean>
+ get() =
+ combine(
+ keyguardRepository.isKeyguardShowing,
+ deviceEntryFaceAuthRepository.isBypassEnabled,
+ ) { isKeyguardShowing, isBypassEnabled ->
+ isKeyguardShowing && isBypassEnabled
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
new file mode 100644
index 000000000000..b190cf658fa8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt
@@ -0,0 +1,135 @@
+/*
+ * 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.shelf.ui.viewbinder
+
+import android.view.View
+import android.view.accessibility.AccessibilityManager
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.NotificationShelfController
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController
+import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController
+import com.android.systemui.statusbar.notification.row.ExpandableViewController
+import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationIconContainer
+import com.android.systemui.statusbar.phone.NotificationTapHelper
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.util.kotlin.getValue
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper
+ * around a [NotificationShelfViewBinder], so that external code can continue to depend on the
+ * [NotificationShelfController] interface. Once the [LegacyNotificationShelfControllerImpl] is
+ * removed, this class can go away and the ViewBinder can be used directly.
+ */
+@CentralSurfacesScope
+class NotificationShelfViewBinderWrapperControllerImpl
+@Inject
+constructor(
+ private val shelf: NotificationShelf,
+ private val viewModel: NotificationShelfViewModel,
+ featureFlags: FeatureFlags,
+ private val notifTapHelperFactory: NotificationTapHelper.Factory,
+ private val a11yManager: AccessibilityManager,
+ private val falsingManager: FalsingManager,
+ private val falsingCollector: FalsingCollector,
+ hostControllerLazy: Lazy<NotificationStackScrollLayoutController>,
+) : NotificationShelfController {
+
+ private val hostController: NotificationStackScrollLayoutController by hostControllerLazy
+
+ override val view: NotificationShelf
+ get() = unsupported
+
+ init {
+ shelf.apply {
+ setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR))
+ useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES))
+ setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM))
+ }
+ }
+
+ fun init() {
+ NotificationShelfViewBinder.bind(viewModel, shelf)
+
+ ActivatableNotificationViewController(
+ shelf,
+ notifTapHelperFactory,
+ ExpandableOutlineViewController(shelf, ExpandableViewController(shelf)),
+ a11yManager,
+ falsingManager,
+ falsingCollector,
+ )
+ .init()
+ hostController.setShelf(shelf)
+ hostController.setOnNotificationRemovedListener { child, _ ->
+ view.requestRoundnessResetFor(child)
+ }
+ }
+
+ override val intrinsicHeight: Int
+ get() = shelf.intrinsicHeight
+
+ override val shelfIcons: NotificationIconContainer
+ get() = shelf.shelfIcons
+
+ override fun canModifyColorOfNotifications(): Boolean = unsupported
+
+ override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) {
+ shelf.setOnActivatedListener(listener)
+ }
+
+ override fun bind(
+ ambientState: AmbientState,
+ notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
+ ) = unsupported
+
+ override fun setOnClickListener(listener: View.OnClickListener) {
+ shelf.setOnClickListener(listener)
+ }
+
+ private val unsupported: Nothing
+ get() = NotificationShelfController.throwIllegalFlagStateError(expected = true)
+}
+
+/** Binds a [NotificationShelf] to its backend. */
+object NotificationShelfViewBinder {
+ fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) {
+ shelf.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ viewModel.canModifyColorOfNotifications
+ .onEach(shelf::setCanModifyColorOfNotifications)
+ .launchIn(this)
+ viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
new file mode 100644
index 000000000000..5e297c89dd2f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.shelf.ui.viewmodel
+
+import com.android.systemui.statusbar.NotificationShelf
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** ViewModel for [NotificationShelf]. */
+@CentralSurfacesScope
+class NotificationShelfViewModel
+@Inject
+constructor(
+ private val interactor: NotificationShelfInteractor,
+) {
+ /** Is the shelf allowed to be clickable when it has content? */
+ val isClickable: Flow<Boolean>
+ get() = interactor.isShowingOnKeyguard
+
+ /** Is the shelf allowed to modify the color of notifications in the host layout? */
+ val canModifyColorOfNotifications: Flow<Boolean>
+ get() = interactor.isShelfStatic.map { static -> !static }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
index 112d48c115c2..00b9aa42ab26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java
@@ -32,6 +32,7 @@ public class AnimationProperties {
public long duration;
public long delay;
private ArrayMap<Property, Interpolator> mInterpolatorMap;
+ private Consumer<Property> mAnimationCancelAction;
private Consumer<Property> mAnimationEndAction;
/**
@@ -50,27 +51,43 @@ public class AnimationProperties {
* @return a listener that will be added for a given property during its animation.
*/
public AnimatorListenerAdapter getAnimationFinishListener(Property property) {
- if (mAnimationEndAction == null) {
+ if (mAnimationEndAction == null && mAnimationCancelAction == null) {
return null;
}
+ Consumer<Property> cancelAction = mAnimationCancelAction;
Consumer<Property> endAction = mAnimationEndAction;
return new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationCancel(Animator animation) {
- mCancelled = true;
+ mCancelled = true;
+ if (cancelAction != null) {
+ cancelAction.accept(property);
+ }
}
@Override
public void onAnimationEnd(Animator animation) {
- if (!mCancelled) {
+ if (!mCancelled && endAction != null) {
endAction.accept(property);
}
}
};
}
+ /**
+ * Add a callback for animation cancellation.
+ */
+ public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) {
+ mAnimationCancelAction = listener;
+ return this;
+ }
+
+ /**
+ * Add a callback for animation ending successfully. The callback will not be called when the
+ * animations is cancelled.
+ */
public AnimationProperties setAnimationEndAction(Consumer<Property> listener) {
mAnimationEndAction = listener;
return this;
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 e47e4146d4c0..af608a7f3a64 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
@@ -5137,8 +5137,26 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
requestChildrenUpdate();
}
+ public void setShelf(NotificationShelf shelf) {
+ if (!NotificationShelfController.checkRefactorFlagEnabled(
+ mAmbientState.getFeatureFlags())) {
+ return;
+ }
+ int index = -1;
+ if (mShelf != null) {
+ index = indexOfChild(mShelf);
+ removeView(mShelf);
+ }
+ mShelf = shelf;
+ addView(mShelf, index);
+ mAmbientState.setShelf(mShelf);
+ mStateAnimator.setShelf(mShelf);
+ shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager());
+ }
+
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void setShelfController(NotificationShelfController notificationShelfController) {
+ NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags());
int index = -1;
if (mShelf != null) {
index = indexOfChild(mShelf);
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 792746c60134..1c8727f1b092 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
@@ -73,6 +73,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
@@ -1382,6 +1383,7 @@ public class NotificationStackScrollLayoutController {
}
public void setShelfController(NotificationShelfController notificationShelfController) {
+ NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags);
mView.setShelfController(notificationShelfController);
}
@@ -1593,6 +1595,11 @@ public class NotificationStackScrollLayoutController {
mView.setOnNotificationRemovedListener(listener);
}
+ public void setShelf(NotificationShelf shelf) {
+ if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return;
+ mView.setShelf(shelf);
+ }
+
/**
* Enum for UiEvent logged from this class
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 092242814869..c7cb70c1da05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -23,12 +23,14 @@ import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.util.Compile
+import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade
import com.android.systemui.util.children
import java.io.PrintWriter
import javax.inject.Inject
@@ -51,11 +53,10 @@ class NotificationStackSizeCalculator
constructor(
private val statusBarStateController: SysuiStatusBarStateController,
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
+ private val mediaDataManager: MediaDataManager,
@Main private val resources: Resources
) {
- private lateinit var lastComputeHeightLog : String
-
/**
* Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf.
* If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space
@@ -67,67 +68,157 @@ constructor(
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
+ /**
+ * True when there is not enough vertical space to show at least one notification with heads up
+ * layout. When true, notifications always show collapsed layout.
+ */
+ private var saveSpaceOnLockscreen = false
+
init {
updateResources()
}
/**
* Returns whether notifications and (shelf if visible) can fit in total space available.
- * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon.
+ * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon.
*/
private fun canStackFitInSpace(
stackHeight: StackHeight,
- spaceForNotifications: Float,
- spaceForShelf: Float,
- ): Boolean {
-
- val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight
- var canFit: Boolean
+ notifSpace: Float,
+ shelfSpace: Float,
+ ): FitResult {
+ val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight
if (shelfHeightWithSpaceBefore == 0f) {
- canFit = notificationsHeight <= spaceForNotifications
+ if (notifHeight <= notifSpace) {
+ log {
+ "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" +
+ " <= notifSpace[$notifSpace]"
+ }
+ return FitResult.FIT
+ }
+ if (notifHeightSaveSpace <= notifSpace) {
+ log {
+ "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+ " = notifHeightSaveSpace[$notifHeightSaveSpace]" +
+ " <= notifSpace[$notifSpace]"
+ }
+ return FitResult.FIT_IF_SAVE_SPACE
+ }
log {
- "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" +
- " <= spaceForNotifications[$spaceForNotifications]"
+ "\tcanStackFitInSpace[NO_FIT]" +
+ " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]"
}
+ return FitResult.NO_FIT
} else {
- canFit =
- (notificationsHeight + shelfHeightWithSpaceBefore) <=
- (spaceForNotifications + spaceForShelf)
- log {
- "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" +
- " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
- " <= (spaceForNotifications[$spaceForNotifications] " +
- " + spaceForShelf[$spaceForShelf])"
+ if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) {
+ log {
+ "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" +
+ " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+ " <= (notifSpace[$notifSpace] " +
+ " + spaceForShelf[$shelfSpace])"
+ }
+ return FitResult.FIT
+ } else if (
+ (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)
+ ) {
+ log {
+ "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" +
+ " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+ " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+ " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+ }
+ return FitResult.FIT_IF_SAVE_SPACE
+ } else {
+ log {
+ "\tcanStackFitInSpace[NO_FIT]" +
+ " = (notifHeightSaveSpace[$notifHeightSaveSpace]" +
+ " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" +
+ " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])"
+ }
+ return FitResult.NO_FIT
}
}
- return canFit
}
/**
- * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many
- * notifications to show. This number is only valid in keyguard.
+ * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to
+ * show. This number is only valid in keyguard.
*
* @param totalAvailableSpace space for notifications. This includes the space for the shelf.
*/
fun computeMaxKeyguardNotifications(
stack: NotificationStackScrollLayout,
- spaceForNotifications: Float,
- spaceForShelf: Float,
- shelfIntrinsicHeight: Float
+ notifSpace: Float,
+ shelfSpace: Float,
+ shelfHeight: Float,
): Int {
+ log { "\n " }
+ log {
+ "computeMaxKeyguardNotifications ---" +
+ "\n\tnotifSpace $notifSpace" +
+ "\n\tspaceForShelf $shelfSpace" +
+ "\n\tshelfIntrinsicHeight $shelfHeight"
+ }
+ if (notifSpace + shelfSpace <= 0f) {
+ log { "--- No space to show anything. maxNotifs=0" }
+ return 0
+ }
log { "\n" }
- val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
- /* computeHeight= */ false)
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+ val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation()
- var maxNotifications =
+ log { "\tGet maxNotifWithoutSavingSpace ---" }
+ val maxNotifWithoutSavingSpace =
stackHeightSequence.lastIndexWhile { heightResult ->
canStackFitInSpace(
heightResult,
- spaceForNotifications = spaceForNotifications,
- spaceForShelf = spaceForShelf)
+ notifSpace = notifSpace,
+ shelfSpace = shelfSpace
+ ) == FitResult.FIT
+ }
+
+ // How many notifications we can show at heightWithoutLockscreenConstraints
+ var minCountAtHeightWithoutConstraints =
+ if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1
+ log {
+ "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " +
+ "isMediaShowing=$isMediaShowing" +
+ "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints"
+ }
+ log { "\n" }
+
+ var maxNotifications: Int
+ if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) {
+ saveSpaceOnLockscreen = false
+ maxNotifications = maxNotifWithoutSavingSpace
+ log {
+ "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications"
+ }
+ } else {
+ log { "\tSAVE space ---" }
+ saveSpaceOnLockscreen = true
+ maxNotifications =
+ stackHeightSequence.lastIndexWhile { heightResult ->
+ canStackFitInSpace(
+ heightResult,
+ notifSpace = notifSpace,
+ shelfSpace = shelfSpace
+ ) != FitResult.NO_FIT
+ }
+ log { "\t--- maxNotifications=$maxNotifications" }
+ }
+
+ // Must update views immediately to avoid mismatches between initial HUN layout height
+ // and the height adapted to lockscreen space constraints, which causes jump cuts.
+ stack.showableChildren().toList().forEach { currentNotification ->
+ run {
+ if (currentNotification is ExpandableNotificationRow) {
+ currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen
+ }
}
+ }
if (onLockscreen()) {
maxNotifications = min(maxKeyguardNotifications, maxNotifications)
@@ -137,53 +228,80 @@ constructor(
maxNotifications = max(0, maxNotifications)
log {
val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else ""
- "computeMaxKeyguardNotifications(" +
- " spaceForNotifications=$spaceForNotifications" +
- " spaceForShelf=$spaceForShelf" +
- " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence"
+ "--- computeMaxKeyguardNotifications(" +
+ " notifSpace=$notifSpace" +
+ " shelfSpace=$shelfSpace" +
+ " shelfHeight=$shelfHeight) -> $maxNotifications$sequence"
}
+ log { "\n" }
return maxNotifications
}
/**
- * Given the [maxNotifications] constraint, calculates the height of the
+ * Given the [maxNotifs] constraint, calculates the height of the
* [NotificationStackScrollLayout]. This might or might not be in keyguard.
*
* @param stack stack containing notifications as children.
- * @param maxNotifications Maximum number of notifications. When reached, the others will go
- * into the shelf.
- * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero.
- *
+ * @param maxNotifs Maximum number of notifications. When reached, the others will go into the
+ * shelf.
+ * @param shelfHeight height of the shelf, without any padding. It might be zero.
* @return height of the stack, including shelf height, if needed.
*/
fun computeHeight(
stack: NotificationStackScrollLayout,
- maxNotifications: Int,
- shelfIntrinsicHeight: Float
+ maxNotifs: Int,
+ shelfHeight: Float
): Float {
log { "\n" }
- lastComputeHeightLog = ""
- val heightPerMaxNotifications =
- computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight,
- /* computeHeight= */ true)
-
- val (notificationsHeight, shelfHeightWithSpaceBefore) =
- heightPerMaxNotifications.elementAtOrElse(maxNotifications) {
- heightPerMaxNotifications.last() // Height with all notifications visible.
+ log { "computeHeight ---" }
+
+ val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight)
+
+ val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) =
+ stackHeightSequence.elementAtOrElse(maxNotifs) {
+ stackHeightSequence.last() // Height with all notifications visible.
+ }
+
+ var height: Float
+ if (saveSpaceOnLockscreen) {
+ height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore
+ log {
+ "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+ " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," +
+ " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
+ }
+ } else {
+ height = notifsHeight + shelfHeightWithSpaceBefore
+ log {
+ "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" +
+ " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" +
+ " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen"
}
- lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," +
- "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " +
- "${notificationsHeight + shelfHeightWithSpaceBefore}" +
- " = ($notificationsHeight + $shelfHeightWithSpaceBefore)"
- log {
- lastComputeHeightLog
}
- return notificationsHeight + shelfHeightWithSpaceBefore
+ return height
}
+ private enum class FitResult {
+ FIT,
+ FIT_IF_SAVE_SPACE,
+ NO_FIT
+ }
+
+ data class SpaceNeeded(
+ // Float height of spaceNeeded when showing heads up layout for FSI HUNs.
+ val whenEnoughSpace: Float,
+
+ // Float height of space needed when showing collapsed layout for FSI HUNs.
+ val whenSavingSpace: Float
+ )
+
private data class StackHeight(
// Float height with ith max notifications (not including shelf)
- val notificationsHeight: Float,
+ val notifsHeight: Float,
+
+ // Float height with ith max notifications
+ // (not including shelf, using collapsed layout for FSI HUN)
+ val notifsHeightSavingSpace: Float,
// Float height of shelf (0 if shelf is not showing), and space before the shelf that
// changes during the lockscreen <=> full shade transition.
@@ -193,20 +311,27 @@ constructor(
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
shelfHeight: Float,
- computeHeight: Boolean
): Sequence<StackHeight> = sequence {
- log { "computeHeightPerNotificationLimit" }
-
val children = stack.showableChildren().toList()
var notifications = 0f
+ var notifsWithCollapsedHun = 0f
var previous: ExpandableView? = null
val onLockscreen = onLockscreen()
// Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState).
- yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight))
+ yield(
+ StackHeight(
+ notifsHeight = 0f,
+ notifsHeightSavingSpace = 0f,
+ shelfHeightWithSpaceBefore = shelfHeight
+ )
+ )
children.forEachIndexed { i, currentNotification ->
- notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+ val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen)
+ notifications += space.whenEnoughSpace
+ notifsWithCollapsedHun += space.whenSavingSpace
+
previous = currentNotification
val shelfWithSpaceBefore =
@@ -219,22 +344,23 @@ constructor(
stack,
previous = currentNotification,
current = children[firstViewInShelfIndex],
- currentIndex = firstViewInShelfIndex)
+ currentIndex = firstViewInShelfIndex
+ )
spaceBeforeShelf + shelfHeight
}
- val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " +
- "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore"
- if (computeHeight) {
- lastComputeHeightLog += "\n" + currentLog
- }
log {
- currentLog
+ "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
+ "notifsHeightSavingSpace=$notifsWithCollapsedHun" +
+ " shelfWithSpaceBefore=$shelfWithSpaceBefore"
}
yield(
StackHeight(
- notificationsHeight = notifications,
- shelfHeightWithSpaceBefore = shelfWithSpaceBefore))
+ notifsHeight = notifications,
+ notifsHeightSavingSpace = notifsWithCollapsedHun,
+ shelfHeightWithSpaceBefore = shelfWithSpaceBefore
+ )
+ )
}
}
@@ -256,32 +382,46 @@ constructor(
}
@VisibleForTesting
- fun spaceNeeded(
+ fun getSpaceNeeded(
view: ExpandableView,
visibleIndex: Int,
previousView: ExpandableView?,
stack: NotificationStackScrollLayout,
- onLockscreen: Boolean
- ): Float {
+ onLockscreen: Boolean,
+ ): SpaceNeeded {
assert(view.isShowable(onLockscreen))
+ // Must use heightWithoutLockscreenConstraints because intrinsicHeight references
+ // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow.
+ val height = view.heightWithoutLockscreenConstraints.toFloat()
+ val gapAndDividerHeight =
+ calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
+
var size =
if (onLockscreen) {
if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) {
- view.intrinsicHeight.toFloat()
+ height
} else {
view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
}
} else {
- view.intrinsicHeight.toFloat()
+ height
+ }
+ size += gapAndDividerHeight
+
+ var sizeWhenSavingSpace =
+ if (onLockscreen) {
+ view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat()
+ } else {
+ height
}
+ sizeWhenSavingSpace += gapAndDividerHeight
- size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex)
- return size
+ return SpaceNeeded(size, sizeWhenSavingSpace)
}
fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog")
+ pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
}
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
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 0c8e9e56b04a..7596ce08a53c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -192,6 +192,7 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
import com.android.systemui.statusbar.CircleReveal;
@@ -505,6 +506,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
/** Controller for the Shade. */
@VisibleForTesting
NotificationPanelViewController mNotificationPanelViewController;
+ private final ShadeLogger mShadeLogger;
// settings
private QSPanelController mQSPanelController;
@@ -738,6 +740,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
KeyguardViewMediator keyguardViewMediator,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
+ ShadeLogger shadeLogger,
@UiBackground Executor uiBgExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
@@ -830,6 +833,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mKeyguardViewMediator = keyguardViewMediator;
mDisplayMetrics = displayMetrics;
mMetricsLogger = metricsLogger;
+ mShadeLogger = shadeLogger;
mUiBgExecutor = uiBgExecutor;
mMediaManager = notificationMediaManager;
mLockscreenUserManager = lockScreenUserManager;
@@ -3672,6 +3676,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
|| goingToSleepWithoutAnimation
|| mDeviceProvisionedController.isFrpActive();
+ mShadeLogger.logUpdateNotificationPanelTouchState(disabled, isGoingToSleep(),
+ !mDozeParameters.shouldControlScreenOff(), !mDeviceInteractive,
+ !mDozeServiceHost.isPulsing(), mDeviceProvisionedController.isFrpActive());
+
mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index e4227dce94e7..d433814d7ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -29,6 +29,7 @@ import com.android.systemui.R
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.VibratorHelper
@@ -57,7 +58,7 @@ constructor(
}
private var ambientIndicationArea: View? = null
- private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
+ private var binding: KeyguardBottomAreaViewBinder.Binding? = null
private var lockIconViewController: LockIconViewController? = null
/** Initializes the view. */
@@ -67,13 +68,16 @@ constructor(
lockIconViewController: LockIconViewController? = null,
messageDisplayer: MessageDisplayer? = null,
vibratorHelper: VibratorHelper? = null,
+ activityStarter: ActivityStarter? = null,
) {
+ binding?.destroy()
binding =
bind(
this,
viewModel,
falsingManager,
vibratorHelper,
+ activityStarter,
) {
messageDisplayer?.display(it)
}
@@ -114,12 +118,12 @@ constructor(
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- binding.onConfigurationChanged()
+ binding?.onConfigurationChanged()
}
/** Returns a list of animators to use to animate the indication areas. */
val indicationAreaAnimators: List<ViewPropertyAnimator>
- get() = binding.getIndicationAreaAnimators()
+ get() = checkNotNull(binding).getIndicationAreaAnimators()
override fun hasOverlappingRendering(): Boolean {
return false
@@ -139,7 +143,7 @@ constructor(
super.onLayout(changed, left, top, right, bottom)
findViewById<View>(R.id.ambient_indication_container)?.let {
val (ambientLeft, ambientTop) = it.locationOnScreen
- if (binding.shouldConstrainToTopOfLockIcon()) {
+ if (binding?.shouldConstrainToTopOfLockIcon() == true) {
// make top of ambient indication view the bottom of the lock icon
it.layout(
ambientLeft,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 8ee2c6f2c399..74ab47ff27a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -29,6 +29,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.Assert
import com.android.systemui.util.sensors.AsyncSensorManager
@@ -46,6 +47,7 @@ class KeyguardLiftController @Inject constructor(
private val statusBarStateController: StatusBarStateController,
private val asyncSensorManager: AsyncSensorManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
private val dumpManager: DumpManager
) : Dumpable, CoreStartable {
@@ -72,6 +74,7 @@ class KeyguardLiftController @Inject constructor(
// Not listening anymore since trigger events unregister themselves
isListening = false
updateListeningState()
+ keyguardFaceAuthInteractor.onDeviceLifted()
keyguardUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
deleted file mode 100644
index 076e5f1c1ce7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java
+++ /dev/null
@@ -1,69 +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.phone;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorFilter;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.WindowInsets;
-import android.widget.FrameLayout;
-
-/**
- * A view group which contains the preview of phone/camera and draws a black bar at the bottom as
- * the fake navigation bar.
- */
-public class KeyguardPreviewContainer extends FrameLayout {
-
- private Drawable mBlackBarDrawable = new Drawable() {
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight());
- canvas.drawColor(Color.BLACK);
- canvas.restore();
- }
-
- @Override
- public void setAlpha(int alpha) {
- // noop
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- // noop
- }
-
- @Override
- public int getOpacity() {
- return android.graphics.PixelFormat.OPAQUE;
- }
- };
-
- public KeyguardPreviewContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
- setBackground(mBlackBarDrawable);
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- setPadding(0, 0, 0, insets.getStableInsetBottom());
- return super.onApplyWindowInsets(insets);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index eb19c0d2ad71..057fa42bd347 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -193,7 +193,6 @@ public class NotificationIconAreaController implements
public void setupShelf(NotificationShelfController notificationShelfController) {
mShelfIcons = notificationShelfController.getShelfIcons();
- notificationShelfController.setCollapsedIcons(mNotificationIcons);
}
public void onDensityOrFontScaleChanged(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
index b3031515ae9d..c8174669cc65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt
@@ -85,17 +85,16 @@ class ScreenOffAnimationController @Inject constructor(
/**
* Called when keyguard is about to be displayed and allows to perform custom animation
- *
- * @return A handle that can be used for cancelling the animation, if necessary
*/
- fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
- animations.forEach {
+ fun animateInKeyguard(keyguardView: View, after: Runnable) =
+ animations.firstOrNull {
if (it.shouldAnimateInKeyguard()) {
- return@animateInKeyguard it.animateInKeyguard(keyguardView, after)
+ it.animateInKeyguard(keyguardView, after)
+ true
+ } else {
+ false
}
}
- return null
- }
/**
* If returns true it will disable propagating touches to apps and keyguard
@@ -212,10 +211,7 @@ interface ScreenOffAnimation {
fun onAlwaysOnChanged(alwaysOn: Boolean) {}
fun shouldAnimateInKeyguard(): Boolean = false
- fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle? {
- after.run()
- return null
- }
+ fun animateInKeyguard(keyguardView: View, after: Runnable) = after.run()
fun shouldDelayKeyguardShow(): Boolean = false
fun isKeyguardShowDelayed(): Boolean = false
@@ -228,7 +224,3 @@ interface ScreenOffAnimation {
fun shouldAnimateDozingChange(): Boolean = true
fun shouldAnimateClockChange(): Boolean = true
}
-
-interface AnimatorHandle {
- fun cancel()
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index de7bf3c021dd..d731f8886536 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -230,7 +230,7 @@ public class StatusBarSignalPolicy implements SignalCallback,
if (state == null) {
return;
}
- if (statusIcon.icon == R.drawable.ic_qs_no_calling_sms) {
+ if (statusIcon.icon == R.drawable.ic_shade_no_calling_sms) {
state.isNoCalling = statusIcon.visible;
state.noCallingDescription = statusIcon.contentDescription;
} else {
@@ -422,7 +422,7 @@ public class StatusBarSignalPolicy implements SignalCallback,
private CallIndicatorIconState(int subId) {
this.subId = subId;
- this.noCallingResId = R.drawable.ic_qs_no_calling_sms;
+ this.noCallingResId = R.drawable.ic_shade_no_calling_sms;
this.callStrengthResId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index 50cce45cd87a..6dc8065b2822 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -16,6 +16,12 @@
package com.android.systemui.statusbar.phone;
public interface StatusBarWindowCallback {
- void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
- boolean isDozing, boolean panelExpanded, boolean isDreaming);
+ /**
+ * Invoked when the internal state of NotificationShadeWindowControllerImpl changes.
+ * Some of the flags passed as argument to the callback might have changed, but this is not
+ * guaranteed.
+ */
+ void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
+ boolean keyguardGoingAway, boolean bouncerShowing, boolean isDozing,
+ boolean panelExpanded, boolean isDreaming);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
index deb041454da4..0cd3401ae541 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -160,7 +160,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
*/
- override fun animateInKeyguard(keyguardView: View, after: Runnable): AnimatorHandle {
+ override fun animateInKeyguard(keyguardView: View, after: Runnable) {
shouldAnimateInKeyguard = false
keyguardView.alpha = 0f
keyguardView.visibility = View.VISIBLE
@@ -175,40 +175,19 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// We animate the Y properly separately using the PropertyAnimator, as the panel
// view also needs to update the end position.
PropertyAnimator.cancelAnimation(keyguardView, AnimatableProperty.Y)
+ PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
+ AnimationProperties().setDuration(duration.toLong()),
+ true /* animate */)
- // Start the animation on the next frame using Choreographer APIs. animateInKeyguard() is
- // called while the system is busy processing lots of requests, so delaying the animation a
- // frame will mitigate jank. In the event the animation is cancelled before the next frame
- // is called, this callback will be removed
- val keyguardAnimator = keyguardView.animate()
- val nextFrameCallback = TraceUtils.namedRunnable("startAnimateInKeyguard") {
- PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY,
- AnimationProperties().setDuration(duration.toLong()),
- true /* animate */)
- keyguardAnimator.start()
- }
- DejankUtils.postAfterTraversal(nextFrameCallback)
- val animatorHandle = object : AnimatorHandle {
- private var hasCancelled = false
- override fun cancel() {
- if (!hasCancelled) {
- DejankUtils.removeCallbacks(nextFrameCallback)
- // If we're cancelled, reset state flags/listeners. The end action above
- // will not be called, which is what we want since that will finish the
- // screen off animation and show the lockscreen, which we don't want if we
- // were cancelled.
- aodUiAnimationPlaying = false
- decidedToAnimateGoingToSleep = null
- keyguardView.animate().setListener(null)
- hasCancelled = true
- }
- }
- }
- keyguardAnimator
+ // Cancel any existing CUJs before starting the animation
+ interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+
+ PropertyAnimator.setProperty(
+ keyguardView, AnimatableProperty.ALPHA, 1f,
+ AnimationProperties()
+ .setDelay(0)
.setDuration(duration.toLong())
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .alpha(1f)
- .withEndAction {
+ .setAnimationEndAction {
aodUiAnimationPlaying = false
// Lock the keyguard if it was waiting for the screen off animation to end.
@@ -224,23 +203,23 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Done going to sleep, reset this flag.
decidedToAnimateGoingToSleep = null
- // We need to unset the listener. These are persistent for future animators
- keyguardView.animate().setListener(null)
interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD)
}
- .setListener(object : AnimatorListenerAdapter() {
- override fun onAnimationCancel(animation: Animator?) {
- animatorHandle.cancel()
- interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
- }
-
- override fun onAnimationStart(animation: Animator?) {
- interactionJankMonitor.begin(
- mCentralSurfaces.notificationShadeWindowView,
- CUJ_SCREEN_OFF_SHOW_AOD)
- }
- })
- return animatorHandle
+ .setAnimationCancelAction {
+ // If we're cancelled, reset state flags/listeners. The end action above
+ // will not be called, which is what we want since that will finish the
+ // screen off animation and show the lockscreen, which we don't want if we
+ // were cancelled.
+ aodUiAnimationPlaying = false
+ decidedToAnimateGoingToSleep = null
+ interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD)
+ }
+ .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN),
+ true /* animate */)
+ interactionJankMonitor.begin(
+ mCentralSurfaces.notificationShadeWindowView,
+ CUJ_SCREEN_OFF_SHOW_AOD
+ )
}
override fun onStartedWakingUp() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 0929233feb88..5d4addab240a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -33,6 +33,7 @@ import com.android.systemui.biometrics.AuthRippleView;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.settings.UserTracker;
@@ -44,12 +45,14 @@ import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationsQuickSettingsContainer;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
@@ -76,6 +79,7 @@ import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.Executor;
import javax.inject.Named;
+import javax.inject.Provider;
import dagger.Binds;
import dagger.Module;
@@ -130,16 +134,24 @@ public abstract class StatusBarViewModule {
@Provides
@CentralSurfacesComponent.CentralSurfacesScope
public static NotificationShelfController providesStatusBarWindowView(
+ FeatureFlags featureFlags,
+ Provider<NotificationShelfViewBinderWrapperControllerImpl> newImpl,
NotificationShelfComponent.Builder notificationShelfComponentBuilder,
NotificationShelf notificationShelf) {
- NotificationShelfComponent component = notificationShelfComponentBuilder
- .notificationShelf(notificationShelf)
- .build();
- NotificationShelfController notificationShelfController =
- component.getNotificationShelfController();
- notificationShelfController.init();
-
- return notificationShelfController;
+ if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) {
+ NotificationShelfViewBinderWrapperControllerImpl impl = newImpl.get();
+ impl.init();
+ return impl;
+ } else {
+ NotificationShelfComponent component = notificationShelfComponentBuilder
+ .notificationShelf(notificationShelf)
+ .build();
+ LegacyNotificationShelfControllerImpl notificationShelfController =
+ component.getNotificationShelfController();
+ notificationShelfController.init();
+
+ return notificationShelfController;
+ }
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index eaa145582ba3..b3d246164e87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupR
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
@@ -53,6 +54,9 @@ import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import kotlinx.coroutines.flow.Flow
+import java.util.function.Supplier
+import javax.inject.Named
@Module
abstract class StatusBarPipelineModule {
@@ -115,6 +119,17 @@ abstract class StatusBarPipelineModule {
@Provides
@SysUISingleton
+ @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
+ fun provideFirstMobileSubShowingNetworkTypeIconProvider(
+ mobileIconsViewModel: MobileIconsViewModel,
+ ): Supplier<Flow<Boolean>> {
+ return Supplier<Flow<Boolean>> {
+ mobileIconsViewModel.firstMobileSubShowingNetworkTypeIcon
+ }
+ }
+
+ @Provides
+ @SysUISingleton
@WifiInputLog
fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("WifiInputLog", 50)
@@ -168,5 +183,8 @@ abstract class StatusBarPipelineModule {
fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer {
return factory.create("VerboseMobileViewLog", 100)
}
+
+ const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
+ "FirstMobileSubShowingNetworkTypeIcon"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index fa712872eb13..ea77163f0556 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -60,6 +60,13 @@ interface MobileConnectionsRepository {
*/
val mobileIsDefault: StateFlow<Boolean>
+ /**
+ * True if the device currently has a carrier merged connection.
+ *
+ * See [CarrierMergedConnectionRepository] for more info.
+ */
+ val hasCarrierMergedConnection: Flow<Boolean>
+
/** True if the default network connection is validated and false otherwise. */
val defaultConnectionIsValidated: StateFlow<Boolean>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
index 44b5b3fa2591..eb20bba0d21f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -159,6 +159,15 @@ constructor(
.flatMapLatest { it.mobileIsDefault }
.stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.mobileIsDefault.value)
+ override val hasCarrierMergedConnection: StateFlow<Boolean> =
+ activeRepo
+ .flatMapLatest { it.hasCarrierMergedConnection }
+ .stateIn(
+ scope,
+ SharingStarted.WhileSubscribed(),
+ realRepository.hasCarrierMergedConnection.value,
+ )
+
override val defaultConnectionIsValidated: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.defaultConnectionIsValidated }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 737bc6826d08..0e4ceebcc854 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -160,6 +160,9 @@ constructor(
override val mobileIsDefault: StateFlow<Boolean> = MutableStateFlow(true)
// TODO(b/261029387): not yet supported
+ override val hasCarrierMergedConnection = MutableStateFlow(false)
+
+ // TODO(b/261029387): not yet supported
override val defaultConnectionIsValidated: StateFlow<Boolean> = MutableStateFlow(true)
override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 45d50c103909..0e9b6c56437e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -257,18 +258,32 @@ constructor(
override val mobileIsDefault: StateFlow<Boolean> =
connectivityRepository.defaultConnections
- // Because carrier merged networks are displayed as mobile networks, they're
- // part of the `isDefault` calculation. See b/272586234.
- .map { it.mobile.isDefault || it.carrierMerged.isDefault }
+ .map { it.mobile.isDefault }
.distinctUntilChanged()
.logDiffsForTable(
tableLogger,
- columnPrefix = "",
+ columnPrefix = LOGGING_PREFIX,
columnName = "mobileIsDefault",
initialValue = false,
)
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
+ override val hasCarrierMergedConnection: StateFlow<Boolean> =
+ combine(
+ connectivityRepository.defaultConnections,
+ carrierMergedSubId,
+ ) { defaultConnections, carrierMergedSubId ->
+ defaultConnections.carrierMerged.isDefault || carrierMergedSubId != null
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLogger,
+ columnPrefix = LOGGING_PREFIX,
+ columnName = "hasCarrierMergedConnection",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
override val defaultConnectionIsValidated: StateFlow<Boolean> =
connectivityRepository.defaultConnections
.map { it.isValidated }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index 1e3122b1a515..eec91a0bca82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -113,7 +113,22 @@ constructor(
private val context: Context,
) : MobileIconsInteractor {
- override val mobileIsDefault = mobileConnectionsRepo.mobileIsDefault
+ override val mobileIsDefault =
+ combine(
+ mobileConnectionsRepo.mobileIsDefault,
+ mobileConnectionsRepo.hasCarrierMergedConnection,
+ ) { mobileIsDefault, hasCarrierMergedConnection ->
+ // Because carrier merged networks are displayed as mobile networks, they're part of
+ // the `isDefault` calculation. See b/272586234.
+ mobileIsDefault || hasCarrierMergedConnection
+ }
+ .logDiffsForTable(
+ tableLogger,
+ LOGGING_PREFIX,
+ columnName = "mobileIsDefault",
+ initialValue = false,
+ )
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
mobileConnectionsRepo.activeMobileDataRepository
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 075e6ec11ae7..a05ab849088d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -26,15 +26,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
@@ -46,39 +38,16 @@ import kotlinx.coroutines.launch
* the list of available mobile lines of service for which we want to show icons.
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MobileUiAdapter
@Inject
constructor(
- interactor: MobileIconsInteractor,
private val iconController: StatusBarIconController,
- private val iconsViewModelFactory: MobileIconsViewModel.Factory,
+ val mobileIconsViewModel: MobileIconsViewModel,
private val logger: MobileViewLogger,
@Application private val scope: CoroutineScope,
private val statusBarPipelineFlags: StatusBarPipelineFlags,
) : CoreStartable {
- private val mobileSubIds: Flow<List<Int>> =
- interactor.filteredSubscriptions.mapLatest { subscriptions ->
- subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
- }
-
- /**
- * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is
- * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which
- * house the mobile infos.
- *
- * NOTE: this should go away as the view presenter learns more about this data pipeline
- */
- private val mobileSubIdsState: StateFlow<List<Int>> =
- mobileSubIds
- .distinctUntilChanged()
- .onEach { logger.logUiAdapterSubIdsUpdated(it) }
- .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
-
- /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
- val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
-
private var isCollecting: Boolean = false
private var lastValue: List<Int>? = null
@@ -90,7 +59,7 @@ constructor(
if (statusBarPipelineFlags.useNewMobileIcons()) {
scope.launch {
isCollecting = true
- mobileSubIds.collectLatest {
+ mobileIconsViewModel.subscriptionIdsFlow.collectLatest {
logger.logUiAdapterSubIdsSentToIconController(it)
lastValue = it
iconController.setNewMobileIconSubIds(it)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
index 90dff23c637c..f2f91430eba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt
@@ -41,15 +41,6 @@ constructor(
private val collectionStatuses = mutableMapOf<String, Boolean>()
- fun logUiAdapterSubIdsUpdated(subs: List<Int>) {
- buffer.log(
- TAG,
- LogLevel.INFO,
- { str1 = subs.toString() },
- { "Sub IDs in MobileUiAdapter updated internally: $str1" },
- )
- }
-
fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) {
buffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index dce7bf21fadd..bfd133e6830c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -37,7 +37,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
/** Common interface for all of the location-based mobile icon view models. */
@@ -80,7 +79,12 @@ constructor(
) : MobileIconViewModelCommon {
/** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: Flow<Boolean> =
- iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+ combine(
+ iconInteractor.isDefaultDataEnabled,
+ iconInteractor.isDefaultConnectionFailed,
+ ) { isDefaultDataEnabled, isDefaultConnectionFailed ->
+ !isDefaultDataEnabled || isDefaultConnectionFailed
+ }
override val isVisible: StateFlow<Boolean> =
if (!constants.hasDataCapabilities) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
index 2b90065284d0..40b8c90fb9f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt
@@ -29,18 +29,26 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
* View model for describing the system's current mobile cellular connections. The result is a list
* of [MobileIconViewModel]s which describe the individual icons and can be bound to
- * [ModernStatusBarMobileView]
+ * [ModernStatusBarMobileView].
*/
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
class MobileIconsViewModel
@Inject
constructor(
- val subscriptionIdsFlow: StateFlow<List<Int>>,
val logger: MobileViewLogger,
private val verboseLogger: VerboseMobileViewLogger,
private val interactor: MobileIconsInteractor,
@@ -51,22 +59,43 @@ constructor(
) {
@VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
+ val subscriptionIdsFlow: StateFlow<List<Int>> =
+ interactor.filteredSubscriptions
+ .mapLatest { subscriptions ->
+ subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+ private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> =
+ subscriptionIdsFlow
+ .map {
+ if (it.isEmpty()) {
+ null
+ } else {
+ // Mobile icons get reversed by [StatusBarIconController], so the last element
+ // in this list will show up visually first.
+ commonViewModelForSub(it.last())
+ }
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+ /**
+ * A flow that emits `true` if the mobile sub that's displayed first visually is showing its
+ * network type icon and `false` otherwise.
+ */
+ val firstMobileSubShowingNetworkTypeIcon: StateFlow<Boolean> =
+ firstMobileSubViewModel
+ .flatMapLatest { firstMobileSubViewModel ->
+ firstMobileSubViewModel?.networkTypeIcon?.map { it != null } ?: flowOf(false)
+ }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
init {
scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
}
fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
- val common =
- mobileIconSubIdCache[subId]
- ?: MobileIconViewModel(
- subId,
- interactor.createMobileConnectionInteractorForSubId(subId),
- airplaneModeInteractor,
- constants,
- scope,
- )
- .also { mobileIconSubIdCache[subId] = it }
-
+ val common = commonViewModelForSub(subId)
return LocationBasedMobileViewModel.viewModelForLocation(
common,
statusBarPipelineFlags,
@@ -75,34 +104,20 @@ constructor(
)
}
+ private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon {
+ return mobileIconSubIdCache[subId]
+ ?: MobileIconViewModel(
+ subId,
+ interactor.createMobileConnectionInteractorForSubId(subId),
+ airplaneModeInteractor,
+ constants,
+ scope,
+ )
+ .also { mobileIconSubIdCache[subId] = it }
+ }
+
private fun removeInvalidModelsFromCache(subIds: List<Int>) {
val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
}
-
- @SysUISingleton
- class Factory
- @Inject
- constructor(
- private val logger: MobileViewLogger,
- private val verboseLogger: VerboseMobileViewLogger,
- private val interactor: MobileIconsInteractor,
- private val airplaneModeInteractor: AirplaneModeInteractor,
- private val constants: ConnectivityConstants,
- @Application private val scope: CoroutineScope,
- private val statusBarPipelineFlags: StatusBarPipelineFlags,
- ) {
- fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
- return MobileIconsViewModel(
- subscriptionIdsFlow,
- logger,
- verboseLogger,
- interactor,
- airplaneModeInteractor,
- constants,
- scope,
- statusBarPipelineFlags,
- )
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 6479f3d9f8a6..731f1e028470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -44,11 +44,11 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnecti
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Ethernet
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Mobile
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel.Wifi
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.tuner.TunerService
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -68,12 +68,12 @@ interface ConnectivityRepository {
val defaultConnections: StateFlow<DefaultConnectionModel>
}
-@OptIn(ExperimentalCoroutinesApi::class)
+@SuppressLint("MissingPermission")
@SysUISingleton
class ConnectivityRepositoryImpl
@Inject
constructor(
- connectivityManager: ConnectivityManager,
+ private val connectivityManager: ConnectivityManager,
private val connectivitySlots: ConnectivitySlots,
context: Context,
dumpManager: DumpManager,
@@ -144,15 +144,14 @@ constructor(
) {
logger.logOnDefaultCapabilitiesChanged(network, networkCapabilities)
+ val wifiInfo =
+ networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
val isWifiDefault =
- networkCapabilities.hasTransport(TRANSPORT_WIFI) ||
- networkCapabilities.getMainOrUnderlyingWifiInfo() != null
+ networkCapabilities.hasTransport(TRANSPORT_WIFI) || wifiInfo != null
val isMobileDefault =
networkCapabilities.hasTransport(TRANSPORT_CELLULAR)
- val isCarrierMergedDefault =
- networkCapabilities
- .getMainOrUnderlyingWifiInfo()
- ?.isCarrierMerged == true
+ val isCarrierMergedDefault = wifiInfo?.isCarrierMerged == true
val isEthernetDefault =
networkCapabilities.hasTransport(TRANSPORT_ETHERNET)
@@ -209,7 +208,32 @@ constructor(
* always use [WifiInfo] if it's available, so we need to check the underlying transport
* info.
*/
- fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(): WifiInfo? {
+ fun NetworkCapabilities.getMainOrUnderlyingWifiInfo(
+ connectivityManager: ConnectivityManager,
+ ): WifiInfo? {
+ val mainWifiInfo = this.getMainWifiInfo()
+ if (mainWifiInfo != null) {
+ return mainWifiInfo
+ }
+ // Only CELLULAR networks may have underlying wifi information that's relevant to SysUI,
+ // so skip the underlying network check if it's not CELLULAR.
+ if (!this.hasTransport(TRANSPORT_CELLULAR)) {
+ return mainWifiInfo
+ }
+
+ // Some connections, like VPN connections, may have underlying networks that are
+ // eventually traced to a wifi or carrier merged connection. So, check those underlying
+ // networks for possible wifi information as well. See b/225902574.
+ return this.underlyingNetworks?.firstNotNullOfOrNull { underlyingNetwork ->
+ connectivityManager.getNetworkCapabilities(underlyingNetwork)?.getMainWifiInfo()
+ }
+ }
+
+ /**
+ * Checks the network capabilities for wifi info, but does *not* check the underlying
+ * networks. See [getMainOrUnderlyingWifiInfo].
+ */
+ private fun NetworkCapabilities.getMainWifiInfo(): WifiInfo? {
// Wifi info can either come from a WIFI Transport, or from a CELLULAR transport for
// virtual networks like VCN.
val canHaveWifiInfo =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 08c14e743bb6..f800cf496ca9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -33,6 +33,15 @@ interface WifiRepository {
/** Observable for the current wifi network activity. */
val wifiActivity: StateFlow<DataActivityModel>
+
+ /**
+ * Returns true if the device is currently connected to a wifi network with a valid SSID and
+ * false otherwise.
+ */
+ fun isWifiConnectedWithValidSsid(): Boolean {
+ val currentNetwork = wifiNetwork.value
+ return currentNetwork is WifiNetworkModel.Active && currentNetwork.hasValidSsid()
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index f80aa688268f..b37c44a2f8cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -138,7 +138,8 @@ constructor(
wifiNetworkChangeEvents.tryEmit(Unit)
- val wifiInfo = networkCapabilities.getMainOrUnderlyingWifiInfo()
+ val wifiInfo =
+ networkCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel =
createWifiNetworkModel(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index 96ab074c6e56..1a41abf031bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
-import android.net.wifi.WifiManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -76,7 +75,7 @@ constructor(
when {
info.isPasspointAccessPoint || info.isOnlineSignUpForPasspointAccessPoint ->
info.passpointProviderFriendlyName
- info.ssid != WifiManager.UNKNOWN_SSID -> info.ssid
+ info.hasValidSsid() -> info.ssid
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 0923d7848d8c..4b33c88cea30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.shared.model
+import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.telephony.SubscriptionManager
import androidx.annotation.VisibleForTesting
import com.android.systemui.log.table.Diffable
@@ -223,6 +224,11 @@ sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
}
}
+ /** Returns true if this network has a valid SSID and false otherwise. */
+ fun hasValidSsid(): Boolean {
+ return ssid != null && ssid != UNKNOWN_SSID
+ }
+
override fun logDiffs(prevVal: WifiNetworkModel, row: TableRowLogger) {
if (prevVal !is Active) {
logFull(row)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 9e8c814ca2a9..e819c4fc96ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -36,7 +36,6 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -64,6 +63,7 @@ object WifiViewBinder {
val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
val activityContainerView = view.requireViewById<View>(R.id.inout_container)
val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer)
+ val signalSpacer = view.requireViewById<View>(R.id.wifi_signal_spacer)
view.isVisible = true
iconView.isVisible = true
@@ -133,6 +133,12 @@ object WifiViewBinder {
}
}
+ launch {
+ viewModel.isSignalSpacerVisible.distinctUntilChanged().collect { visible ->
+ signalSpacer.isVisible = visible
+ }
+ }
+
try {
awaitCancellation()
} finally {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index c9a0786acc72..d9c214452ef1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -30,8 +30,8 @@ import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
+import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON
import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -39,7 +39,9 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiIntera
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import java.util.function.Supplier
import javax.inject.Inject
+import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -53,24 +55,24 @@ import kotlinx.coroutines.flow.stateIn
/**
* Models the UI state for the status bar wifi icon.
*
- * This class exposes three view models, one per status bar location: [home], [keyguard], and [qs].
- * In order to get the UI state for the wifi icon, you must use one of those view models (whichever
- * is correct for your location).
- *
- * Internally, this class maintains the current state of the wifi icon and notifies those three view
- * models of any changes.
+ * This is a singleton so that we don't have duplicate logs and should *not* be used directly to
+ * control views. Instead, use an instance of [LocationBasedWifiViewModel]. See
+ * [LocationBasedWifiViewModel.viewModelForLocation].
*/
@SysUISingleton
class WifiViewModel
@Inject
constructor(
airplaneModeViewModel: AirplaneModeViewModel,
+ // TODO(b/238425913): The wifi icon shouldn't need to consume mobile information. A
+ // container-level view model should do the work instead.
+ @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON)
+ shouldShowSignalSpacerProvider: Supplier<Flow<Boolean>>,
connectivityConstants: ConnectivityConstants,
private val context: Context,
@WifiTableLog wifiTableLogBuffer: TableLogBuffer,
interactor: WifiInteractor,
@Application private val scope: CoroutineScope,
- statusBarPipelineFlags: StatusBarPipelineFlags,
wifiConstants: WifiConstants,
) : WifiViewModelCommon {
/** Returns the icon to use based on the given network. */
@@ -183,6 +185,8 @@ constructor(
override val isAirplaneSpacerVisible: Flow<Boolean> =
airplaneModeViewModel.isAirplaneModeIconVisible
+ override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get()
+
companion object {
@StringRes
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt
index eccf02397a82..617e19200a0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt
@@ -39,4 +39,7 @@ interface WifiViewModelCommon {
/** True if the airplane spacer view should be visible. */
val isAirplaneSpacerVisible: Flow<Boolean>
+
+ /** True if the spacer between the wifi icon and the RAT icon should be visible. */
+ val isSignalSpacerVisible: Flow<Boolean>
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 654ba04eba7a..1e63b2a4b3e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -21,6 +21,8 @@ import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
import static android.os.BatteryManager.EXTRA_HEALTH;
import static android.os.BatteryManager.EXTRA_PRESENT;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
+
import android.annotation.WorkerThread;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -169,7 +171,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
@Override
public void setPowerSaveMode(boolean powerSave, View view) {
if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
- BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
+ BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
+ SAVER_ENABLED_QS);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index f1269f2b012a..673819b20e4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -38,14 +38,14 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import dagger.Lazy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Objects;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
*/
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
index e9f0dcb4eb51..928e0115287d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java
@@ -61,6 +61,7 @@ import javax.inject.Inject;
* Manages the user switcher on the Keyguard.
*/
@KeyguardUserSwitcherScope
+@Deprecated
public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> {
private static final String TAG = "KeyguardUserSwitcherController";
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index a20a5b2fdbbc..e819f946a6d6 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -31,6 +31,7 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
+import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import com.android.internal.widget.CachingIconView
@@ -180,8 +181,9 @@ constructor(
// Button
val buttonView = currentView.requireViewById<TextView>(R.id.end_button)
- if (newInfo.endItem is ChipbarEndItem.Button) {
- TextViewBinder.bind(buttonView, newInfo.endItem.text)
+ val hasButton = newInfo.endItem is ChipbarEndItem.Button
+ if (hasButton) {
+ TextViewBinder.bind(buttonView, (newInfo.endItem as ChipbarEndItem.Button).text)
val onClickListener =
View.OnClickListener { clickedView ->
@@ -196,6 +198,12 @@ constructor(
buttonView.visibility = View.GONE
}
+ currentView
+ .getInnerView()
+ .setEndPadding(
+ if (hasButton) R.dimen.chipbar_outer_padding_half else R.dimen.chipbar_outer_padding
+ )
+
// ---- Overall accessibility ----
val iconDesc = newInfo.startIcon.icon.contentDescription
val loadedIconDesc =
@@ -309,6 +317,15 @@ constructor(
viewUtil.setRectToViewWindowLocation(view, outRect)
}
+ private fun View.setEndPadding(@DimenRes endPaddingDimen: Int) {
+ this.setPaddingRelative(
+ this.paddingStart,
+ this.paddingTop,
+ context.resources.getDimensionPixelSize(endPaddingDimen),
+ this.paddingBottom,
+ )
+ }
+
private fun Boolean.visibleIfTrue(): Int {
return if (this) {
View.VISIBLE
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 6e58f2296c86..52f2d11f814e 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay.chipbar
import android.os.VibrationEffect
import android.view.View
-import androidx.annotation.ColorRes
+import androidx.annotation.AttrRes
import com.android.systemui.R
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.shared.model.TintedIcon
@@ -49,7 +49,9 @@ data class ChipbarInfo(
override val priority: ViewPriority,
) : TemporaryViewInfo() {
companion object {
- @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color
+ // LINT.IfChange
+ @AttrRes val DEFAULT_ICON_TINT = com.android.internal.R.attr.materialColorOnSecondaryFixed
+ // LINT.ThenChange(systemui/res/layout/chipbar.xml)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
index 05e566690f57..29f16c7b924a 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java
@@ -272,10 +272,10 @@ public class SystemUIToast implements ToastPlugin.Toast {
private static boolean showApplicationIcon(ApplicationInfo appInfo,
PackageManager packageManager) {
- if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) {
+ if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) {
return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null;
}
- return !hasFlag(appInfo.flags, FLAG_SYSTEM);
+ return true;
}
private static boolean hasFlag(int flags, int flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
index 7c61e7108265..c587f2edd601 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardSettingsPopupMenuModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt
@@ -12,19 +12,21 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
- *
*/
-package com.android.systemui.keyguard.domain.model
+package com.android.systemui.util.kotlin
-import com.android.systemui.common.shared.model.Position
+import dagger.Lazy
+import kotlin.reflect.KProperty
-/** Models a settings popup menu for the lock screen. */
-data class KeyguardSettingsPopupMenuModel(
- /** Where the menu should be anchored, roughly in screen space. */
- val position: Position,
- /** Callback to invoke when the menu gets clicked by the user. */
- val onClicked: () -> Unit,
- /** Callback to invoke when the menu gets dismissed by the user. */
- val onDismissed: () -> Unit,
-)
+/**
+ * Extension operator that allows developers to use [dagger.Lazy] as a property delegate:
+ * ```kotlin
+ * class MyClass @Inject constructor(
+ * lazyDependency: dagger.Lazy<Foo>,
+ * ) {
+ * val dependency: Foo by lazyDependency
+ * }
+ * ```
+ */
+operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 209ea41fed61..58cffa761a66 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -58,6 +58,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QSTile;
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.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -418,6 +419,7 @@ public class GarbageMonitor implements Dumpable {
@Inject
public MemoryTile(
QSHost host,
+ QsEventLogger uiEventLogger,
@Background Looper backgroundLooper,
@Main Handler mainHandler,
FalsingManager falsingManager,
@@ -428,7 +430,7 @@ public class GarbageMonitor implements Dumpable {
GarbageMonitor monitor,
PanelInteractor panelInteractor
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
gm = monitor;
mPanelInteractor = panelInteractor;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index 35af44442ca9..e3ed2b405fb0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -16,20 +16,34 @@
package com.android.systemui.volume;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
-import android.os.CountDownTimer;
+import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
/**
* A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}:
@@ -53,34 +67,58 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
* communication between the native audio framework that implements the dose computation and the
* audio service.
*/
-public abstract class CsdWarningDialog extends SystemUIDialog
+public class CsdWarningDialog extends SystemUIDialog
implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
private static final String TAG = Util.logTag(CsdWarningDialog.class);
private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
// time after which action is taken when the user hasn't ack'd or dismissed the dialog
- private static final int NO_ACTION_TIMEOUT_MS = 5000;
+ public static final int NO_ACTION_TIMEOUT_MS = 5000;
private final Context mContext;
private final AudioManager mAudioManager;
private final @AudioManager.CsdWarning int mCsdWarning;
private final Object mTimerLock = new Object();
+
/**
* Timer to keep track of how long the user has before an action (here volume reduction) is
* taken on their behalf.
*/
@GuardedBy("mTimerLock")
- private final CountDownTimer mNoUserActionTimer;
+ private Runnable mNoUserActionRunnable;
+ private Runnable mCancelScheduledNoUserActionRunnable = null;
+
+ private final DelayableExecutor mDelayableExecutor;
+ private NotificationManager mNotificationManager;
+ private Runnable mOnCleanup;
private long mShowTime;
- public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context,
- AudioManager audioManager) {
+ /**
+ * To inject dependencies and allow for easier testing
+ */
+ @AssistedFactory
+ public interface Factory {
+ /**
+ * Create a dialog object
+ */
+ CsdWarningDialog create(int csdWarning, Runnable onCleanup);
+ }
+
+ @AssistedInject
+ public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context,
+ AudioManager audioManager, NotificationManager notificationManager,
+ @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) {
super(context);
mCsdWarning = csdWarning;
mContext = context;
mAudioManager = audioManager;
+ mNotificationManager = notificationManager;
+ mOnCleanup = onCleanup;
+
+ mDelayableExecutor = delayableExecutor;
+
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
setShowForAllUsers(true);
setMessage(mContext.getString(getStringForWarning(csdWarning)));
@@ -95,25 +133,24 @@ public abstract class CsdWarningDialog extends SystemUIDialog
Context.RECEIVER_EXPORTED_UNAUDITED);
if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
- mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) {
- @Override
- public void onTick(long millisUntilFinished) { }
-
- @Override
- public void onFinish() {
- if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
- // unlike on the 5x dose repeat, level is only reduced to RS1
- // when the warning is not acknowledged quick enough
- mAudioManager.lowerVolumeToRs1();
- }
+ mNoUserActionRunnable = () -> {
+ if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+ // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning
+ // is not acknowledged quickly enough
+ mAudioManager.lowerVolumeToRs1();
+ sendNotification();
}
};
} else {
- mNoUserActionTimer = null;
+ mNoUserActionRunnable = null;
}
}
- protected abstract void cleanUp();
+ private void cleanUp() {
+ if (mOnCleanup != null) {
+ mOnCleanup.run();
+ }
+ }
// NOT overriding onKeyDown as we're not allowing a dismissal on any key other than
// VOLUME_DOWN, and for this, we don't need to track if it's the start of a new
@@ -153,12 +190,9 @@ public abstract class CsdWarningDialog extends SystemUIDialog
super.onStart();
mShowTime = System.currentTimeMillis();
synchronized (mTimerLock) {
- if (mNoUserActionTimer != null) {
- new Thread(() -> {
- synchronized (mTimerLock) {
- mNoUserActionTimer.start();
- }
- }).start();
+ if (mNoUserActionRunnable != null) {
+ mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed(
+ mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS);
}
}
}
@@ -166,8 +200,8 @@ public abstract class CsdWarningDialog extends SystemUIDialog
@Override
protected void onStop() {
synchronized (mTimerLock) {
- if (mNoUserActionTimer != null) {
- mNoUserActionTimer.cancel();
+ if (mCancelScheduledNoUserActionRunnable != null) {
+ mCancelScheduledNoUserActionRunnable.run();
}
}
}
@@ -212,4 +246,32 @@ public abstract class CsdWarningDialog extends SystemUIDialog
Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception());
return com.android.internal.R.string.csd_dose_reached_warning;
}
+
+
+ /**
+ * In case user did not respond to the dialog, they still need to know volume was lowered.
+ */
+ private void sendNotification() {
+ Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+ FLAG_IMMUTABLE);
+
+ String text = mContext.getString(R.string.csd_system_lowered_text);
+ String title = mContext.getString(R.string.csd_lowered_title);
+
+ Notification.Builder builder =
+ new Notification.Builder(mContext, NotificationChannels.ALERTS)
+ .setSmallIcon(R.drawable.hearing)
+ .setContentTitle(title)
+ .setContentText(text)
+ .setContentIntent(pendingIntent)
+ .setStyle(new Notification.BigTextStyle().bigText(text))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setLocalOnly(true)
+ .setAutoCancel(true)
+ .setCategory(Notification.CATEGORY_SYSTEM);
+
+ mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO,
+ builder.build());
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 95cc12a48bb2..3c007f99a654 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -263,6 +263,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
private final ConfigurationController mConfigurationController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
private final VolumePanelFactory mVolumePanelFactory;
+ private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
private final ActivityStarter mActivityStarter;
private boolean mShowing;
@@ -311,6 +312,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
InteractionJankMonitor interactionJankMonitor,
DeviceConfigProxy deviceConfigProxy,
Executor executor,
+ CsdWarningDialog.Factory csdWarningDialogFactory,
DumpManager dumpManager) {
mContext =
new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -322,6 +324,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
mConfigurationController = configurationController;
mMediaOutputDialogFactory = mediaOutputDialogFactory;
mVolumePanelFactory = volumePanelFactory;
+ mCsdWarningDialogFactory = csdWarningDialogFactory;
mActivityStarter = activityStarter;
mShowActiveStreamOnly = showActiveStreamOnly();
mHasSeenODICaptionsTooltip =
@@ -2084,21 +2087,21 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
rescheduleTimeoutH();
}
- private void showCsdWarningH(int csdWarning, int durationMs) {
+ @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
synchronized (mSafetyWarningLock) {
+
if (mCsdDialog != null) {
return;
}
- mCsdDialog = new CsdWarningDialog(csdWarning,
- mContext, mController.getAudioManager()) {
- @Override
- protected void cleanUp() {
- synchronized (mSafetyWarningLock) {
- mCsdDialog = null;
- }
- recheckH(null);
+
+ final Runnable cleanUp = () -> {
+ synchronized (mSafetyWarningLock) {
+ mCsdDialog = null;
}
+ recheckH(null);
};
+
+ mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp);
mCsdDialog.show();
}
recheckH(null);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 0ab6c690e1e1..14d3ca334073 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,17 +30,18 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.volume.CsdWarningDialog;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.volume.VolumeDialogComponent;
import com.android.systemui.volume.VolumeDialogImpl;
import com.android.systemui.volume.VolumePanelFactory;
-import java.util.concurrent.Executor;
-
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import java.util.concurrent.Executor;
+
/** Dagger Module for code in the volume package. */
@Module
@@ -63,6 +64,7 @@ public interface VolumeModule {
InteractionJankMonitor interactionJankMonitor,
DeviceConfigProxy deviceConfigProxy,
@Main Executor executor,
+ CsdWarningDialog.Factory csdFactory,
DumpManager dumpManager) {
VolumeDialogImpl impl = new VolumeDialogImpl(
context,
@@ -76,6 +78,7 @@ public interface VolumeModule {
interactionJankMonitor,
deviceConfigProxy,
executor,
+ csdFactory,
dumpManager);
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
impl.setAutomute(true);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 08f7eaee3011..a4b093d1832a 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -241,8 +241,8 @@ public class BubblesManager {
// Store callback in a field so it won't get GC'd
mStatusBarWindowCallback =
- (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded,
- isDreaming) ->
+ (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, isDozing,
+ panelExpanded, isDreaming) ->
mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
deleted file mode 100644
index e93e86291535..000000000000
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java
+++ /dev/null
@@ -1,174 +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 androidx.core.animation;
-
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.AndroidRuntimeException;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to
- * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static
- * list of callbacks.
- *
- * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code.
- */
-public final class AnimatorTestRule2 implements TestRule {
-
- class TestAnimationHandler extends AnimationHandler {
- TestAnimationHandler() {
- super(new TestProvider());
- }
-
- List<AnimationFrameCallback> animationCallbacks = new ArrayList<>();
-
- @Override
- void addAnimationFrameCallback(AnimationFrameCallback callback) {
- animationCallbacks.add(callback);
- callback.doAnimationFrame(getCurrentTime());
- }
-
- @Override
- public void removeCallback(AnimationFrameCallback callback) {
- int id = animationCallbacks.indexOf(callback);
- if (id >= 0) {
- animationCallbacks.set(id, null);
- }
- }
-
- void onAnimationFrame(long frameTime) {
- for (int i = 0; i < animationCallbacks.size(); i++) {
- final AnimationFrameCallback callback = animationCallbacks.get(i);
- if (callback == null) {
- continue;
- }
- callback.doAnimationFrame(frameTime);
- }
- }
-
- @Override
- void autoCancelBasedOn(ObjectAnimator objectAnimator) {
- for (int i = animationCallbacks.size() - 1; i >= 0; i--) {
- AnimationFrameCallback cb = animationCallbacks.get(i);
- if (cb == null) {
- continue;
- }
- if (objectAnimator.shouldAutoCancel(cb)) {
- ((Animator) animationCallbacks.get(i)).cancel();
- }
- }
- }
- }
-
- final TestAnimationHandler mTestHandler;
- final long mStartTime;
- private long mTotalTimeDelta = 0;
- private final Object mLock = new Object();
-
- public AnimatorTestRule2() {
- mStartTime = SystemClock.uptimeMillis();
- mTestHandler = new TestAnimationHandler();
- }
-
- @NonNull
- @Override
- public Statement apply(@NonNull final Statement base, @NonNull Description description) {
- return new Statement() {
- @Override
- public void evaluate() throws Throwable {
- AnimationHandler.setTestHandler(mTestHandler);
- try {
- base.evaluate();
- } finally {
- AnimationHandler.setTestHandler(null);
- }
- }
- };
- }
-
- /**
- * Advances the animation clock by the given amount of delta in milliseconds. This call will
- * produce an animation frame to all the ongoing animations. This method needs to be
- * called on the same thread as {@link Animator#start()}.
- *
- * @param timeDelta the amount of milliseconds to advance
- */
- public void advanceTimeBy(long timeDelta) {
- if (Looper.myLooper() == null) {
- // Throw an exception
- throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be"
- + "called on Looper threads");
- }
- synchronized (mLock) {
- // Advance time & pulse a frame
- mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta;
- }
- // produce a frame
- mTestHandler.onAnimationFrame(getCurrentTime());
- }
-
-
- /**
- * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a
- * different time than the time tracked by {@link SystemClock} This method needs to be called on
- * the same thread as {@link Animator#start()}.
- */
- public long getCurrentTime() {
- if (Looper.myLooper() == null) {
- // Throw an exception
- throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be"
- + "called on Looper threads");
- }
- synchronized (mLock) {
- return mStartTime + mTotalTimeDelta;
- }
- }
-
-
- private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider {
- TestProvider() {
- }
-
- @Override
- public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) {
- callback.doAnimationFrame(getCurrentTime());
- }
-
- @Override
- public void postFrameCallback() {
- }
-
- @Override
- public void setFrameDelay(long delay) {
- }
-
- @Override
- public long getFrameDelay() {
- return 0;
- }
- }
-}
-
diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
index bddd60b5970a..e7738aff6278 100644
--- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
+++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt
@@ -30,7 +30,7 @@ import org.junit.runner.RunWith
@RunWithLooper(setAsMainLooper = true)
class AnimatorTestRuleTest : SysuiTestCase() {
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Test
fun testA() {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index ecf7e0d46373..5557efa97e3e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -38,8 +38,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
import android.provider.Settings;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -52,6 +50,8 @@ import android.text.TextUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository;
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel;
import com.android.systemui.telephony.TelephonyListenerManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -88,8 +88,7 @@ public class CarrierTextManagerTest extends SysuiTestCase {
private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_CARRIER_ID, 0xFFFFFF, "",
DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
- @Mock
- private WifiManager mWifiManager;
+ private FakeWifiRepository mWifiRepository = new FakeWifiRepository();
@Mock
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
@@ -121,7 +120,6 @@ public class CarrierTextManagerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext.addMockSystemService(WifiManager.class, mWifiManager);
mContext.addMockSystemService(PackageManager.class, mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
mContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
@@ -144,7 +142,7 @@ public class CarrierTextManagerTest extends SysuiTestCase {
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
mCarrierTextManager = new CarrierTextManager.Builder(
- mContext, mContext.getResources(), mWifiManager,
+ mContext, mContext.getResources(), mWifiRepository,
mTelephonyManager, mTelephonyListenerManager, mWakefulnessLifecycle, mMainExecutor,
mBgExecutor, mKeyguardUpdateMonitor)
.setShowAirplaneMode(true)
@@ -364,7 +362,11 @@ public class CarrierTextManagerTest extends SysuiTestCase {
when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
TelephonyManager.SIM_STATE_READY);
when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
- mockWifi();
+
+ assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
+ mWifiRepository.setWifiNetwork(
+ new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+ assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
ServiceState ss = mock(ServiceState.class);
@@ -385,13 +387,6 @@ public class CarrierTextManagerTest extends SysuiTestCase {
assertNotEquals(AIRPLANE_MODE_TEXT, captor.getValue().carrierText);
}
- private void mockWifi() {
- when(mWifiManager.isWifiEnabled()).thenReturn(true);
- WifiInfo wifiInfo = mock(WifiInfo.class);
- when(wifiInfo.getBSSID()).thenReturn("");
- when(mWifiManager.getConnectionInfo()).thenReturn(wifiInfo);
- }
-
@Test
public void testCreateInfo_noSubscriptions() {
reset(mCarrierTextCallback);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 2f627cb5d9d7..fc906dedfa1d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -48,8 +48,10 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockAnimations;
import com.android.systemui.plugins.ClockController;
import com.android.systemui.plugins.ClockEvents;
+import com.android.systemui.plugins.ClockFaceConfig;
import com.android.systemui.plugins.ClockFaceController;
import com.android.systemui.plugins.ClockFaceEvents;
+import com.android.systemui.plugins.ClockTickRate;
import com.android.systemui.plugins.log.LogBuffer;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.clocks.AnimatableClockView;
@@ -185,6 +187,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mClockController.getAnimations()).thenReturn(mClockAnimations);
when(mClockRegistry.createCurrentClock()).thenReturn(mClockController);
when(mClockEventController.getClock()).thenReturn(mClockController);
+ when(mSmallClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
+ when(mLargeClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false));
mSliceView = new View(getContext());
when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView);
@@ -367,6 +373,28 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
}
@Test
+ public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() {
+ ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class);
+ when(mSmartspaceController.isEnabled()).thenReturn(true);
+ when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true);
+ when(mSmartspaceController.isWeatherEnabled()).thenReturn(true);
+ mController.init();
+ mExecutor.runAllReady();
+ assertEquals(View.VISIBLE, mFakeDateView.getVisibility());
+
+ when(mSmallClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+ when(mLargeClockController.getConfig())
+ .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true));
+ verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture());
+ listenerArgumentCaptor.getValue().onCurrentClockChanged();
+
+ mExecutor.runAllReady();
+ assertEquals(View.INVISIBLE, mFakeDateView.getVisibility());
+ }
+
+ @Test
public void testGetClock_nullClock_returnsNull() {
when(mClockEventController.getClock()).thenReturn(null);
assertNull(mController.getClock());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d760189bcfd6..3e4fd891a668 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -67,6 +67,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -208,7 +209,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
- mTelephonyManager, mViewMediatorCallback, mAudioManager);
+ mTelephonyManager, mViewMediatorCallback, mAudioManager,
+ mock(KeyguardFaceAuthInteractor.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 48f7d924667e..2c1d2adb11ee 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -24,9 +23,13 @@ import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.plugins.ClockConfig;
import com.android.systemui.plugins.ClockController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -45,26 +48,21 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class KeyguardStatusViewControllerTest extends SysuiTestCase {
- @Mock
- private KeyguardStatusView mKeyguardStatusView;
- @Mock
- private KeyguardSliceViewController mKeyguardSliceViewController;
- @Mock
- private KeyguardClockSwitchController mKeyguardClockSwitchController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- ConfigurationController mConfigurationController;
- @Mock
- DozeParameters mDozeParameters;
- @Mock
- ScreenOffAnimationController mScreenOffAnimationController;
+ @Mock private KeyguardStatusView mKeyguardStatusView;
+ @Mock private KeyguardSliceViewController mKeyguardSliceViewController;
+ @Mock private KeyguardClockSwitchController mKeyguardClockSwitchController;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock private ConfigurationController mConfigurationController;
+ @Mock private DozeParameters mDozeParameters;
+ @Mock private ScreenOffAnimationController mScreenOffAnimationController;
+ @Mock private KeyguardLogger mKeyguardLogger;
+ @Mock private KeyguardStatusViewController mControllerMock;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private InteractionJankMonitor mInteractionJankMonitor;
+
@Captor
private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
- @Mock
- KeyguardLogger mKeyguardLogger;
private KeyguardStatusViewController mController;
@@ -81,7 +79,18 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
mConfigurationController,
mDozeParameters,
mScreenOffAnimationController,
- mKeyguardLogger);
+ mKeyguardLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor) {
+ @Override
+ void setProperty(
+ AnimatableProperty property,
+ float value,
+ boolean animate) {
+ // Route into the mock version for verification
+ mControllerMock.setProperty(property, value, animate);
+ }
+ };
}
@Test
@@ -118,10 +127,32 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
}
@Test
- public void getClock_forwardsToClockSwitch() {
+ public void updatePosition_primaryClockAnimation() {
ClockController mockClock = mock(ClockController.class);
when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+ when(mockClock.getConfig()).thenReturn(new ClockConfig(false, false, true));
+
+ mController.updatePosition(10, 15, 20f, true);
+
+ verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+ verify(mKeyguardClockSwitchController).updatePosition(
+ 10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+ verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true);
+ verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true);
+ }
+
+ @Test
+ public void updatePosition_alternateClockAnimation() {
+ ClockController mockClock = mock(ClockController.class);
+ when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock);
+ when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true, true));
+
+ mController.updatePosition(10, 15, 20f, true);
- assertEquals(mockClock, mController.getClockController());
+ verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true);
+ verify(mKeyguardClockSwitchController).updatePosition(
+ 10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true);
+ verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true);
+ verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 08813a7fb48a..3eb9590c0b95 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -20,9 +20,12 @@ import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_CONVENIENCE;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_PRIMARY_BOUNCER_SHOWN;
import static android.hardware.face.FaceAuthenticateOptions.AUTHENTICATE_REASON_STARTED_WAKING_UP;
import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
@@ -41,6 +44,8 @@ import static com.android.systemui.statusbar.policy.DevicePostureController.DEVI
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertEquals;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -79,7 +84,6 @@ import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
-import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorProperties;
@@ -283,33 +287,13 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Before
public void setup() throws RemoteException {
MockitoAnnotations.initMocks(this);
-
- mFaceSensorProperties =
- List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false));
- when(mFaceManager.isHardwareDetected()).thenReturn(true);
- when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
- when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
- mFingerprintSensorProperties = List.of(
- new FingerprintSensorPropertiesInternal(1 /* sensorId */,
- FingerprintSensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */,
- "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */)),
- FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
- false /* resetLockoutRequiresHAT */));
- when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
- when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
- when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
- mFingerprintSensorProperties);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
when(mStrongAuthTracker
- .isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
+ .isUnlockingWithBiometricAllowed(anyBoolean() /* isClass3Biometric */))
.thenReturn(true);
when(mTelephonyManager.getServiceStateForSubscriber(anyInt()))
.thenReturn(new ServiceState());
@@ -346,20 +330,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
anyInt());
mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
-
- ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
- ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
- verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
- mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
- mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
-
- ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
- ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
- verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
- fingerprintCaptor.capture());
- mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
- mFingerprintAuthenticatorsRegisteredCallback
- .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+ captureAuthenticatorsRegisteredCallbacks();
+ setupFaceAuth(/* isClass3 */ false);
+ setupFingerprintAuth(/* isClass3 */ true);
verify(mBiometricManager)
.registerEnabledOnKeyguardCallback(mBiometricEnabledCallbackArgCaptor.capture());
@@ -381,8 +354,64 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mAuthController.areAllFingerprintAuthenticatorsRegistered()).thenReturn(true);
}
+ private void captureAuthenticatorsRegisteredCallbacks() throws RemoteException {
+ ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> faceCaptor =
+ ArgumentCaptor.forClass(IFaceAuthenticatorsRegisteredCallback.class);
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(faceCaptor.capture());
+ mFaceAuthenticatorsRegisteredCallback = faceCaptor.getValue();
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+
+ ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> fingerprintCaptor =
+ ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+ fingerprintCaptor.capture());
+ mFingerprintAuthenticatorsRegisteredCallback = fingerprintCaptor.getValue();
+ mFingerprintAuthenticatorsRegisteredCallback
+ .onAllAuthenticatorsRegistered(mFingerprintSensorProperties);
+ }
+
+ private void setupFaceAuth(boolean isClass3) throws RemoteException {
+ when(mFaceManager.isHardwareDetected()).thenReturn(true);
+ when(mAuthController.isFaceAuthEnrolled(anyInt())).thenReturn(true);
+ mFaceSensorProperties =
+ List.of(createFaceSensorProperties(/* supportsFaceDetection = */ false, isClass3));
+ when(mFaceManager.getSensorPropertiesInternal()).thenReturn(mFaceSensorProperties);
+ mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
+ assertEquals(isClass3, mKeyguardUpdateMonitor.isFaceClass3());
+ }
+
+ private void setupFingerprintAuth(boolean isClass3) throws RemoteException {
+ when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
+ when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
+ mFingerprintSensorProperties = List.of(
+ createFingerprintSensorPropertiesInternal(TYPE_UDFPS_OPTICAL, isClass3));
+ when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
+ mFingerprintSensorProperties);
+ mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
+ mFingerprintSensorProperties);
+ assertEquals(isClass3, mKeyguardUpdateMonitor.isFingerprintClass3());
+ }
+
+ private FingerprintSensorPropertiesInternal createFingerprintSensorPropertiesInternal(
+ @FingerprintSensorProperties.SensorType int sensorType,
+ boolean isClass3) {
+ final List<ComponentInfoInternal> componentInfo =
+ List.of(new ComponentInfoInternal("fingerprintSensor" /* componentId */,
+ "vendor/model/revision" /* hardwareVersion */,
+ "1.01" /* firmwareVersion */,
+ "00000001" /* serialNumber */, "" /* softwareVersion */));
+ return new FingerprintSensorPropertiesInternal(
+ FINGERPRINT_SENSOR_ID,
+ isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
+ 1 /* maxEnrollmentsPerUser */,
+ componentInfo,
+ sensorType,
+ true /* resetLockoutRequiresHardwareAuthToken */);
+ }
+
@NonNull
- private FaceSensorPropertiesInternal createFaceSensorProperties(boolean supportsFaceDetection) {
+ private FaceSensorPropertiesInternal createFaceSensorProperties(
+ boolean supportsFaceDetection, boolean isClass3) {
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
"vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
@@ -391,10 +420,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
"" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
"vendor/version/revision" /* softwareVersion */));
-
return new FaceSensorPropertiesInternal(
- 0 /* id */,
- FaceSensorProperties.STRENGTH_STRONG,
+ FACE_SENSOR_ID /* id */,
+ isClass3 ? STRENGTH_STRONG : STRENGTH_CONVENIENCE,
1 /* maxTemplatesAllowed */,
componentInfo,
FaceSensorProperties.TYPE_UNKNOWN,
@@ -686,7 +714,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFaceAllowed_strongAuthTrackerUnlockingWithBiometricAllowed() {
// GIVEN unlocking with biometric is allowed
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
// THEN unlocking with face and fp is allowed
Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -706,12 +734,31 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
- public void testUnlockingWithFaceAllowed_fingerprintLockout() {
- // GIVEN unlocking with biometric is allowed
- strongAuthNotRequired();
+ public void class3FingerprintLockOut_lockOutClass1Face() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ false);
+ setupFingerprintAuth(/* isClass3 */ true);
+
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN fingerprint (class 3) is lock out
+ fingerprintErrorTemporaryLockOut();
+
+ // THEN unlocking with face is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FACE));
+ }
+
+ @Test
+ public void class3FingerprintLockOut_lockOutClass3Face() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ true);
+ setupFingerprintAuth(/* isClass3 */ true);
- // WHEN fingerprint is locked out
- fingerprintErrorTemporaryLockedOut();
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN fingerprint (class 3) is lock out
+ fingerprintErrorTemporaryLockOut();
// THEN unlocking with face is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -719,6 +766,38 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void class3FaceLockOut_lockOutClass3Fingerprint() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ true);
+ setupFingerprintAuth(/* isClass3 */ true);
+
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN face (class 3) is lock out
+ faceAuthLockOut();
+
+ // THEN unlocking with fingerprint is not allowed
+ Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
+ public void class1FaceLockOut_doesNotLockOutClass3Fingerprint() throws RemoteException {
+ setupFaceAuth(/* isClass3 */ false);
+ setupFingerprintAuth(/* isClass3 */ true);
+
+ // GIVEN primary auth is not required by StrongAuthTracker
+ primaryAuthNotRequiredByStrongAuthTracker();
+
+ // WHEN face (class 1) is lock out
+ faceAuthLockOut();
+
+ // THEN unlocking with fingerprint is still allowed
+ Assert.assertTrue(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
+ BiometricSourceType.FINGERPRINT));
+ }
+
+ @Test
public void testUnlockingWithFpAllowed_strongAuthTrackerUnlockingWithBiometricNotAllowed() {
// GIVEN unlocking with biometric is not allowed
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
@@ -731,10 +810,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testUnlockingWithFpAllowed_fingerprintLockout() {
// GIVEN unlocking with biometric is allowed
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
- // WHEN fingerprint is locked out
- fingerprintErrorTemporaryLockedOut();
+ // WHEN fingerprint is lock out
+ fingerprintErrorTemporaryLockOut();
// THEN unlocking with fingerprint is not allowed
Assert.assertFalse(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(
@@ -757,8 +836,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mKeyguardUpdateMonitor.onTrustChanged(true, true, getCurrentUser(), 0, null);
Assert.assertTrue(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
- // WHEN fingerprint is locked out
- fingerprintErrorTemporaryLockedOut();
+ // WHEN fingerprint is lock out
+ fingerprintErrorTemporaryLockOut();
// THEN user is NOT considered as "having trust" and bouncer cannot be skipped
Assert.assertFalse(mKeyguardUpdateMonitor.getUserHasTrust(getCurrentUser()));
@@ -826,7 +905,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// GIVEN udfps is supported and strong auth required for weak biometrics (face) only
givenUdfpsSupported();
- strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face
+ primaryAuthRequiredForWeakBiometricOnly(); // allows class3 fp to run but not class1 face
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -863,7 +942,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
public void noFaceDetect_whenStrongAuthRequiredAndBypass_faceDetectionUnsupported() {
// GIVEN bypass is enabled, face detection is NOT supported and strong auth is required
lockscreenBypassIsAllowed();
- strongAuthRequiredEncrypted();
+ primaryAuthRequiredEncrypted();
keyguardIsVisible();
// WHEN the device wakes up
@@ -931,6 +1010,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
verifyFingerprintAuthenticateNeverCalled();
// WHEN alternate bouncer is shown
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
// THEN make sure FP listening begins
@@ -1011,10 +1091,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testOnFaceAuthenticated_skipsFaceWhenAuthenticated() {
- // test whether face will be skipped if authenticated, so the value of isStrongBiometric
+ // test whether face will be skipped if authenticated, so the value of isClass3Biometric
// doesn't matter here
mKeyguardUpdateMonitor.onFaceAuthenticated(KeyguardUpdateMonitor.getCurrentUser(),
- true /* isStrongBiometric */);
+ true /* isClass3Biometric */);
setKeyguardBouncerVisibility(true);
mTestableLooper.processAllMessages();
@@ -1027,7 +1107,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
keyguardIsVisible();
- faceAuthLockedOut();
+ faceAuthLockOut();
verify(mLockPatternUtils, never()).requireStrongAuth(anyInt(), anyInt());
}
@@ -1050,7 +1130,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
keyguardIsVisible();
- faceAuthLockedOut();
+ faceAuthLockOut();
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT_PERMANENT, "");
@@ -1060,32 +1140,32 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Test
public void testGetUserCanSkipBouncer_whenFace() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFaceAuthenticated(user, true /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@Test
public void testGetUserCanSkipBouncer_whenFace_nonStrongAndDisallowed() {
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
.thenReturn(false);
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFaceAuthenticated(user, false /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
}
@Test
public void testGetUserCanSkipBouncer_whenFingerprint() {
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, true /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isTrue();
}
@Test
public void testGetUserCanSkipBouncer_whenFingerprint_nonStrongAndDisallowed() {
- when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isStrongBiometric */))
+ when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(false /* isClass3Biometric */))
.thenReturn(false);
int user = KeyguardUpdateMonitor.getCurrentUser();
- mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isStrongBiometric */);
+ mKeyguardUpdateMonitor.onFingerprintAuthenticated(user, false /* isClass3Biometric */);
assertThat(mKeyguardUpdateMonitor.getUserCanSkipBouncer(user)).isFalse();
}
@@ -1126,9 +1206,9 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@BiometricConstants.LockoutMode int fingerprintLockoutMode,
@BiometricConstants.LockoutMode int faceLockoutMode) {
final int newUser = 12;
- final boolean faceLocked =
+ final boolean faceLockOut =
faceLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
- final boolean fpLocked =
+ final boolean fpLockOut =
fingerprintLockoutMode != BiometricConstants.BIOMETRIC_LOCKOUT_NONE;
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -1161,8 +1241,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
eq(false), eq(BiometricSourceType.FINGERPRINT));
// THEN locked out states are updated
- assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked);
- assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked);
+ assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLockOut);
+ assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLockOut);
// Fingerprint should be cancelled on lockout if going to lockout state, else
// restarted if it's not
@@ -1443,7 +1523,8 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
throws RemoteException {
// GIVEN SFPS supported and enrolled
final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
- props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+ props.add(createFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON,
+ /* isClass3 */ true));
when(mAuthController.getSfpsProps()).thenReturn(props);
when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
@@ -1466,17 +1547,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
}
- private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
- @FingerprintSensorProperties.SensorType int sensorType) {
- return new FingerprintSensorPropertiesInternal(
- 0 /* sensorId */,
- SensorProperties.STRENGTH_STRONG,
- 1 /* maxEnrollmentsPerUser */,
- new ArrayList<ComponentInfoInternal>(),
- sensorType,
- true /* resetLockoutRequiresHardwareAuthToken */);
- }
-
@Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state
@@ -1613,7 +1683,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
keyguardNotGoingAway();
occludingAppRequestsFaceAuth();
currentUserIsPrimary();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1622,7 +1692,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Fingerprint is locked out.
- fingerprintErrorTemporaryLockedOut();
+ fingerprintErrorTemporaryLockOut();
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isFalse();
}
@@ -1634,7 +1704,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
bouncerFullyVisibleAndNotGoingToSleep();
keyguardNotGoingAway();
currentUserIsPrimary();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1657,7 +1727,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
bouncerFullyVisibleAndNotGoingToSleep();
keyguardNotGoingAway();
currentUserIsPrimary();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1684,7 +1754,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// Face auth should run when the following is true.
keyguardNotGoingAway();
bouncerFullyVisibleAndNotGoingToSleep();
- strongAuthNotRequired();
+ primaryAuthNotRequiredByStrongAuthTracker();
biometricsEnabledForCurrentUser();
currentUserDoesNotHaveTrust();
biometricsNotDisabledThroughDevicePolicyManager();
@@ -1940,7 +2010,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFace()).isTrue();
// Face is locked out.
- faceAuthLockedOut();
+ faceAuthLockOut();
mTestableLooper.processAllMessages();
// This is needed beccause we want to show face locked out error message whenever face auth
@@ -2578,7 +2648,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
verify(mFingerprintManager).addLockoutResetCallback(fpLockoutResetCallbackCaptor.capture());
// GIVEN device is locked out
- fingerprintErrorTemporaryLockedOut();
+ fingerprintErrorTemporaryLockOut();
// GIVEN FP detection is running
givenDetectFingerprintWithClearingFingerprintManagerInvocations();
@@ -2662,6 +2732,21 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
KeyguardUpdateMonitor.getCurrentUser())).isTrue();
}
+ @Test
+ public void testFingerprintListeningStateWhenOccluded() {
+ when(mAuthController.isUdfpsSupported()).thenReturn(true);
+
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, false);
+ mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_BIOMETRIC);
+ mKeyguardUpdateMonitor.setKeyguardShowing(false, true);
+
+ verifyFingerprintAuthenticateNeverCalled();
+
+ mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
+ mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+
+ verifyFingerprintAuthenticateCall();
+ }
private void verifyFingerprintAuthenticateNeverCalled() {
verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
@@ -2705,8 +2790,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
private void supportsFaceDetection() throws RemoteException {
+ final boolean isClass3 = !mFaceSensorProperties.isEmpty()
+ && mFaceSensorProperties.get(0).sensorStrength == STRENGTH_STRONG;
mFaceSensorProperties =
- List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true));
+ List.of(createFaceSensorProperties(/* supportsFaceDetection = */ true, isClass3));
mFaceAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(mFaceSensorProperties);
}
@@ -2734,7 +2821,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
}
- private void faceAuthLockedOut() {
+ private void faceAuthLockOut() {
mKeyguardUpdateMonitor.mFaceAuthenticationCallback
.onAuthenticationError(FaceManager.FACE_ERROR_LOCKOUT_PERMANENT, "");
}
@@ -2767,7 +2854,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mKeyguardUpdateMonitor.setSwitchingUser(true);
}
- private void fingerprintErrorTemporaryLockedOut() {
+ private void fingerprintErrorTemporaryLockOut() {
mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
.onAuthenticationError(FINGERPRINT_ERROR_LOCKOUT, "Fingerprint locked out");
}
@@ -2821,18 +2908,18 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
);
}
- private void strongAuthRequiredEncrypted() {
+ private void primaryAuthRequiredEncrypted() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(STRONG_AUTH_REQUIRED_AFTER_BOOT);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
}
- private void strongAuthRequiredForWeakBiometricOnly() {
+ private void primaryAuthRequiredForWeakBiometricOnly() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
}
- private void strongAuthNotRequired() {
+ private void primaryAuthNotRequiredByStrongAuthTracker() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(0);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
@@ -2917,10 +3004,10 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
private void givenDetectFace() throws RemoteException {
- // GIVEN bypass is enabled, face detection is supported and strong auth is required
+ // GIVEN bypass is enabled, face detection is supported and primary auth is required
lockscreenBypassIsAllowed();
supportsFaceDetection();
- strongAuthRequiredEncrypted();
+ primaryAuthRequiredEncrypted();
keyguardIsVisible();
// fingerprint is NOT running, UDFPS is NOT supported
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 64fec5bfd4ed..dea2082a2c22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -13,15 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.shade
+package com.android.keyguard
import android.animation.Animator
import android.testing.AndroidTestingRunner
import android.transition.TransitionValues
import androidx.test.filters.SmallTest
-import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
import com.android.systemui.SysuiTestCase
-import com.android.systemui.shade.NotificationPanelViewController.SplitShadeTransitionAdapter
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -33,14 +32,14 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
class SplitShadeTransitionAdapterTest : SysuiTestCase() {
- @Mock private lateinit var keyguardStatusViewController: KeyguardStatusViewController
+ @Mock private lateinit var KeyguardClockSwitchController: KeyguardClockSwitchController
private lateinit var adapter: SplitShadeTransitionAdapter
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- adapter = SplitShadeTransitionAdapter(keyguardStatusViewController)
+ adapter = SplitShadeTransitionAdapter(KeyguardClockSwitchController)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt
deleted file mode 100644
index 44da5f42aafd..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ChooserPinMigrationTest.kt
+++ /dev/null
@@ -1,255 +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
-
-import android.content.Context
-import android.content.Intent
-import android.content.SharedPreferences
-import android.content.res.Resources
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.broadcast.BroadcastSender
-import com.android.systemui.flags.FakeFeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.kotlinArgumentCaptor
-import com.android.systemui.util.mockito.whenever
-import com.google.common.truth.Truth.assertThat
-import java.io.File
-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.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.MockitoAnnotations
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class ChooserPinMigrationTest : SysuiTestCase() {
-
- private val fakeFeatureFlags = FakeFeatureFlags()
- private val fakePreferences =
- mutableMapOf(
- "TestPinnedPackage/TestPinnedClass" to true,
- "TestUnpinnedPackage/TestUnpinnedClass" to false,
- )
- private val intent = kotlinArgumentCaptor<Intent>()
- private val permission = kotlinArgumentCaptor<String>()
-
- private lateinit var chooserPinMigration: ChooserPinMigration
-
- @Mock private lateinit var mockContext: Context
- @Mock private lateinit var mockResources: Resources
- @Mock
- private lateinit var mockLegacyPinPrefsFileSupplier:
- ChooserPinMigration.Companion.LegacyPinPrefsFileSupplier
- @Mock private lateinit var mockFile: File
- @Mock private lateinit var mockSharedPreferences: SharedPreferences
- @Mock private lateinit var mockSharedPreferencesEditor: SharedPreferences.Editor
- @Mock private lateinit var mockBroadcastSender: BroadcastSender
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
-
- whenever(mockContext.resources).thenReturn(mockResources)
- whenever(mockContext.getSharedPreferences(any<File>(), anyInt()))
- .thenReturn(mockSharedPreferences)
- whenever(mockResources.getString(anyInt())).thenReturn("TestPackage/TestClass")
- whenever(mockSharedPreferences.all).thenReturn(fakePreferences)
- whenever(mockSharedPreferences.edit()).thenReturn(mockSharedPreferencesEditor)
- whenever(mockSharedPreferencesEditor.commit()).thenReturn(true)
- whenever(mockLegacyPinPrefsFileSupplier.get()).thenReturn(mockFile)
- whenever(mockFile.exists()).thenReturn(true)
- whenever(mockFile.delete()).thenReturn(true)
- fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, true)
- }
-
- @Test
- fun start_performsMigration() {
- // Arrange
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture())
- assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION")
- assertThat(intent.value.`package`).isEqualTo("TestPackage")
- assertThat(intent.value.extras?.keySet()).hasSize(2)
- assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false))
- .isTrue()
- assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true))
- .isFalse()
- assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION")
-
- // Assert
- verify(mockSharedPreferencesEditor).clear()
- verify(mockSharedPreferencesEditor).commit()
-
- // Assert
- verify(mockFile).delete()
- }
-
- @Test
- fun start_doesNotDeleteLegacyPreferencesFile_whenClearingItFails() {
- // Arrange
- whenever(mockSharedPreferencesEditor.commit()).thenReturn(false)
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verify(mockBroadcastSender).sendBroadcast(intent.capture(), permission.capture())
- assertThat(intent.value.action).isEqualTo("android.intent.action.CHOOSER_PIN_MIGRATION")
- assertThat(intent.value.`package`).isEqualTo("TestPackage")
- assertThat(intent.value.extras?.keySet()).hasSize(2)
- assertThat(intent.value.hasExtra("TestPinnedPackage/TestPinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestPinnedPackage/TestPinnedClass", false))
- .isTrue()
- assertThat(intent.value.hasExtra("TestUnpinnedPackage/TestUnpinnedClass")).isTrue()
- assertThat(intent.value.getBooleanExtra("TestUnpinnedPackage/TestUnpinnedClass", true))
- .isFalse()
- assertThat(permission.value).isEqualTo("android.permission.RECEIVE_CHOOSER_PIN_MIGRATION")
-
- // Assert
- verify(mockSharedPreferencesEditor).clear()
- verify(mockSharedPreferencesEditor).commit()
-
- // Assert
- verify(mockFile, never()).delete()
- }
-
- @Test
- fun start_OnlyDeletesLegacyPreferencesFile_whenEmpty() {
- // Arrange
- whenever(mockSharedPreferences.all).thenReturn(emptyMap())
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile).delete()
- }
-
- @Test
- fun start_DoesNotDoMigration_whenFlagIsDisabled() {
- // Arrange
- fakeFeatureFlags.set(Flags.CHOOSER_MIGRATION_ENABLED, false)
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile, never()).delete()
- }
-
- @Test
- fun start_DoesNotDoMigration_whenLegacyPreferenceFileNotPresent() {
- // Arrange
- whenever(mockFile.exists()).thenReturn(false)
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile, never()).delete()
- }
-
- @Test
- fun start_DoesNotDoMigration_whenConfiguredChooserComponentIsInvalid() {
- // Arrange
- whenever(mockResources.getString(anyInt())).thenReturn("InvalidComponent")
- chooserPinMigration =
- ChooserPinMigration(
- mockContext,
- fakeFeatureFlags,
- mockBroadcastSender,
- mockLegacyPinPrefsFileSupplier,
- )
-
- // Act
- chooserPinMigration.start()
-
- // Assert
- verifyZeroInteractions(mockBroadcastSender)
-
- // Assert
- verifyZeroInteractions(mockSharedPreferencesEditor)
-
- // Assert
- verify(mockFile, never()).delete()
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
new file mode 100644
index 000000000000..01d3a3931052
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -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.systemui
+
+import android.graphics.Point
+import android.hardware.display.DisplayManagerGlobal
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.DisplayInfo
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.decor.FaceScanningProviderFactory
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.log.ScreenDecorationsLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FaceScanningProviderFactoryTest : SysuiTestCase() {
+
+ private lateinit var underTest: FaceScanningProviderFactory
+
+ @Mock private lateinit var authController: AuthController
+
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock private lateinit var display: Display
+
+ private val displayId = 2
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ val displayInfo = DisplayInfo()
+ val dmGlobal = mock(DisplayManagerGlobal::class.java)
+ val display =
+ Display(
+ dmGlobal,
+ displayId,
+ displayInfo,
+ DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+ )
+ whenever(dmGlobal.getDisplayInfo(eq(displayId))).thenReturn(displayInfo)
+ val displayContext = context.createDisplayContext(display) as SysuiTestableContext
+ displayContext.orCreateTestableResources.addOverride(
+ R.array.config_displayUniqueIdArray,
+ arrayOf(displayId)
+ )
+ displayContext.orCreateTestableResources.addOverride(
+ R.bool.config_fillMainBuiltInDisplayCutout,
+ true
+ )
+ underTest =
+ FaceScanningProviderFactory(
+ authController,
+ displayContext,
+ statusBarStateController,
+ keyguardUpdateMonitor,
+ mock(Executor::class.java),
+ ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+ )
+
+ whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
+ }
+
+ @Test
+ fun shouldNotShowFaceScanningAnimationIfFaceIsNotEnrolled() {
+ whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(false)
+ whenever(authController.isShowing).thenReturn(true)
+
+ assertThat(underTest.shouldShowFaceScanningAnim()).isFalse()
+ }
+
+ @Test
+ fun shouldShowFaceScanningAnimationIfBiometricPromptIsShowing() {
+ whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+ whenever(authController.isShowing).thenReturn(true)
+
+ assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+ }
+
+ @Test
+ fun shouldShowFaceScanningAnimationIfKeyguardFaceDetectionIsShowing() {
+ whenever(keyguardUpdateMonitor.isFaceEnrolled).thenReturn(true)
+ whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
+
+ assertThat(underTest.shouldShowFaceScanningAnim()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 0978c824cb15..9c36af3a35e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.accessibility;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
import static android.view.Choreographer.FrameCallback;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
@@ -172,6 +173,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
returnsSecondArg());
mResources = getContext().getOrCreateTestableResources().getResources();
+ // prevent the config orientation from undefined, which may cause config.diff method
+ // neglecting the orientation update.
+ if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) {
+ mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT;
+ }
+
mWindowMagnificationAnimationController = new WindowMagnificationAnimationController(
mContext, mValueAnimator);
mWindowMagnificationController =
@@ -688,7 +695,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
@Test
public void enableWindowMagnification_rotationIsChanged_updateRotationValue() {
- final Configuration config = mContext.getResources().getConfiguration();
+ // the config orientation should not be undefined, since it would cause config.diff
+ // returning 0 and thus the orientation changed would not be detected
+ assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation);
+
+ final Configuration config = mResources.getConfiguration();
config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT
: ORIENTATION_LANDSCAPE;
final int newRotation = simulateRotateTheDevice();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
index 921f9a8fc7d6..2b95973363ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt
@@ -37,7 +37,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() {
@Test
fun onBackProgressed_shouldInvoke_onBackProgress() {
- val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+ val backEvent =
+ BackEvent(
+ /* touchX = */ 0f,
+ /* touchY = */ 0f,
+ /* progress = */ 0f,
+ /* swipeEdge = */ BackEvent.EDGE_LEFT
+ )
onBackAnimationCallback.onBackStarted(backEvent)
onBackAnimationCallback.onBackProgressed(backEvent)
@@ -47,7 +53,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() {
@Test
fun onBackStarted_shouldInvoke_onBackStart() {
- val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
+ val backEvent =
+ BackEvent(
+ /* touchX = */ 0f,
+ /* touchY = */ 0f,
+ /* progress = */ 0f,
+ /* swipeEdge = */ BackEvent.EDGE_LEFT
+ )
onBackAnimationCallback.onBackStarted(backEvent)
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 b41053cdea50..ef750be90b4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -51,32 +51,37 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
@Test
fun testEnableDetector_expandWithTrack_shouldPostRunnable() {
detector.enable(action)
- // simulate notification expand
- shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+ shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
verify(action).run()
}
@Test
fun testEnableDetector_trackOnly_shouldPostRunnable() {
detector.enable(action)
- // simulate notification expand
- shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f)
+ shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f)
verify(action).run()
}
@Test
fun testEnableDetector_expandOnly_shouldPostRunnable() {
detector.enable(action)
- // simulate notification expand
- shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f)
+ shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f)
verify(action).run()
}
@Test
+ fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() {
+ detector.enable(action)
+ // simulate headsup notification
+ shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f)
+ verifyZeroInteractions(action)
+ }
+
+ @Test
fun testEnableDetector_shouldNotPostRunnable() {
detector.enable(action)
detector.disable()
- shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f)
+ shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
verifyZeroInteractions(action)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index edee3f1b9f02..eae95a54744b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -87,6 +87,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -311,7 +312,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
- mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
+ mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+ mock(KeyguardFaceAuthInteractor.class));
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -1017,7 +1019,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
// THEN the display should be unconfigured once. If the timeout action is not
// cancelled, the display would be unconfigured twice which would cause two
// FP attempts.
- verify(mUdfpsView, times(1)).unconfigureDisplay();
+ verify(mUdfpsView).unconfigureDisplay();
} else {
verify(mUdfpsView, never()).unconfigureDisplay();
}
@@ -1301,8 +1303,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mBiometricExecutor.runAllReady();
downEvent.recycle();
- // THEN the touch is pilfered, expected twice (valid overlap and touch on sensor)
- verify(mInputManager, times(2)).pilferPointers(any());
+ // THEN the touch is pilfered
+ verify(mInputManager).pilferPointers(any());
}
@Test
@@ -1340,7 +1342,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
downEvent.recycle();
// THEN the touch is NOT pilfered
- verify(mInputManager, times(0)).pilferPointers(any());
+ verify(mInputManager, never()).pilferPointers(any());
}
@Test
@@ -1380,7 +1382,51 @@ public class UdfpsControllerTest extends SysuiTestCase {
downEvent.recycle();
// THEN the touch is pilfered
- verify(mInputManager, times(1)).pilferPointers(any());
+ verify(mInputManager).pilferPointers(any());
+ }
+
+ @Test
+ public void onTouch_withNewTouchDetection_doNotPilferWhenPullingUpBouncer()
+ throws RemoteException {
+ final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
+ 0L);
+ final TouchProcessorResult processorResultMove =
+ new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN,
+ 1 /* pointerId */, touchData);
+
+ // Enable new touch detection.
+ when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true);
+
+ // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider.
+ initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */);
+
+ // Configure UdfpsView to accept the ACTION_MOVE event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+
+ // GIVEN that the alternate bouncer is not showing and a11y touch exploration NOT enabled
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mFgExecutor.runAllReady();
+
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+ // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS
+ when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
+ when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f);
+
+ // WHEN ACTION_MOVE is received and touch is within sensor
+ when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn(
+ processorResultMove);
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricExecutor.runAllReady();
+ moveEvent.recycle();
+
+ // THEN the touch is NOT pilfered
+ verify(mInputManager, never()).pilferPointers(any());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
index 8cb91304808d..4cb99a23a531 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java
@@ -120,7 +120,6 @@ public class BrightLineClassifierTest extends SysuiTestCase {
gestureCompleteListenerCaptor.capture());
mGestureFinalizedListener = gestureCompleteListenerCaptor.getValue();
- mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
mFakeFeatureFlags.set(Flags.MEDIA_FALSING_PENALTY, true);
mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
}
@@ -260,13 +259,6 @@ public class BrightLineClassifierTest extends SysuiTestCase {
}
@Test
- public void testIsFalseLongTap_FalseLongTap_NotFlagged() {
- mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, false);
- when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
- assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isFalse();
- }
-
- @Test
public void testIsFalseLongTap_FalseLongTap() {
when(mLongTapClassifier.isTap(mMotionEventList, 0)).thenReturn(mFalsedResult);
assertThat(mBrightLineFalsingManager.isFalseLongTap(NO_PENALTY)).isTrue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 315774aad71a..292fdff0027d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -94,7 +94,6 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase {
mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier,
mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController,
mAccessibilityManager, false, mFakeFeatureFlags);
- mFakeFeatureFlags.set(Flags.FALSING_FOR_LONG_TAPS, true);
mFakeFeatureFlags.set(Flags.FALSING_OFF_FOR_UNFOLDED, true);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index fd6e31ba3bee..18515825967f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.clipboardoverlay;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
-
import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
import static org.junit.Assert.assertEquals;
@@ -40,7 +38,6 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.flags.FakeFeatureFlags;
import org.junit.Before;
import org.junit.Test;
@@ -65,7 +62,6 @@ public class ClipboardListenerTest extends SysuiTestCase {
private ClipboardOverlayController mOverlayController;
@Mock
private ClipboardToast mClipboardToast;
- private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
@Mock
private UiEventLogger mUiEventLogger;
@@ -99,10 +95,8 @@ public class ClipboardListenerTest extends SysuiTestCase {
when(mClipboardManager.getPrimaryClip()).thenReturn(mSampleClipData);
when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true);
-
mClipboardListener = new ClipboardListener(getContext(), mOverlayControllerProvider,
- mClipboardToast, mClipboardManager, mFeatureFlags, mUiEventLogger);
+ mClipboardToast, mClipboardManager, mUiEventLogger);
}
@@ -222,34 +216,4 @@ public class ClipboardListenerTest extends SysuiTestCase {
verify(mClipboardToast, times(1)).showCopiedToast();
verifyZeroInteractions(mOverlayControllerProvider);
}
-
- @Test
- public void test_minimizedLayoutFlagOff_usesLegacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- mClipboardListener.start();
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mOverlayControllerProvider).get();
-
- verify(mOverlayController).setClipDataLegacy(
- mClipDataCaptor.capture(), mStringCaptor.capture());
-
- assertEquals(mSampleClipData, mClipDataCaptor.getValue());
- assertEquals(mSampleSource, mStringCaptor.getValue());
- }
-
- @Test
- public void test_minimizedLayoutFlagOn_usesNew() {
- mClipboardListener.start();
- mClipboardListener.onPrimaryClipChanged();
-
- verify(mOverlayControllerProvider).get();
-
- verify(mOverlayController).setClipData(
- mClipDataCaptor.capture(), mStringCaptor.capture());
-
- assertEquals(mSampleClipData, mClipDataCaptor.getValue());
- assertEquals(mSampleSource, mStringCaptor.getValue());
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 299869c04d34..8600b7c48d33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -25,7 +25,6 @@ import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBO
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_EXPANDED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHOWN_MINIMIZED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
-import static com.android.systemui.flags.Flags.CLIPBOARD_MINIMIZED_LAYOUT;
import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
import static org.mockito.ArgumentMatchers.any;
@@ -123,7 +122,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
new ClipData.Item("Test Item"));
mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, true); // turned off for legacy tests
mOverlayController = new ClipboardOverlayController(
mContext,
@@ -146,178 +144,6 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
}
@Test
- public void test_setClipData_invalidImageData_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- ClipData clipData = new ClipData("", new String[]{"image/png"},
- new ClipData.Item(Uri.parse("")));
-
- mOverlayController.setClipDataLegacy(clipData, "");
-
- verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(1)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_setClipData_nonImageUri_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- ClipData clipData = new ClipData("", new String[]{"resource/png"},
- new ClipData.Item(Uri.parse("")));
-
- mOverlayController.setClipDataLegacy(clipData, "");
-
- verify(mClipboardOverlayView, times(1)).showDefaultTextPreview();
- verify(mClipboardOverlayView, times(1)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_setClipData_textData_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mClipboardOverlayView, times(1)).showTextPreview("Test Item", false);
- verify(mClipboardOverlayView, times(1)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_setClipData_sensitiveTextData_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- ClipDescription description = mSampleClipData.getDescription();
- PersistableBundle b = new PersistableBundle();
- b.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true);
- description.setExtras(b);
- ClipData data = new ClipData(description, mSampleClipData.getItemAt(0));
- mOverlayController.setClipDataLegacy(data, "");
-
- verify(mClipboardOverlayView, times(1)).showTextPreview("••••••", true);
- verify(mClipboardOverlayView, times(1)).showShareChip();
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_setClipData_repeatedCalls_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- when(mAnimator.isRunning()).thenReturn(true);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mClipboardOverlayView, times(1)).getEnterAnimation();
- }
-
- @Test
- public void test_viewCallbacks_onShareTapped_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- mCallbacks.onShareButtonTapped();
-
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SHARE_TAPPED, 0, "");
- verify(mClipboardOverlayView, times(1)).getExitAnimation();
- }
-
- @Test
- public void test_viewCallbacks_onDismissTapped_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- mCallbacks.onDismissButtonTapped();
-
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "");
- verify(mClipboardOverlayView, times(1)).getExitAnimation();
- }
-
- @Test
- public void test_multipleDismissals_dismissesOnce_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- mCallbacks.onSwipeDismissInitiated(mAnimator);
- mCallbacks.onDismissButtonTapped();
- mCallbacks.onSwipeDismissInitiated(mAnimator);
- mCallbacks.onDismissButtonTapped();
-
- verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED, 0, null);
- verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
- }
-
- @Test
- public void test_remoteCopy_withFlagOn_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
- when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mTimeoutHandler, never()).resetTimeout();
- }
-
- @Test
- public void test_remoteCopy_withFlagOff_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mTimeoutHandler).resetTimeout();
- }
-
- @Test
- public void test_nonRemoteCopy_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
- when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "");
-
- verify(mTimeoutHandler).resetTimeout();
- }
-
- @Test
- public void test_logsUseLastClipSource_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
-
- mOverlayController.setClipDataLegacy(mSampleClipData, "first.package");
- mCallbacks.onDismissButtonTapped();
- mOverlayController.setClipDataLegacy(mSampleClipData, "second.package");
- mCallbacks.onDismissButtonTapped();
-
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "first.package");
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED, 0, "second.package");
- verifyNoMoreInteractions(mUiEventLogger);
- }
-
- @Test
- public void test_logOnClipboardActionsShown_legacy() {
- mFeatureFlags.set(CLIPBOARD_MINIMIZED_LAYOUT, false);
- ClipData.Item item = mSampleClipData.getItemAt(0);
- item.setTextLinks(Mockito.mock(TextLinks.class));
- mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
- when(mClipboardUtils.isRemoteCopy(any(Context.class), any(ClipData.class), anyString()))
- .thenReturn(true);
- when(mClipboardUtils.getAction(any(ClipData.Item.class), anyString()))
- .thenReturn(Optional.of(Mockito.mock(RemoteAction.class)));
- when(mClipboardOverlayView.post(any(Runnable.class))).thenAnswer(new Answer<Object>() {
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- ((Runnable) invocation.getArgument(0)).run();
- return null;
- }
- });
-
- mOverlayController.setClipDataLegacy(
- new ClipData(mSampleClipData.getDescription(), item), "actionShownSource");
- mExecutor.runAllReady();
-
- verify(mUiEventLogger).log(CLIPBOARD_OVERLAY_ACTION_SHOWN, 0, "actionShownSource");
- verifyNoMoreInteractions(mUiEventLogger);
- }
-
- // start of refactored setClipData tests
- @Test
public void test_setClipData_invalidImageData() {
ClipData clipData = new ClipData("", new String[]{"image/png"},
new ClipData.Item(Uri.parse("")));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
index 3d8f04e08825..673b5eb5dc3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
@@ -142,81 +142,6 @@ public class ClipboardOverlayUtilsTest extends SysuiTestCase {
assertEquals(actionB, result);
}
- // TODO(b/267162944): Next four tests (marked "legacy") are obsolete once
- // CLIPBOARD_MINIMIZED_LAYOUT flag is released and removed
- @Test
- public void test_getAction_noLinks_returnsEmptyOptional_legacy() {
- ClipData.Item item = new ClipData.Item("no text links");
- item.setTextLinks(Mockito.mock(TextLinks.class));
-
- Optional<RemoteAction> action = mClipboardUtils.getAction(item, "");
-
- assertTrue(action.isEmpty());
- }
-
- @Test
- public void test_getAction_returnsFirstLink_legacy() {
- when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
- when(mClipDataItem.getText()).thenReturn("");
- RemoteAction actionA = constructRemoteAction("abc");
- RemoteAction actionB = constructRemoteAction("def");
- TextClassification classificationA = Mockito.mock(TextClassification.class);
- when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
- TextClassification classificationB = Mockito.mock(TextClassification.class);
- when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
- when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
- classificationA, classificationB);
-
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
-
- assertEquals(actionA, result);
- }
-
- @Test
- public void test_getAction_skipsMatchingComponent_legacy() {
- when(mClipDataItem.getTextLinks()).thenReturn(getFakeTextLinksBuilder().build());
- when(mClipDataItem.getText()).thenReturn("");
- RemoteAction actionA = constructRemoteAction("abc");
- RemoteAction actionB = constructRemoteAction("def");
- TextClassification classificationA = Mockito.mock(TextClassification.class);
- when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
- TextClassification classificationB = Mockito.mock(TextClassification.class);
- when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
- when(mTextClassifier.classifyText(anyString(), anyInt(), anyInt(), isNull())).thenReturn(
- classificationA, classificationB);
-
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "abc").orElse(null);
-
- assertEquals(actionB, result);
- }
-
- @Test
- public void test_getAction_skipsShortEntity_legacy() {
- TextLinks.Builder textLinks = new TextLinks.Builder("test text of length 22");
- final Map<String, Float> scores = new ArrayMap<>();
- scores.put(TextClassifier.TYPE_EMAIL, 1f);
- textLinks.addLink(20, 22, scores);
- textLinks.addLink(0, 22, scores);
-
- when(mClipDataItem.getTextLinks()).thenReturn(textLinks.build());
- when(mClipDataItem.getText()).thenReturn(textLinks.build().getText());
-
- RemoteAction actionA = constructRemoteAction("abc");
- RemoteAction actionB = constructRemoteAction("def");
- TextClassification classificationA = Mockito.mock(TextClassification.class);
- when(classificationA.getActions()).thenReturn(Lists.newArrayList(actionA));
- TextClassification classificationB = Mockito.mock(TextClassification.class);
- when(classificationB.getActions()).thenReturn(Lists.newArrayList(actionB));
- when(mTextClassifier.classifyText(anyString(), eq(20), eq(22), isNull())).thenReturn(
- classificationA);
- when(mTextClassifier.classifyText(anyString(), eq(0), eq(22), isNull())).thenReturn(
- classificationB);
-
- RemoteAction result = mClipboardUtils.getAction(mClipDataItem, "test").orElse(null);
-
- assertEquals(actionB, result);
- }
-
@Test
public void test_extra_withPackage_returnsTrue() {
PersistableBundle b = new PersistableBundle();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
index 5fcf414f0251..8fdc4918b9fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationCollectionLiveDataTest.java
@@ -18,8 +18,8 @@ package com.android.systemui.dreams.complication;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -29,23 +29,45 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import java.util.Collection;
import java.util.HashSet;
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
+
+ private FakeExecutor mExecutor;
+ private DreamOverlayStateController mStateController;
+ private ComplicationCollectionLiveData mLiveData;
+ private FakeFeatureFlags mFeatureFlags;
+ @Mock
+ private Observer mObserver;
+
@Before
- public void setUp() throws Exception {
- allowTestableLooperAsMainThread();
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mFeatureFlags = new FakeFeatureFlags();
+ mExecutor = new FakeExecutor(new FakeSystemClock());
+ mFeatureFlags.set(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS, true);
+ mStateController = new DreamOverlayStateController(
+ mExecutor,
+ /* overlayEnabled= */ true,
+ mFeatureFlags);
+ mLiveData = new ComplicationCollectionLiveData(mStateController);
}
@Test
@@ -53,45 +75,41 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
* Ensures registration and callback lifecycles are respected.
*/
public void testLifecycle() {
- getContext().getMainExecutor().execute(() -> {
- final DreamOverlayStateController stateController =
- Mockito.mock(DreamOverlayStateController.class);
- final ComplicationCollectionLiveData liveData =
- new ComplicationCollectionLiveData(stateController);
- final HashSet<Complication> complications = new HashSet<>();
- final Observer<Collection<Complication>> observer = Mockito.mock(Observer.class);
- complications.add(Mockito.mock(Complication.class));
-
- when(stateController.getComplications()).thenReturn(complications);
-
- liveData.observeForever(observer);
- ArgumentCaptor<DreamOverlayStateController.Callback> callbackCaptor =
- ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
-
- verify(stateController).addCallback(callbackCaptor.capture());
- verifyUpdate(observer, complications);
-
- complications.add(Mockito.mock(Complication.class));
- callbackCaptor.getValue().onComplicationsChanged();
-
- verifyUpdate(observer, complications);
-
- callbackCaptor.getValue().onAvailableComplicationTypesChanged();
-
- verifyUpdate(observer, complications);
- });
+ final HashSet<Complication> complications = new HashSet<>();
+ mLiveData.observeForever(mObserver);
+ mExecutor.runAllReady();
+ // Verify observer called with empty complications
+ assertObserverCalledWith(complications);
+
+ addComplication(mock(Complication.class), complications);
+ assertObserverCalledWith(complications);
+
+ addComplication(mock(Complication.class), complications);
+ assertObserverCalledWith(complications);
+
+ mStateController.setAvailableComplicationTypes(0);
+ mExecutor.runAllReady();
+ assertObserverCalledWith(complications);
+ mLiveData.removeObserver(mObserver);
}
- void verifyUpdate(Observer<Collection<Complication>> observer,
- Collection<Complication> targetCollection) {
+ private void assertObserverCalledWith(Collection<Complication> targetCollection) {
ArgumentCaptor<Collection<Complication>> collectionCaptor =
ArgumentCaptor.forClass(Collection.class);
- verify(observer).onChanged(collectionCaptor.capture());
+ verify(mObserver).onChanged(collectionCaptor.capture());
+
+ final Collection<Complication> collection = collectionCaptor.getValue();
- final Collection collection = collectionCaptor.getValue();
assertThat(collection.containsAll(targetCollection)
&& targetCollection.containsAll(collection)).isTrue();
- Mockito.clearInvocations(observer);
+ Mockito.clearInvocations(mObserver);
+ }
+
+ private void addComplication(Complication complication,
+ Collection<Complication> complications) {
+ complications.add(complication);
+ mStateController.addComplication(complication);
+ mExecutor.runAllReady();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
index 08427dab978b..21397d97b578 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java
@@ -269,6 +269,30 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
}
@Test
+ public void testInputEventPropagationAfterRemoval() {
+ final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)));
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ // Ensure session started
+ final DreamTouchHandler.TouchSession session = captureSession(touchHandler);
+ final InputChannelCompat.InputEventListener eventListener =
+ registerInputEventListener(session);
+
+ session.pop();
+ environment.executeAll();
+
+ final InputEvent event = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(event);
+
+ verify(eventListener, never()).onInputEvent(eq(event));
+ }
+
+ @Test
public void testInputGesturePropagation() {
final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index c93e677071cd..0de9608b906f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -66,6 +66,8 @@ import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
@@ -142,6 +144,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock CentralSurfaces mCentralSurfaces;
+ private FakeFeatureFlags mFeatureFlags;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -160,6 +164,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mColorExtractor, mDumpManager, mKeyguardStateController,
mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
mShadeWindowLogger);
+ mFeatureFlags = new FakeFeatureFlags();
+
DejankUtils.setImmediate(true);
@@ -515,6 +521,28 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
}
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testNotStartingKeyguardWhenFlagIsDisabled() {
+ mViewMediator.setShowingLocked(false);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+ mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, false);
+ mViewMediator.onDreamingStarted();
+ assertFalse(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testStartingKeyguardWhenFlagIsEnabled() {
+ mViewMediator.setShowingLocked(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+ mFeatureFlags.set(Flags.LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING, true);
+ mViewMediator.onDreamingStarted();
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
@@ -545,7 +573,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mShadeController,
() -> mNotificationShadeWindowController,
() -> mActivityLaunchAnimator,
- () -> mScrimController);
+ () -> mScrimController,
+ mFeatureFlags);
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
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 6e002f5a9a9a..fc75d47c01b7 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
@@ -44,22 +44,28 @@ import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationStatus
import com.android.systemui.keyguard.shared.model.DetectionStatus
import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.captureMany
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
@@ -81,6 +87,7 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.isNull
import org.mockito.Mockito.mock
@@ -115,11 +122,13 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback>
private lateinit var testDispatcher: TestDispatcher
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
private lateinit var testScope: TestScope
private lateinit var fakeUserRepository: FakeUserRepository
private lateinit var authStatus: FlowValue<AuthenticationStatus?>
private lateinit var detectStatus: FlowValue<DetectionStatus?>
private lateinit var authRunning: FlowValue<Boolean?>
+ private lateinit var bypassEnabled: FlowValue<Boolean?>
private lateinit var lockedOut: FlowValue<Boolean?>
private lateinit var canFaceAuthRun: FlowValue<Boolean?>
private lateinit var authenticated: FlowValue<Boolean?>
@@ -180,8 +189,14 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private fun createDeviceEntryFaceAuthRepositoryImpl(
fmOverride: FaceManager? = faceManager,
bypassControllerOverride: KeyguardBypassController? = bypassController
- ) =
- DeviceEntryFaceAuthRepositoryImpl(
+ ): DeviceEntryFaceAuthRepositoryImpl {
+ val systemClock = FakeSystemClock()
+ val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
+ val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
+ keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ val keyguardTransitionInteractor =
+ KeyguardTransitionInteractor(keyguardTransitionRepository)
+ return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
fakeUserRepository,
@@ -197,8 +212,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
keyguardRepository,
keyguardInteractor,
alternateBouncerInteractor,
+ faceDetectBuffer,
+ faceAuthBuffer,
+ keyguardTransitionInteractor,
dumpManager,
)
+ }
@Test
fun faceAuthRunsAndProvidesAuthStatusUpdates() =
@@ -726,6 +745,23 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
@Test
+ fun isBypassEnabledReflectsBypassControllerState() =
+ testScope.runTest {
+ initCollectors()
+ runCurrent()
+ val listeners = captureMany {
+ verify(bypassController, atLeastOnce())
+ .registerOnBypassStateChangedListener(capture())
+ }
+
+ listeners.forEach { it.onBypassStateChanged(true) }
+ assertThat(bypassEnabled()).isTrue()
+
+ listeners.forEach { it.onBypassStateChanged(false) }
+ assertThat(bypassEnabled()).isFalse()
+ }
+
+ @Test
fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() =
testScope.runTest {
testGatingCheckForDetect {
@@ -744,6 +780,50 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
}
}
+ @Test
+ fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromDozing() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.GONE)
+ )
+
+ runCurrent()
+ verify(faceManager).scheduleWatchdog()
+ }
+
+ @Test
+ fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromAod() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ )
+
+ runCurrent()
+ verify(faceManager).scheduleWatchdog()
+ }
+
+ @Test
+ fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromLockscreen() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+
+ runCurrent()
+ verify(faceManager).scheduleWatchdog()
+ }
+
+ @Test
+ fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromBouncer() =
+ testScope.runTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE)
+ )
+
+ runCurrent()
+ verify(faceManager).scheduleWatchdog()
+ }
+
private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
initCollectors()
allPreconditionsToRunFaceAuthAreTrue()
@@ -844,6 +924,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
lockedOut = collectLastValue(underTest.isLockedOut)
canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth)
authenticated = collectLastValue(underTest.isAuthenticated)
+ bypassEnabled = collectLastValue(underTest.isBypassEnabled)
fakeUserRepository.setSelectedUserInfo(primaryUser)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 86e8c9accc45..a668af340e7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -41,6 +41,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
+import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
@@ -67,6 +68,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
@Before
fun setUp() {
+ context.resources.configuration.setLayoutDirection(Locale.US)
config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1)
config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2)
val testDispatcher = StandardTestDispatcher()
@@ -222,6 +224,40 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
}
@Test
+ fun getSlotPickerRepresentations_rightToLeft_slotsReversed() {
+ context.resources.configuration.setLayoutDirection(Locale("he", "IL"))
+ val slot1 = "slot1"
+ val slot2 = "slot2"
+ val slot3 = "slot3"
+ context.orCreateTestableResources.addOverride(
+ R.array.config_keyguardQuickAffordanceSlots,
+ arrayOf(
+ "$slot1:2",
+ "$slot2:4",
+ "$slot3:5",
+ ),
+ )
+
+ assertThat(underTest.getSlotPickerRepresentations())
+ .isEqualTo(
+ listOf(
+ KeyguardSlotPickerRepresentation(
+ id = slot3,
+ maxSelectedAffordances = 5,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = slot2,
+ maxSelectedAffordances = 4,
+ ),
+ KeyguardSlotPickerRepresentation(
+ id = slot1,
+ maxSelectedAffordances = 2,
+ ),
+ )
+ )
+ }
+
+ @Test
fun `selections for secondary user`() =
testScope.runTest {
userTracker.set(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
new file mode 100644
index 000000000000..3d1d2f46a65e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -0,0 +1,292 @@
+/*
+ * 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.domain.interactor
+
+import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+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.log.FaceAuthenticationLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+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.TestCoroutineScheduler
+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.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
+ private lateinit var testScope: TestScope
+ private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ val scheduler = TestCoroutineScheduler()
+ val dispatcher = StandardTestDispatcher(scheduler)
+ testScope = TestScope(dispatcher)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+
+ underTest =
+ SystemUIKeyguardFaceAuthInteractor(
+ testScope.backgroundScope,
+ dispatcher,
+ faceAuthRepository,
+ PrimaryBouncerInteractor(
+ bouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mock(KeyguardStateController::class.java),
+ mock(KeyguardSecurityModel::class.java),
+ mock(PrimaryBouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ context,
+ keyguardUpdateMonitor,
+ mock(KeyguardBypassController::class.java),
+ ),
+ AlternateBouncerInteractor(
+ mock(StatusBarStateController::class.java),
+ mock(KeyguardStateController::class.java),
+ bouncerRepository,
+ mock(BiometricSettingsRepository::class.java),
+ FakeDeviceEntryFingerprintAuthRepository(),
+ FakeSystemClock(),
+ ),
+ keyguardTransitionInteractor,
+ featureFlags,
+ FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
+ keyguardUpdateMonitor,
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() =
+ testScope.runTest {
+ underTest.start()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
+ testScope.runTest {
+ underTest.start()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() =
+ testScope.runTest {
+ underTest.start()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.DOZING,
+ KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ bouncerRepository.setPrimaryShow(true)
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true))
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ bouncerRepository.setAlternateVisible(false)
+ runCurrent()
+
+ bouncerRepository.setAlternateVisible(true)
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+ false
+ )
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenUdfpsSensorTouched() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onUdfpsSensorTouched()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false))
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenOnAssistantTriggeredOnLockScreen() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onAssistantTriggeredOnLockScreen()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenDeviceLifted() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onDeviceLifted()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenQsExpansionStared() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onQsExpansionStared()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenNotificationPanelClicked() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onNotificationPanelClicked()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenSwipeUpOnBouncer() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onSwipeUpOnBouncer()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
index 51988ef1ab78..77bb12c2cbda 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
@@ -29,20 +29,20 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.util.mockito.any
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -51,8 +51,8 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class KeyguardLongPressInteractorTest : SysuiTestCase() {
- @Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var logger: UiEventLogger
+ @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
private lateinit var underTest: KeyguardLongPressInteractor
@@ -63,6 +63,14 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(anyInt(), anyInt())).thenAnswer {
+ it.arguments[0]
+ }
+
+ testScope = TestScope()
+ keyguardRepository = FakeKeyguardRepository()
+ keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+
runBlocking { createUnderTest() }
}
@@ -98,60 +106,117 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() {
}
@Test
- fun `long-pressed - pop-up clicked - starts activity`() =
+ fun longPressed_menuClicked_showsSettings() =
testScope.runTest {
- val menu = collectLastValue(underTest.menu)
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+ val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
runCurrent()
- val x = 100
- val y = 123
- underTest.onLongPress(x, y)
- assertThat(menu()).isNotNull()
- assertThat(menu()?.position?.x).isEqualTo(x)
- assertThat(menu()?.position?.y).isEqualTo(y)
+ underTest.onLongPress()
+ assertThat(isMenuVisible).isTrue()
+
+ underTest.onMenuTouchGestureEnded(/* isClick= */ true)
+
+ assertThat(isMenuVisible).isFalse()
+ assertThat(shouldOpenSettings).isTrue()
+ }
+
+ @Test
+ fun onSettingsShown_consumesSettingsShowEvent() =
+ testScope.runTest {
+ val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
+ runCurrent()
- menu()?.onClicked?.invoke()
+ underTest.onLongPress()
+ underTest.onMenuTouchGestureEnded(/* isClick= */ true)
+ assertThat(shouldOpenSettings).isTrue()
- assertThat(menu()).isNull()
- verify(activityStarter).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+ underTest.onSettingsShown()
+ assertThat(shouldOpenSettings).isFalse()
}
@Test
- fun `long-pressed - pop-up dismissed - never starts activity`() =
+ fun onTouchedOutside_neverShowsSettings() =
testScope.runTest {
- val menu = collectLastValue(underTest.menu)
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+ val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
runCurrent()
- menu()?.onDismissed?.invoke()
+ underTest.onTouchedOutside()
- assertThat(menu()).isNull()
- verify(activityStarter, never()).dismissKeyguardThenExecute(any(), any(), anyBoolean())
+ assertThat(isMenuVisible).isFalse()
+ assertThat(shouldOpenSettings).isFalse()
+ }
+
+ @Test
+ fun longPressed_openWppDirectlyEnabled_doesNotShowMenu_opensSettings() =
+ testScope.runTest {
+ createUnderTest(isOpenWppDirectlyEnabled = true)
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+ val shouldOpenSettings by collectLastValue(underTest.shouldOpenSettings)
+ runCurrent()
+
+ underTest.onLongPress()
+
+ assertThat(isMenuVisible).isFalse()
+ assertThat(shouldOpenSettings).isTrue()
}
- @Suppress("DEPRECATION") // We're okay using ACTION_CLOSE_SYSTEM_DIALOGS on system UI.
@Test
fun `long pressed - close dialogs broadcast received - popup dismissed`() =
testScope.runTest {
- val menu = collectLastValue(underTest.menu)
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
runCurrent()
- underTest.onLongPress(123, 456)
- assertThat(menu()).isNotNull()
+ underTest.onLongPress()
+ assertThat(isMenuVisible).isTrue()
fakeBroadcastDispatcher.registeredReceivers.forEach { broadcastReceiver ->
broadcastReceiver.onReceive(context, Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
}
- assertThat(menu()).isNull()
+ assertThat(isMenuVisible).isFalse()
+ }
+
+ @Test
+ fun closesDialogAfterTimeout() =
+ testScope.runTest {
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+ runCurrent()
+
+ underTest.onLongPress()
+ assertThat(isMenuVisible).isTrue()
+
+ advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+
+ assertThat(isMenuVisible).isFalse()
+ }
+
+ @Test
+ fun closesDialogAfterTimeout_onlyAfterTouchGestureEnded() =
+ testScope.runTest {
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+ runCurrent()
+
+ underTest.onLongPress()
+ assertThat(isMenuVisible).isTrue()
+ underTest.onMenuTouchGestureStarted()
+
+ advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+ assertThat(isMenuVisible).isTrue()
+
+ underTest.onMenuTouchGestureEnded(/* isClick= */ false)
+ advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+ assertThat(isMenuVisible).isFalse()
}
@Test
fun `logs when menu is shown`() =
testScope.runTest {
- collectLastValue(underTest.menu)
+ collectLastValue(underTest.isMenuVisible)
runCurrent()
- underTest.onLongPress(100, 123)
+ underTest.onLongPress()
verify(logger)
.log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
@@ -160,41 +225,61 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() {
@Test
fun `logs when menu is clicked`() =
testScope.runTest {
- val menu = collectLastValue(underTest.menu)
+ collectLastValue(underTest.isMenuVisible)
runCurrent()
- underTest.onLongPress(100, 123)
- menu()?.onClicked?.invoke()
+ underTest.onLongPress()
+ underTest.onMenuTouchGestureEnded(/* isClick= */ true)
verify(logger)
.log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
}
+ @Test
+ fun showMenu_leaveLockscreen_returnToLockscreen_menuNotVisible() =
+ testScope.runTest {
+ val isMenuVisible by collectLastValue(underTest.isMenuVisible)
+ runCurrent()
+ underTest.onLongPress()
+ assertThat(isMenuVisible).isTrue()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ to = KeyguardState.GONE,
+ ),
+ )
+ assertThat(isMenuVisible).isFalse()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ to = KeyguardState.LOCKSCREEN,
+ ),
+ )
+ assertThat(isMenuVisible).isFalse()
+ }
+
private suspend fun createUnderTest(
isLongPressFeatureEnabled: Boolean = true,
isRevampedWppFeatureEnabled: Boolean = true,
+ isOpenWppDirectlyEnabled: Boolean = false,
) {
- testScope = TestScope()
- keyguardRepository = FakeKeyguardRepository()
- keyguardTransitionRepository = FakeKeyguardTransitionRepository()
-
underTest =
KeyguardLongPressInteractor(
- unsafeContext = context,
scope = testScope.backgroundScope,
transitionInteractor =
KeyguardTransitionInteractor(
repository = keyguardTransitionRepository,
),
repository = keyguardRepository,
- activityStarter = activityStarter,
logger = logger,
featureFlags =
FakeFeatureFlags().apply {
set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, isLongPressFeatureEnabled)
set(Flags.REVAMPED_WALLPAPER_UI, isRevampedWppFeatureEnabled)
+ set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
},
broadcastDispatcher = fakeBroadcastDispatcher,
+ accessibilityManager = accessibilityManager
)
setUpState()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index bfc09d7c0379..224eec1bf3ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -20,10 +20,12 @@ import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.animation.Expandable
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.doze.util.BurnInHelperWrapper
@@ -38,10 +40,13 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -51,6 +56,7 @@ import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.any
@@ -91,6 +97,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
@Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
private lateinit var underTest: KeyguardBottomAreaViewModel
@@ -134,6 +142,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
FakeFeatureFlags().apply {
set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
+ set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
}
val keyguardInteractor =
@@ -196,6 +206,19 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
dumpManager = mock(),
userHandle = UserHandle.SYSTEM,
)
+ val keyguardLongPressInteractor =
+ KeyguardLongPressInteractor(
+ scope = testScope.backgroundScope,
+ transitionInteractor =
+ KeyguardTransitionInteractor(
+ repository = FakeKeyguardTransitionRepository(),
+ ),
+ repository = repository,
+ logger = UiEventLoggerFake(),
+ featureFlags = featureFlags,
+ broadcastDispatcher = broadcastDispatcher,
+ accessibilityManager = accessibilityManager,
+ )
underTest =
KeyguardBottomAreaViewModel(
keyguardInteractor = keyguardInteractor,
@@ -216,6 +239,14 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
+ longPressViewModel =
+ KeyguardLongPressViewModel(
+ interactor = keyguardLongPressInteractor,
+ ),
+ settingsMenuViewModel =
+ KeyguardSettingsMenuViewModel(
+ interactor = keyguardLongPressInteractor,
+ ),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 543875dc31cf..1e465c791795 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -24,6 +24,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
+import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
@@ -42,6 +43,7 @@ import android.os.Bundle
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
@@ -2199,7 +2201,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
}
@Test
- fun bindRecommendation_carouselNotFitThreeRecs() {
+ fun bindRecommendation_carouselNotFitThreeRecs_OrientationPortrait() {
useRealConstraintSets()
setupUpdatedRecommendationViewHolder()
val albumArt = getColorIcon(Color.RED)
@@ -2227,16 +2229,84 @@ public class MediaControlPanelTest : SysuiTestCase() {
// set the screen width less than the width of media controls.
player.context.resources.configuration.screenWidthDp = 350
+ player.context.resources.configuration.orientation = Configuration.ORIENTATION_PORTRAIT
player.attachRecommendation(recommendationViewHolder)
player.bindRecommendation(data)
- assertThat(player.numberOfFittedRecommendations).isEqualTo(2)
- assertThat(expandedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(coverContainer1.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(collapsedSet.getVisibility(coverContainer2.id)).isEqualTo(ConstraintSet.VISIBLE)
- assertThat(expandedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
- assertThat(collapsedSet.getVisibility(coverContainer3.id)).isEqualTo(ConstraintSet.GONE)
+ val res = player.context.resources
+ val displayAvailableWidth =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+ val recCoverWidth: Int =
+ (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+ res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+ val numOfRecs = displayAvailableWidth / recCoverWidth
+
+ assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+ recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+ if (index < numOfRecs) {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(container.id))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ } else {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ }
+ }
+ }
+
+ @Test
+ fun bindRecommendation_carouselNotFitThreeRecs_OrientationLandscape() {
+ useRealConstraintSets()
+ setupUpdatedRecommendationViewHolder()
+ val albumArt = getColorIcon(Color.RED)
+ val data =
+ smartspaceData.copy(
+ recommendations =
+ listOf(
+ SmartspaceAction.Builder("id1", "title1")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id2", "title2")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build(),
+ SmartspaceAction.Builder("id3", "title3")
+ .setSubtitle("subtitle1")
+ .setIcon(albumArt)
+ .setExtras(Bundle.EMPTY)
+ .build()
+ )
+ )
+
+ // set the screen width less than the width of media controls.
+ // We should have dp width less than 378 to test. In landscape we should have 2x.
+ player.context.resources.configuration.screenWidthDp = 700
+ player.context.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
+ player.attachRecommendation(recommendationViewHolder)
+ player.bindRecommendation(data)
+
+ val res = player.context.resources
+ val displayAvailableWidth =
+ TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 350f, res.displayMetrics).toInt()
+ val recCoverWidth: Int =
+ (res.getDimensionPixelSize(R.dimen.qs_media_rec_album_width) +
+ res.getDimensionPixelSize(R.dimen.qs_media_info_spacing) * 2)
+ val numOfRecs = displayAvailableWidth / recCoverWidth
+
+ assertThat(player.numberOfFittedRecommendations).isEqualTo(numOfRecs)
+ recommendationViewHolder.mediaCoverContainers.forEachIndexed { index, container ->
+ if (index < numOfRecs) {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.VISIBLE)
+ assertThat(collapsedSet.getVisibility(container.id))
+ .isEqualTo(ConstraintSet.VISIBLE)
+ } else {
+ assertThat(expandedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ assertThat(collapsedSet.getVisibility(container.id)).isEqualTo(ConstraintSet.GONE)
+ }
+ }
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
new file mode 100644
index 000000000000..86f3062bca35
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.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.media.controls.util
+
+import android.testing.AndroidTestingRunner
+import android.util.Pair as APair
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MediaDataUtilsTest : SysuiTestCase() {
+
+ @Test
+ fun testScaleFactor_zeroInput_returnsZero() {
+ val input = APair(0, 0)
+ val target = APair(100, 100)
+
+ val scale = MediaDataUtils.getScaleFactor(input, target)
+ assertThat(scale).isEqualTo(0f)
+ }
+
+ @Test
+ fun testScaleFactor_tooWide_scaleDown() {
+ val input = APair(400, 200)
+ val target = APair(100, 100)
+
+ val scale = MediaDataUtils.getScaleFactor(input, target)
+ assertThat(scale).isEqualTo(0.5f)
+ }
+
+ @Test
+ fun testScaleFactor_tooTall_scaleDown() {
+ val input = APair(200, 400)
+ val target = APair(100, 100)
+
+ val scale = MediaDataUtils.getScaleFactor(input, target)
+ assertThat(scale).isEqualTo(0.5f)
+ }
+
+ @Test
+ fun testScaleFactor_lessWide_scaleUp() {
+ val input = APair(50, 100)
+ val target = APair(100, 100)
+
+ val scale = MediaDataUtils.getScaleFactor(input, target)
+ assertThat(scale).isEqualTo(2f)
+ }
+
+ @Test
+ fun testScaleFactor_lessTall_scaleUp() {
+ val input = APair(100, 50)
+ val target = APair(100, 100)
+
+ val scale = MediaDataUtils.getScaleFactor(input, target)
+ assertThat(scale).isEqualTo(2f)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
index c96853d1a406..a0c376ff1a1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.notetask.NoteTaskController
import com.android.systemui.notetask.NoteTaskEntryPoint
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import org.junit.After
@@ -38,6 +39,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.never
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -86,15 +88,28 @@ class LaunchNoteTaskActivityTest : SysuiTestCase() {
@Test
fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
+ val mainUserHandle: UserHandle = mainUser.userHandle
userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
whenever(userManager.isManagedProfile).thenReturn(true)
+ whenever(userManager.mainUser).thenReturn(mainUserHandle)
activityRule.launchActivity(/* startIntent= */ null)
- val mainUserHandle: UserHandle = mainUser.userHandle
verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
}
+ @Test
+ fun startActivityOnWorkProfileUser_noMainUser_shouldNotLaunch() {
+ userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
+ whenever(userManager.isManagedProfile).thenReturn(true)
+ whenever(userManager.mainUser).thenReturn(null)
+
+ activityRule.launchActivity(/* startIntent= */ null)
+
+ verify(noteTaskController, never()).showNoteTask(any())
+ verify(noteTaskController, never()).startNoteTaskProxyActivityForUser(any())
+ }
+
private companion object {
val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
val workProfileUser =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 34d2b14d46a9..e4d8b2598fe3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -46,13 +46,14 @@ import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.CollectionUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QSFactory;
@@ -62,7 +63,6 @@ import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.external.CustomTileStatePersister;
import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.external.TileServiceRequestController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
@@ -110,25 +110,17 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private Provider<AutoTileManager> mAutoTiles;
@Mock
- private DumpManager mDumpManager;
- @Mock
private CentralSurfaces mCentralSurfaces;
@Mock
private QSLogger mQSLogger;
@Mock
private CustomTile mCustomTile;
@Mock
- private UiEventLogger mUiEventLogger;
- @Mock
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
@Mock
- private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder;
- @Mock
- private TileServiceRequestController mTileServiceRequestController;
- @Mock
private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
@Mock
private TileLifecycleManager mTileLifecycleManager;
@@ -137,6 +129,8 @@ public class QSTileHostTest extends SysuiTestCase {
private SparseArray<SharedPreferences> mSharedPreferencesByUser;
+ private FakeFeatureFlags mFeatureFlags;
+
private FakeExecutor mMainExecutor;
private QSTileHost mQSTileHost;
@@ -144,12 +138,13 @@ public class QSTileHostTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mFeatureFlags = new FakeFeatureFlags();
+
+ mFeatureFlags.set(Flags.QS_PIPELINE_NEW_HOST, false);
+
mMainExecutor = new FakeExecutor(new FakeSystemClock());
mSharedPreferencesByUser = new SparseArray<>();
-
- when(mTileServiceRequestControllerBuilder.create(any()))
- .thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
@@ -165,10 +160,9 @@ public class QSTileHostTest extends SysuiTestCase {
mSecureSettings = new FakeSettings();
saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
- mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
- mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
- mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory,
- mUserFileManager);
+ mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
+ mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+ mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
@Override
@@ -686,18 +680,16 @@ public class QSTileHostTest extends SysuiTestCase {
TestQSTileHost(Context context,
QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
- Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
- CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+ Provider<AutoTileManager> autoTiles,
+ CentralSurfaces centralSurfaces, QSLogger qsLogger,
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
- TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager) {
+ UserFileManager userFileManager, FeatureFlags featureFlags) {
super(context, defaultFactory, mainExecutor, pluginManager,
- tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
- uiEventLogger, userTracker, secureSettings, customTileStatePersister,
- tileServiceRequestControllerBuilder, tileLifecycleManagerFactory,
- userFileManager);
+ tunerService, autoTiles, Optional.of(centralSurfaces), qsLogger,
+ userTracker, secureSettings, customTileStatePersister,
+ tileLifecycleManagerFactory, userFileManager, featureFlags);
}
@Override
@@ -715,6 +707,7 @@ public class QSTileHostTest extends SysuiTestCase {
protected TestTile(QSHost host) {
super(
host,
+ mock(QsEventLogger.class),
mock(Looper.class),
mock(Handler.class),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt
new file mode 100644
index 000000000000..40aa2607dc94
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.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.qs
+
+import com.android.internal.logging.InstanceId
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.InstanceIdSequenceFake
+
+class QsEventLoggerFake(
+ uiEventLogger: UiEventLoggerFake,
+ private val instanceIdSequence: InstanceIdSequenceFake,
+) : QsEventLogger, UiEventLogger by uiEventLogger {
+
+ val lastInstanceId: Int
+ get() = instanceIdSequence.lastInstanceId
+
+ override fun getNewInstanceId(): InstanceId {
+ return instanceIdSequence.newInstanceId()
+ }
+}
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 ac106ef9bf51..198ed4ac08fd 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
@@ -41,6 +41,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.settings.FakeDisplayTracker
import com.android.systemui.util.mockito.any
@@ -56,12 +57,12 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -89,6 +90,7 @@ class CustomTileTest : SysuiTestCase() {
@Mock private lateinit var applicationInfo: ApplicationInfo
@Mock private lateinit var serviceInfo: ServiceInfo
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+ @Mock private lateinit var uiEventLogger: QsEventLogger
private var displayTracker = FakeDisplayTracker(mContext)
private lateinit var customTile: CustomTile
@@ -115,6 +117,7 @@ class CustomTileTest : SysuiTestCase() {
customTileBuilder = CustomTile.Builder(
{ tileHost },
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index c03849b35f54..50a8d2630d79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -170,6 +170,21 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
}
@Test
+ fun addTileAtPosition_tooLarge_addedAtEnd() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ val specs = "a,custom(b/c)"
+ storeTilesForUser(specs, 0)
+
+ underTest.addTile(userId = 0, TileSpec.create("d"), position = 100)
+
+ val expected = "a,custom(b/c),d"
+ assertThat(loadTilesForUser(0)).isEqualTo(expected)
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ }
+
+ @Test
fun addTileForOtherUser_addedInThatUser() =
testScope.runTest {
val tilesUser0 by collectLastValue(underTest.tilesSpecs(0))
@@ -187,27 +202,27 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
}
@Test
- fun removeTile() =
+ fun removeTiles() =
testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
storeTilesForUser("a,b", 0)
- underTest.removeTile(userId = 0, TileSpec.create("a"))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
assertThat(loadTilesForUser(0)).isEqualTo("b")
assertThat(tiles).isEqualTo("b".toTileSpecs())
}
@Test
- fun removeTileNotThere_noop() =
+ fun removeTilesNotThere_noop() =
testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
val specs = "a,b"
storeTilesForUser(specs, 0)
- underTest.removeTile(userId = 0, TileSpec.create("c"))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(tiles).isEqualTo(specs.toTileSpecs())
@@ -221,7 +236,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
val specs = "a,b"
storeTilesForUser(specs, 0)
- underTest.removeTile(userId = 0, TileSpec.Invalid)
+ underTest.removeTiles(userId = 0, listOf(TileSpec.Invalid))
assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(tiles).isEqualTo(specs.toTileSpecs())
@@ -237,7 +252,7 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
storeTilesForUser(specs, 0)
storeTilesForUser(specs, 1)
- underTest.removeTile(userId = 1, TileSpec.create("a"))
+ underTest.removeTiles(userId = 1, listOf(TileSpec.create("a")))
assertThat(loadTilesForUser(0)).isEqualTo(specs)
assertThat(user0Tiles).isEqualTo(specs.toTileSpecs())
@@ -246,6 +261,19 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
}
@Test
+ fun removeMultipleTiles() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tilesSpecs(0))
+
+ storeTilesForUser("a,b,c,d", 0)
+
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("a"), TileSpec.create("c")))
+
+ assertThat(loadTilesForUser(0)).isEqualTo("b,d")
+ assertThat(tiles).isEqualTo("b,d".toTileSpecs())
+ }
+
+ @Test
fun changeTiles() =
testScope.runTest {
val tiles by collectLastValue(underTest.tilesSpecs(0))
@@ -310,8 +338,8 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() {
storeTilesForUser(specs, 0)
coroutineScope {
- underTest.removeTile(userId = 0, TileSpec.create("c"))
- underTest.removeTile(userId = 0, TileSpec.create("a"))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("c")))
+ underTest.removeTiles(userId = 0, listOf(TileSpec.create("a")))
}
assertThat(loadTilesForUser(0)).isEqualTo("b")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
new file mode 100644
index 000000000000..7ecb4dc9376a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -0,0 +1,674 @@
+/*
+ * 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.pipeline.domain.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.UserInfo
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTile.BooleanState
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.external.CustomTile
+import com.android.systemui.qs.external.CustomTileStatePersister
+import com.android.systemui.qs.external.TileLifecycleManager
+import com.android.systemui.qs.external.TileServiceKey
+import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeCustomTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.domain.model.TileModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.qs.toProto
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import com.google.protobuf.nano.MessageNano
+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.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class CurrentTilesInteractorImplTest : SysuiTestCase() {
+
+ private val tileSpecRepository: TileSpecRepository = FakeTileSpecRepository()
+ private val userRepository = FakeUserRepository()
+ private val tileFactory = FakeQSFactory(::tileCreator)
+ private val customTileAddedRepository: CustomTileAddedRepository =
+ FakeCustomTileAddedRepository()
+ private val featureFlags = FakeFeatureFlags()
+ private val tileLifecycleManagerFactory = TLMFactory()
+
+ @Mock private lateinit var customTileStatePersister: CustomTileStatePersister
+
+ @Mock private lateinit var userTracker: UserTracker
+
+ @Mock private lateinit var logger: QSPipelineLogger
+
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
+
+ private val unavailableTiles = mutableSetOf("e")
+
+ private lateinit var underTest: CurrentTilesInteractorImpl
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ featureFlags.set(Flags.QS_PIPELINE_NEW_HOST, true)
+
+ userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
+ setUserTracker(0)
+
+ underTest =
+ CurrentTilesInteractorImpl(
+ tileSpecRepository = tileSpecRepository,
+ userRepository = userRepository,
+ customTileStatePersister = customTileStatePersister,
+ tileFactory = tileFactory,
+ customTileAddedRepository = customTileAddedRepository,
+ tileLifecycleManagerFactory = tileLifecycleManagerFactory,
+ userTracker = userTracker,
+ mainDispatcher = testDispatcher,
+ backgroundDispatcher = testDispatcher,
+ scope = testScope.backgroundScope,
+ logger = logger,
+ featureFlags = featureFlags,
+ )
+ }
+
+ @Test
+ fun initialState() =
+ testScope.runTest(USER_INFO_0) {
+ assertThat(underTest.currentTiles.value).isEmpty()
+ assertThat(underTest.currentQSTiles).isEmpty()
+ assertThat(underTest.currentTilesSpecs).isEmpty()
+ assertThat(underTest.userId.value).isEqualTo(0)
+ assertThat(underTest.userContext.value.userId).isEqualTo(0)
+ }
+
+ @Test
+ fun correctTiles() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("e"),
+ CUSTOM_TILE_SPEC,
+ TileSpec.create("d"),
+ TileSpec.create("non_existent")
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ // check each tile
+
+ // Tile a
+ val tile0 = tiles!![0]
+ assertThat(tile0.spec).isEqualTo(specs[0])
+ assertThat(tile0.tile.tileSpec).isEqualTo(specs[0].spec)
+ assertThat(tile0.tile).isInstanceOf(FakeQSTile::class.java)
+ assertThat(tile0.tile.isAvailable).isTrue()
+
+ // Tile e is not available and is not in the list
+
+ // Custom Tile
+ val tile1 = tiles!![1]
+ assertThat(tile1.spec).isEqualTo(specs[2])
+ assertThat(tile1.tile.tileSpec).isEqualTo(specs[2].spec)
+ assertThat(tile1.tile).isInstanceOf(CustomTile::class.java)
+ assertThat(tile1.tile.isAvailable).isTrue()
+
+ // Tile d
+ val tile2 = tiles!![2]
+ assertThat(tile2.spec).isEqualTo(specs[3])
+ assertThat(tile2.tile.tileSpec).isEqualTo(specs[3].spec)
+ assertThat(tile2.tile).isInstanceOf(FakeQSTile::class.java)
+ assertThat(tile2.tile.isAvailable).isTrue()
+
+ // Tile non-existent shouldn't be created. Therefore, only 3 tiles total
+ assertThat(tiles?.size).isEqualTo(3)
+ }
+
+ @Test
+ fun logTileCreated() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ CUSTOM_TILE_SPEC,
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ specs.forEach { verify(logger).logTileCreated(it) }
+ }
+
+ @Test
+ fun logTileNotFoundInFactory() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("non_existing"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ verify(logger, never()).logTileCreated(any())
+ verify(logger).logTileNotFoundInFactory(specs[0])
+ }
+
+ @Test
+ fun tileNotAvailableDestroyed_logged() =
+ testScope.runTest(USER_INFO_0) {
+ val specs =
+ listOf(
+ TileSpec.create("e"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ verify(logger, never()).logTileCreated(any())
+ verify(logger)
+ .logTileDestroyed(
+ specs[0],
+ QSPipelineLogger.TileDestroyedReason.NEW_TILE_NOT_AVAILABLE
+ )
+ }
+
+ @Test
+ fun someTilesNotValid_repositorySetToDefinitiveList() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("e"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles).isEqualTo(listOf(TileSpec.create("a")))
+ }
+
+ @Test
+ fun deduplicatedTiles() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), TileSpec.create("a"))
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ assertThat(tiles?.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(specs[0])
+ }
+
+ @Test
+ fun tilesChange_platformTileNotRecreated() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs =
+ listOf(
+ TileSpec.create("a"),
+ )
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ val originalTileA = tiles!![0].tile
+
+ tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+
+ assertThat(tiles?.size).isEqualTo(2)
+ assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+ }
+
+ @Test
+ fun tileRemovedIsDestroyed() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"), TileSpec.create("c"))
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ val originalTileC = tiles!![1].tile
+
+ tileSpecRepository.removeTiles(USER_INFO_0.id, listOf(TileSpec.create("c")))
+
+ assertThat(tiles?.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("a"))
+
+ assertThat((originalTileC as FakeQSTile).destroyed).isTrue()
+ verify(logger)
+ .logTileDestroyed(
+ TileSpec.create("c"),
+ QSPipelineLogger.TileDestroyedReason.TILE_REMOVED
+ )
+ }
+
+ @Test
+ fun tileBecomesNotAvailable_destroyed() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+ val repoTiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs = listOf(TileSpec.create("a"))
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ val originalTileA = tiles!![0].tile
+
+ // Tile becomes unavailable
+ (originalTileA as FakeQSTile).available = false
+ unavailableTiles.add("a")
+ // and there is some change in the specs
+ tileSpecRepository.addTile(USER_INFO_0.id, TileSpec.create("b"))
+ runCurrent()
+
+ assertThat(originalTileA.destroyed).isTrue()
+ verify(logger)
+ .logTileDestroyed(
+ TileSpec.create("a"),
+ QSPipelineLogger.TileDestroyedReason.EXISTING_TILE_NOT_AVAILABLE
+ )
+
+ assertThat(tiles?.size).isEqualTo(1)
+ assertThat(tiles!![0].spec).isEqualTo(TileSpec.create("b"))
+ assertThat(tiles!![0].tile).isNotSameInstanceAs(originalTileA)
+
+ assertThat(repoTiles).isEqualTo(tiles!!.map(TileModel::spec))
+ }
+
+ @Test
+ fun userChange_tilesChange() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs0 = listOf(TileSpec.create("a"))
+ val specs1 = listOf(TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+
+ switchUser(USER_INFO_1)
+
+ assertThat(tiles!![0].spec).isEqualTo(specs1[0])
+ assertThat(tiles!![0].tile.tileSpec).isEqualTo(specs1[0].spec)
+ }
+
+ @Test
+ fun tileNotPresentInSecondaryUser_destroyedInUserChange() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs0 = listOf(TileSpec.create("a"))
+ val specs1 = listOf(TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs0)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs1)
+
+ val originalTileA = tiles!![0].tile
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ assertThat((originalTileA as FakeQSTile).destroyed).isTrue()
+ verify(logger)
+ .logTileDestroyed(
+ specs0[0],
+ QSPipelineLogger.TileDestroyedReason.TILE_NOT_PRESENT_IN_NEW_USER
+ )
+ }
+
+ @Test
+ fun userChange_customTileDestroyed_lifecycleNotTerminated() {
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+
+ val originalCustomTile = tiles!![0].tile
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ verify(originalCustomTile).destroy()
+ assertThat(tileLifecycleManagerFactory.created).isEmpty()
+ }
+ }
+
+ @Test
+ fun userChange_sameTileUserChanged() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+
+ val specs = listOf(TileSpec.create("a"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, specs)
+
+ val originalTileA = tiles!![0].tile as FakeQSTile
+ assertThat(originalTileA.user).isEqualTo(USER_INFO_0.id)
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ assertThat(tiles!![0].tile).isSameInstanceAs(originalTileA)
+ assertThat(originalTileA.user).isEqualTo(USER_INFO_1.id)
+ verify(logger).logTileUserChanged(specs[0], USER_INFO_1.id)
+ }
+
+ @Test
+ fun addTile() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+ val spec = TileSpec.create("a")
+ val currentSpecs =
+ listOf(
+ TileSpec.create("b"),
+ TileSpec.create("c"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+
+ underTest.addTile(spec, position = 1)
+
+ val expectedSpecs =
+ listOf(
+ TileSpec.create("b"),
+ spec,
+ TileSpec.create("c"),
+ )
+ assertThat(tiles).isEqualTo(expectedSpecs)
+ }
+
+ @Test
+ fun addTile_currentUser() =
+ testScope.runTest(USER_INFO_1) {
+ val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+ val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+ val spec = TileSpec.create("a")
+ val currentSpecs =
+ listOf(
+ TileSpec.create("b"),
+ TileSpec.create("c"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+
+ switchUser(USER_INFO_1)
+ underTest.addTile(spec, position = 1)
+
+ assertThat(tiles0).isEqualTo(currentSpecs)
+
+ val expectedSpecs =
+ listOf(
+ TileSpec.create("b"),
+ spec,
+ TileSpec.create("c"),
+ )
+ assertThat(tiles1).isEqualTo(expectedSpecs)
+ }
+
+ @Test
+ fun removeTile_platform() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+
+ underTest.removeTiles(specs.subList(0, 1))
+
+ assertThat(tiles).isEqualTo(specs.subList(1, 2))
+ }
+
+ @Test
+ fun removeTile_customTile_lifecycleEnded() {
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+ runCurrent()
+ assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+ .isTrue()
+
+ underTest.removeTiles(listOf(CUSTOM_TILE_SPEC))
+
+ assertThat(tiles).isEqualTo(specs.subList(0, 1))
+
+ val tileLifecycleManager =
+ tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]
+ assertThat(tileLifecycleManager).isNotNull()
+
+ with(inOrder(tileLifecycleManager!!)) {
+ verify(tileLifecycleManager).onStopListening()
+ verify(tileLifecycleManager).onTileRemoved()
+ verify(tileLifecycleManager).flushMessagesAndUnbind()
+ }
+ assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+ .isFalse()
+ verify(customTileStatePersister)
+ .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
+ }
+ }
+
+ @Test
+ fun removeTiles_currentUser() =
+ testScope.runTest {
+ val tiles0 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+ val tiles1 by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_1.id))
+ val currentSpecs =
+ listOf(
+ TileSpec.create("a"),
+ TileSpec.create("b"),
+ TileSpec.create("c"),
+ )
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ tileSpecRepository.setTiles(USER_INFO_1.id, currentSpecs)
+
+ switchUser(USER_INFO_1)
+ runCurrent()
+
+ underTest.removeTiles(currentSpecs.subList(0, 2))
+
+ assertThat(tiles0).isEqualTo(currentSpecs)
+ assertThat(tiles1).isEqualTo(currentSpecs.subList(2, 3))
+ }
+
+ @Test
+ fun setTiles() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(tileSpecRepository.tilesSpecs(USER_INFO_0.id))
+
+ val currentSpecs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ runCurrent()
+
+ val newSpecs = listOf(TileSpec.create("b"), TileSpec.create("c"), TileSpec.create("a"))
+ underTest.setTiles(newSpecs)
+ runCurrent()
+
+ assertThat(tiles).isEqualTo(newSpecs)
+ }
+
+ @Test
+ fun setTiles_customTiles_lifecycleEndedIfGone() =
+ testScope.runTest(USER_INFO_0) {
+ val otherCustomTileSpec = TileSpec.create("custom(b/c)")
+
+ val currentSpecs = listOf(CUSTOM_TILE_SPEC, TileSpec.create("a"), otherCustomTileSpec)
+ tileSpecRepository.setTiles(USER_INFO_0.id, currentSpecs)
+ runCurrent()
+
+ val newSpecs =
+ listOf(
+ otherCustomTileSpec,
+ TileSpec.create("a"),
+ )
+
+ underTest.setTiles(newSpecs)
+ runCurrent()
+
+ val tileLifecycleManager =
+ tileLifecycleManagerFactory.created[USER_INFO_0.id to TEST_COMPONENT]!!
+
+ with(inOrder(tileLifecycleManager)) {
+ verify(tileLifecycleManager).onStopListening()
+ verify(tileLifecycleManager).onTileRemoved()
+ verify(tileLifecycleManager).flushMessagesAndUnbind()
+ }
+ assertThat(customTileAddedRepository.isTileAdded(TEST_COMPONENT, USER_INFO_0.id))
+ .isFalse()
+ verify(customTileStatePersister)
+ .removeState(TileServiceKey(TEST_COMPONENT, USER_INFO_0.id))
+ }
+
+ @Test
+ fun protoDump() =
+ testScope.runTest(USER_INFO_0) {
+ val tiles by collectLastValue(underTest.currentTiles)
+ val specs = listOf(TileSpec.create("a"), CUSTOM_TILE_SPEC)
+
+ tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+ val stateA = tiles!![0].tile.state
+ stateA.fillIn(Tile.STATE_INACTIVE, "A", "AA")
+ val stateCustom = QSTile.BooleanState()
+ stateCustom.fillIn(Tile.STATE_ACTIVE, "B", "BB")
+ stateCustom.spec = CUSTOM_TILE_SPEC.spec
+ whenever(tiles!![1].tile.state).thenReturn(stateCustom)
+
+ val proto = SystemUIProtoDump()
+ underTest.dumpProto(proto, emptyArray())
+
+ assertThat(MessageNano.messageNanoEquals(proto.tiles[0], stateA.toProto())).isTrue()
+ assertThat(MessageNano.messageNanoEquals(proto.tiles[1], stateCustom.toProto()))
+ .isTrue()
+ }
+
+ private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
+ this.state = state
+ this.label = label
+ this.secondaryLabel = secondaryLabel
+ if (this is BooleanState) {
+ value = state == Tile.STATE_ACTIVE
+ }
+ }
+
+ private fun tileCreator(spec: String): QSTile? {
+ val currentUser = userTracker.userId
+ return when (spec) {
+ CUSTOM_TILE_SPEC.spec ->
+ mock<CustomTile> {
+ var tileSpecReference: String? = null
+ whenever(user).thenReturn(currentUser)
+ whenever(component).thenReturn(CUSTOM_TILE_SPEC.componentName)
+ whenever(isAvailable).thenReturn(true)
+ whenever(setTileSpec(anyString())).thenAnswer {
+ tileSpecReference = it.arguments[0] as? String
+ Unit
+ }
+ whenever(tileSpec).thenAnswer { tileSpecReference }
+ // Also, add it to the set of added tiles (as this happens as part of the tile
+ // creation).
+ customTileAddedRepository.setTileAdded(
+ CUSTOM_TILE_SPEC.componentName,
+ currentUser,
+ true
+ )
+ }
+ in VALID_TILES -> FakeQSTile(currentUser, available = spec !in unavailableTiles)
+ else -> null
+ }
+ }
+
+ private fun TestScope.runTest(user: UserInfo, body: suspend TestScope.() -> Unit) {
+ return runTest {
+ switchUser(user)
+ body()
+ }
+ }
+
+ private suspend fun switchUser(user: UserInfo) {
+ setUserTracker(user.id)
+ userRepository.setSelectedUserInfo(user)
+ }
+
+ private fun setUserTracker(user: Int) {
+ val mockContext = mockUserContext(user)
+ whenever(userTracker.userContext).thenReturn(mockContext)
+ whenever(userTracker.userId).thenReturn(user)
+ }
+
+ private class TLMFactory : TileLifecycleManager.Factory {
+
+ val created = mutableMapOf<Pair<Int, ComponentName>, TileLifecycleManager>()
+
+ override fun create(intent: Intent, userHandle: UserHandle): TileLifecycleManager {
+ val componentName = intent.component!!
+ val user = userHandle.identifier
+ val manager: TileLifecycleManager = mock()
+ created[user to componentName] = manager
+ return manager
+ }
+ }
+
+ private fun mockUserContext(user: Int): Context {
+ return mock {
+ whenever(this.userId).thenReturn(user)
+ whenever(this.user).thenReturn(UserHandle.of(user))
+ }
+ }
+
+ companion object {
+ private val USER_INFO_0 = UserInfo().apply { id = 0 }
+ private val USER_INFO_1 = UserInfo().apply { id = 1 }
+
+ private val VALID_TILES = setOf("a", "b", "c", "d", "e")
+ private val TEST_COMPONENT = ComponentName("pkg", "cls")
+ private val CUSTOM_TILE_SPEC = TileSpec.Companion.create(TEST_COMPONENT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
new file mode 100644
index 000000000000..e50969641a71
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.pipeline.domain.interactor
+
+import android.content.Context
+import android.view.View
+import com.android.internal.logging.InstanceId
+import com.android.systemui.plugins.qs.QSIconView
+import com.android.systemui.plugins.qs.QSTile
+
+class FakeQSTile(
+ var user: Int,
+ var available: Boolean = true,
+) : QSTile {
+ private var tileSpec: String? = null
+ var destroyed = false
+ private val state = QSTile.State()
+
+ override fun getTileSpec(): String? {
+ return tileSpec
+ }
+
+ override fun isAvailable(): Boolean {
+ return available
+ }
+
+ override fun setTileSpec(tileSpec: String?) {
+ this.tileSpec = tileSpec
+ state.spec = tileSpec
+ }
+
+ override fun refreshState() {}
+
+ override fun addCallback(callback: QSTile.Callback?) {}
+
+ override fun removeCallback(callback: QSTile.Callback?) {}
+
+ override fun removeCallbacks() {}
+
+ override fun createTileView(context: Context?): QSIconView? {
+ return null
+ }
+
+ override fun click(view: View?) {}
+
+ override fun secondaryClick(view: View?) {}
+
+ override fun longClick(view: View?) {}
+
+ override fun userSwitch(currentUser: Int) {
+ user = currentUser
+ }
+
+ override fun getMetricsCategory(): Int {
+ return 0
+ }
+
+ override fun setListening(client: Any?, listening: Boolean) {}
+
+ override fun setDetailListening(show: Boolean) {}
+
+ override fun destroy() {
+ destroyed = true
+ }
+
+ override fun getTileLabel(): CharSequence {
+ return ""
+ }
+
+ override fun getState(): QSTile.State {
+ return state
+ }
+
+ override fun getInstanceId(): InstanceId {
+ return InstanceId.fakeInstanceId(0)
+ }
+
+ override fun isListening(): Boolean {
+ return false
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
index 36549fb826ec..962b53737274 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java
@@ -61,6 +61,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.systemui.InstanceIdSequenceFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
@@ -69,6 +70,8 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
+import com.android.systemui.qs.QsEventLoggerFake;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.statusbar.StatusBarState;
@@ -106,7 +109,8 @@ public class QSTileImplTest extends SysuiTestCase {
private ActivityStarter mActivityStarter;
private UiEventLoggerFake mUiEventLoggerFake;
- private InstanceId mInstanceId = InstanceId.fakeInstanceId(5);
+ private QsEventLoggerFake mQsEventLoggerFake;
+ private InstanceId mInstanceId;
@Captor
private ArgumentCaptor<LogMaker> mLogCaptor;
@@ -115,18 +119,29 @@ public class QSTileImplTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
+
mUiEventLoggerFake = new UiEventLoggerFake();
+ mQsEventLoggerFake =
+ new QsEventLoggerFake(mUiEventLoggerFake, new InstanceIdSequenceFake(10));
when(mHost.indexOf(SPEC)).thenReturn(POSITION);
when(mHost.getContext()).thenReturn(mContext);
- when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake);
- when(mHost.getNewInstanceId()).thenReturn(mInstanceId);
Handler mainHandler = new Handler(mTestableLooper.getLooper());
- mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager,
- mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger);
+ mTile = new TileImpl(
+ mHost,
+ mQsEventLoggerFake,
+ mTestableLooper.getLooper(),
+ mainHandler,
+ mFalsingManager,
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQsLogger
+ );
mTile.initialize();
mTestableLooper.processAllMessages();
+ mInstanceId = InstanceId.fakeInstanceId(mQsEventLoggerFake.getLastInstanceId());
mTile.setTileSpec(SPEC);
}
@@ -507,6 +522,7 @@ public class QSTileImplTest extends SysuiTestCase {
protected TileImpl(
QSHost host,
+ QsEventLogger uiEventLogger,
Looper backgroundLooper,
Handler mainHandler,
FalsingManager falsingManager,
@@ -515,7 +531,7 @@ public class QSTileImplTest extends SysuiTestCase {
ActivityStarter activityStarter,
QSLogger qsLogger
) {
- super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
getState().state = Tile.STATE_ACTIVE;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
index 5e0190b65a12..c60cecb29d75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -22,8 +22,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,6 +30,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.settings.UserTracker
@@ -68,20 +67,21 @@ class AirplaneModeTileTest : SysuiTestCase() {
private lateinit var mGlobalSettings: GlobalSettings
@Mock
private lateinit var mUserTracker: UserTracker
+ @Mock
+ private lateinit var mUiEventLogger: QsEventLogger
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: AirplaneModeTile
- private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
-
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mTestableLooper = TestableLooper.get(this)
Mockito.`when`(mHost.context).thenReturn(mContext)
- Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger)
Mockito.`when`(mHost.userContext).thenReturn(mContext)
- mTile = AirplaneModeTile(mHost,
+ mTile = AirplaneModeTile(
+ mHost,
+ mUiEventLogger,
mTestableLooper.looper,
Handler(mTestableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
index f1e3e8a71398..52b84559f396 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt
@@ -9,12 +9,12 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
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.settings.UserTracker
import com.android.systemui.statusbar.policy.NextAlarmController
@@ -28,8 +28,8 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -52,7 +52,7 @@ class AlarmTileTest : SysuiTestCase() {
@Mock
private lateinit var nextAlarmController: NextAlarmController
@Mock
- private lateinit var uiEventLogger: UiEventLogger
+ private lateinit var uiEventLogger: QsEventLogger
@Mock
private lateinit var pendingIntent: PendingIntent
@Captor
@@ -67,10 +67,10 @@ class AlarmTileTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
`when`(qsHost.context).thenReturn(mContext)
- `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
tile = AlarmTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index a5c0004afe02..ff6814c06001 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BatteryController
@@ -43,10 +44,10 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -63,6 +64,8 @@ class BatterySaverTileTest : SysuiTestCase() {
@Mock
private lateinit var qsHost: QSHost
@Mock
+ private lateinit var uiEventLogger: QsEventLogger
+ @Mock
private lateinit var metricsLogger: MetricsLogger
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@@ -90,6 +93,7 @@ class BatterySaverTileTest : SysuiTestCase() {
tile = BatterySaverTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 2e77de270c65..5e7f68ccf3d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -9,7 +9,6 @@ import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.settingslib.Utils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.systemui.R
@@ -20,6 +19,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BluetoothController
@@ -49,8 +49,8 @@ class BluetoothTileTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var bluetoothController: BluetoothController
+ @Mock private lateinit var uiEventLogger: QsEventLogger
- private val uiEventLogger = UiEventLoggerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var tile: FakeBluetoothTile
@@ -60,11 +60,11 @@ class BluetoothTileTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
whenever(qsHost.context).thenReturn(mContext)
- whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
tile =
FakeBluetoothTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
falsingManager,
@@ -211,6 +211,7 @@ class BluetoothTileTest : SysuiTestCase() {
private class FakeBluetoothTile(
qsHost: QSHost,
+ uiEventLogger: QsEventLogger,
backgroundLooper: Looper,
mainHandler: Handler,
falsingManager: FalsingManager,
@@ -222,6 +223,7 @@ class BluetoothTileTest : SysuiTestCase() {
) :
BluetoothTile(
qsHost,
+ uiEventLogger,
backgroundLooper,
mainHandler,
falsingManager,
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 41938541124a..70d82fdb6e5a 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
@@ -21,8 +21,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLoggerFake
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,21 @@ class CameraToggleTileTest : SysuiTestCase() {
private lateinit var privacyController: IndividualSensorPrivacyController
@Mock
private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var uiEventLogger: QsEventLoggerFake
private lateinit var testableLooper: TestableLooper
private lateinit var tile: CameraToggleTile
- private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
whenever(host.context).thenReturn(mContext)
- whenever(host.uiEventLogger).thenReturn(uiEventLogger)
- tile = CameraToggleTile(host,
+ tile = CameraToggleTile(
+ host,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
index 64fd09d5f5d9..93ed99423f0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java
@@ -43,6 +43,7 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
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.statusbar.connectivity.IconState;
import com.android.systemui.statusbar.connectivity.NetworkController;
@@ -94,6 +95,8 @@ public class CastTileTest extends SysuiTestCase {
private QSLogger mQSLogger;
@Mock
private DialogLaunchAnimator mDialogLaunchAnimator;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private CastTile mCastTile;
@@ -107,6 +110,7 @@ public class CastTileTest extends SysuiTestCase {
mCastTile = new CastTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
index 13c30e9ea9ab..2250ef33f9b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java
@@ -32,12 +32,12 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
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.settings.UserTracker;
import com.android.systemui.util.settings.FakeSettings;
@@ -67,7 +67,7 @@ public class ColorCorrectionTileTest extends SysuiTestCase {
@Mock
private QSLogger mQSLogger;
@Mock
- private UiEventLogger mUiEventLogger;
+ private QsEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
@@ -83,10 +83,10 @@ public class ColorCorrectionTileTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
when(mHost.getContext()).thenReturn(mContext);
- when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
mTile = new ColorCorrectionTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
index ff27e0255aa3..2e02bbe2ebf2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java
@@ -32,7 +32,6 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
@@ -72,7 +72,7 @@ public class ColorInversionTileTest extends SysuiTestCase {
@Mock
private QSLogger mQSLogger;
@Mock
- private UiEventLogger mUiEventLogger;
+ private QsEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
@@ -88,10 +88,10 @@ public class ColorInversionTileTest extends SysuiTestCase {
mTestableLooper = TestableLooper.get(this);
when(mHost.getContext()).thenReturn(mContext);
- when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
mTile = new ColorInversionTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index b048643aba84..176b33fa9fc8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -21,7 +21,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
@@ -30,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.DataSaverController
@@ -57,8 +57,8 @@ class DataSaverTileTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var dataSaverController: DataSaverController
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+ @Mock private lateinit var uiEventLogger: QsEventLogger
- private val uiEventLogger = UiEventLoggerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var tile: DataSaverTile
@@ -68,11 +68,11 @@ class DataSaverTileTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
Mockito.`when`(mHost.context).thenReturn(mContext)
- Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger)
tile =
DataSaverTile(
mHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
index b51c378f6b6b..1346069d3c25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt
@@ -27,7 +27,6 @@ import android.testing.TestableLooper
import androidx.lifecycle.LifecycleOwner
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
@@ -44,6 +43,7 @@ import com.android.systemui.controls.ui.SelectedItem
import com.android.systemui.plugins.ActivityStarter
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.qs.tileimpl.QSTileImpl
import com.android.systemui.util.mockito.any
@@ -52,6 +52,7 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,15 +60,14 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.nullable
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
import java.util.Optional
-import org.junit.After
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -95,7 +95,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
@Mock
private lateinit var serviceInfo: ControlsServiceInfo
@Mock
- private lateinit var uiEventLogger: UiEventLogger
+ private lateinit var uiEventLogger: QsEventLogger
@Captor
private lateinit var listingCallbackCaptor:
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
@@ -118,7 +118,6 @@ class DeviceControlsTileTest : SysuiTestCase() {
spiedContext = spy(mContext)
doNothing().`when`(spiedContext).startActivity(any(Intent::class.java))
`when`(qsHost.context).thenReturn(spiedContext)
- `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
`when`(controlsComponent.isEnabled()).thenReturn(true)
`when`(controlsController.getPreferredSelection())
.thenReturn(SelectedItem.StructureItem(
@@ -399,6 +398,7 @@ class DeviceControlsTileTest : SysuiTestCase() {
private fun createTile(): DeviceControlsTile {
return DeviceControlsTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index 6c0904eb9bfd..f0e4e3adda7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -28,7 +28,6 @@ import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
@@ -37,6 +36,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.ZenModeController
@@ -46,7 +46,6 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
-import java.io.File
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -55,8 +54,9 @@ import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import java.io.File
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -84,7 +84,7 @@ class DndTileTest : SysuiTestCase() {
private lateinit var qsLogger: QSLogger
@Mock
- private lateinit var uiEventLogger: UiEventLogger
+ private lateinit var uiEventLogger: QsEventLogger
@Mock
private lateinit var zenModeController: ZenModeController
@@ -109,7 +109,6 @@ class DndTileTest : SysuiTestCase() {
secureSettings = FakeSettings()
whenever(qsHost.userId).thenReturn(DEFAULT_USER)
- whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger)
val wrappedContext = object : ContextWrapper(context) {
override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences {
@@ -120,6 +119,7 @@ class DndTileTest : SysuiTestCase() {
tile = DndTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
index 7d41aa6c3548..f231c6e56096 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -48,6 +48,7 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
@@ -83,6 +84,8 @@ public class DreamTileTest extends SysuiTestCase {
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
@@ -258,6 +261,7 @@ public class DreamTileTest extends SysuiTestCase {
boolean dreamOnlyEnabledForSystemUser) {
return new DreamTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
index 692a64422a7d..73aa6991c18a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt
@@ -6,7 +6,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -14,6 +13,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.FlashlightController
@@ -45,7 +45,8 @@ class FlashlightTileTest : SysuiTestCase() {
@Mock private lateinit var flashlightController: FlashlightController
- private val uiEventLogger = UiEventLoggerFake()
+ @Mock private lateinit var uiEventLogger: QsEventLogger
+
private val falsingManager = FalsingManagerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var tile: FlashlightTile
@@ -56,11 +57,11 @@ class FlashlightTileTest : SysuiTestCase() {
testableLooper = TestableLooper.get(this)
Mockito.`when`(qsHost.context).thenReturn(mockContext)
- Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
tile =
FlashlightTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
index eeebd4fb7792..1d6f225dd0a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt
@@ -23,7 +23,6 @@ import android.testing.TestableLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
@@ -31,7 +30,8 @@ import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -52,13 +52,13 @@ import org.mockito.MockitoAnnotations
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
class FontScalingTileTest : SysuiTestCase() {
- @Mock private lateinit var qsHost: QSTileHost
+ @Mock private lateinit var qsHost: QSHost
@Mock private lateinit var metricsLogger: MetricsLogger
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var qsLogger: QSLogger
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
- @Mock private lateinit var uiEventLogger: UiEventLogger
+ @Mock private lateinit var uiEventLogger: QsEventLogger
private lateinit var testableLooper: TestableLooper
private lateinit var fontScalingTile: FontScalingTile
@@ -70,11 +70,11 @@ class FontScalingTileTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
`when`(qsHost.getContext()).thenReturn(mContext)
- `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
fontScalingTile =
FontScalingTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
index 959e750ac5f4..73f61d0690e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java
@@ -38,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.DataSaverController;
@@ -66,6 +67,8 @@ public class HotspotTileTest extends SysuiTestCase {
private HotspotController mHotspotController;
@Mock
private DataSaverController mDataSaverController;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private HotspotTile mTile;
@@ -80,6 +83,7 @@ public class HotspotTileTest extends SysuiTestCase {
mTile = new HotspotTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index adfd7f71e8f8..7957c6a7cfb6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -35,6 +35,7 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
@@ -63,6 +64,8 @@ public class InternetTileTest extends SysuiTestCase {
private AccessPointController mAccessPointController;
@Mock
private InternetDialogFactory mInternetDialogFactory;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private InternetTile mTile;
@@ -76,6 +79,7 @@ public class InternetTileTest extends SysuiTestCase {
mTile = new InternetTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
index 3642e874e7ae..0bf0b38f7471 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -22,7 +22,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.tileimpl.QSTileImpl
@@ -43,8 +43,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -71,8 +71,9 @@ class LocationTileTest : SysuiTestCase() {
private lateinit var keyguardStateController: KeyguardStateController
@Mock
private lateinit var panelInteractor: PanelInteractor
+ @Mock
+ private lateinit var uiEventLogger: QsEventLogger
- private val uiEventLogger = UiEventLoggerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var tile: LocationTile
@@ -80,10 +81,11 @@ class LocationTileTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
- `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
`when`(qsHost.context).thenReturn(mockContext)
- tile = LocationTile(qsHost,
+ tile = LocationTile(
+ qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
falsingManager,
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 e2f64b2cc226..ceff546106f4 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
@@ -21,8 +21,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -30,6 +28,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
@@ -67,19 +66,22 @@ class MicrophoneToggleTileTest : SysuiTestCase() {
private lateinit var privacyController: IndividualSensorPrivacyController
@Mock
private lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ private lateinit var uiEventLogger: QsEventLogger
private lateinit var testableLooper: TestableLooper
private lateinit var tile: MicrophoneToggleTile
- private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
whenever(host.context).thenReturn(mContext)
- whenever(host.uiEventLogger).thenReturn(uiEventLogger)
- tile = MicrophoneToggleTile(host,
+ tile = MicrophoneToggleTile(
+ host,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
metricsLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
index c7dae83e2056..763a7e5e4e06 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java
@@ -37,6 +37,7 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
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 org.junit.After;
@@ -70,6 +71,8 @@ public class NfcTileTest extends SysuiTestCase {
private QSLogger mQSLogger;
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private NfcTile mNfcTile;
@@ -84,6 +87,7 @@ public class NfcTileTest extends SysuiTestCase {
mNfcTile = new NfcTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
index 04af69c84cf8..6c8f76b48f03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt
@@ -23,8 +23,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.LocationController
@@ -43,8 +42,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -68,17 +67,18 @@ class NightDisplayTileTest : SysuiTestCase() {
@Mock private lateinit var mNightDisplayListener: NightDisplayListener
+ @Mock private lateinit var mUiEventLogger: QsEventLogger
+
private lateinit var mTestableLooper: TestableLooper
private lateinit var mTile: NightDisplayTile
- private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mTestableLooper = TestableLooper.get(this)
whenever(mHost.context).thenReturn(mContext)
- whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger)
whenever(mHost.userContext).thenReturn(mContext)
whenever(mNightDisplayListenerBuilder.setUser(anyInt()))
.thenReturn(mNightDisplayListenerBuilder)
@@ -87,6 +87,7 @@ class NightDisplayTileTest : SysuiTestCase() {
mTile =
NightDisplayTile(
mHost,
+ mUiEventLogger,
mTestableLooper.looper,
Handler(mTestableLooper.looper),
FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
index 652c138f6478..c391153fecc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java
@@ -33,6 +33,7 @@ import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
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.settings.UserTracker;
import com.android.systemui.util.settings.SecureSettings;
@@ -65,6 +66,8 @@ public class OneHandedModeTileTest extends SysuiTestCase {
private UserTracker mUserTracker;
@Mock
private SecureSettings mSecureSettings;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private OneHandedModeTile mTile;
@@ -78,6 +81,7 @@ public class OneHandedModeTileTest extends SysuiTestCase {
mTile = spy(new OneHandedModeTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
index 3125d455acfb..6f2d904dda64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java
@@ -30,8 +30,6 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
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;
@@ -64,7 +63,8 @@ public class QRCodeScannerTileTest extends SysuiTestCase {
private ActivityStarter mActivityStarter;
@Mock
private QSLogger mQSLogger;
- private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+ @Mock
+ private QsEventLogger mUiEventLogger;
@Mock
private QRCodeScannerController mController;
@@ -79,6 +79,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase {
mTile = new QRCodeScannerTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
index 596df7856ee1..b089e380304d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java
@@ -58,8 +58,6 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -67,6 +65,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -109,7 +108,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
private ActivityStarter mActivityStarter;
@Mock
private QSLogger mQSLogger;
- private UiEventLogger mUiEventLogger = new UiEventLoggerFake();
+ @Mock
+ private QsEventLogger mUiEventLogger;
@Mock
private QuickAccessWalletClient mQuickAccessWalletClient;
@Mock
@@ -136,7 +136,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
doNothing().when(mSpiedContext).startActivity(any(Intent.class));
when(mHost.getContext()).thenReturn(mSpiedContext);
- when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger);
when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL);
when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon);
when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true);
@@ -146,6 +145,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase {
mTile = new QuickAccessWalletTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
index 7913628c5693..d2445944c182 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java
@@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -69,6 +70,8 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase {
private UserTracker mUserTracker;
@Mock
private ReduceBrightColorsController mReduceBrightColorsController;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private ReduceBrightColorsTile mTile;
@@ -85,6 +88,7 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase {
true,
mReduceBrightColorsController,
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 5b94cfedaedf..e106741499ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
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.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -87,6 +88,8 @@ public class RotationLockTileTest extends SysuiTestCase {
DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController;
@Mock
RotationPolicyWrapper mRotationPolicyWrapper;
+ @Mock
+ QsEventLogger mUiEventLogger;
private RotationLockController mController;
private TestableLooper mTestableLooper;
@@ -105,6 +108,7 @@ public class RotationLockTileTest extends SysuiTestCase {
mLockTile = new RotationLockTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
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 d9ed1a299f51..fff2b8f5f8cd 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
@@ -44,6 +44,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
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.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -86,6 +87,8 @@ public class ScreenRecordTileTest extends SysuiTestCase {
private DialogLaunchAnimator mDialogLaunchAnimator;
@Mock
private PanelInteractor mPanelInteractor;
+ @Mock
+ private QsEventLogger mUiEventLogger;
private TestableLooper mTestableLooper;
private ScreenRecordTile mTile;
@@ -100,6 +103,7 @@ public class ScreenRecordTileTest extends SysuiTestCase {
mTile = new ScreenRecordTile(
mHost,
+ mUiEventLogger,
mTestableLooper.getLooper(),
new Handler(mTestableLooper.getLooper()),
new FalsingManagerFake(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
index b55657163382..79147e7e66b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt
@@ -25,7 +25,6 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
@@ -33,6 +32,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.qs.QSTile
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.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BatteryController
@@ -63,8 +63,8 @@ class UiModeNightTileTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var locationController: LocationController
+ @Mock private lateinit var uiEventLogger: QsEventLogger
- private val uiEventLogger = UiEventLoggerFake()
private val falsingManager = FalsingManagerFake()
private lateinit var testableLooper: TestableLooper
private lateinit var tile: UiModeNightTile
@@ -81,11 +81,11 @@ class UiModeNightTileTest : SysuiTestCase() {
`when`(qsHost.userContext).thenReturn(mContext)
`when`(mockContext.resources).thenReturn(resources)
`when`(resources.configuration).thenReturn(configuration)
- `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
tile =
UiModeNightTile(
qsHost,
+ uiEventLogger,
testableLooper.looper,
Handler(testableLooper.looper),
falsingManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index b55fe3677256..67b1099c1e0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -20,8 +20,10 @@ import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS;
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.eq;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
@@ -29,6 +31,8 @@ import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
import androidx.test.runner.AndroidJUnit4;
@@ -42,6 +46,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -58,6 +63,9 @@ public final class AppClipsServiceTest extends SysuiTestCase {
@Mock private Optional<Bubbles> mOptionalBubbles;
@Mock private Bubbles mBubbles;
@Mock private DevicePolicyManager mDevicePolicyManager;
+ @Mock private UserManager mUserManager;
+
+ private AppClipsService mAppClipsService;
@Before
public void setUp() {
@@ -119,26 +127,53 @@ public final class AppClipsServiceTest extends SysuiTestCase {
@Test
public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException {
+ mockToSatisfyAllPrerequisites();
+
+ assertThat(getInterfaceWithRealContext()
+ .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue();
+ }
+
+ @Test
+ public void isManagedProfile_shouldUseProxyConnection() throws RemoteException {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
+ IAppClipsService service = getInterfaceWithRealContext();
+ mAppClipsService.mProxyConnectorToMainProfile =
+ Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile);
+
+ service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID);
+
+ verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any());
+ }
+
+ @Test
+ public void isManagedProfile_noMainUser_shouldReturnFalse() {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(null);
+
+ getInterfaceWithRealContext();
+
+ assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull();
+ }
+
+ private void mockToSatisfyAllPrerequisites() {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
when(mOptionalBubbles.get()).thenReturn(mBubbles);
when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
-
- assertThat(getInterfaceWithRealContext()
- .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue();
}
private IAppClipsService getInterfaceWithRealContext() {
- AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags,
- mOptionalBubbles, mDevicePolicyManager);
- return getInterfaceFromService(appClipsService);
+ mAppClipsService = new AppClipsService(getContext(), mFeatureFlags,
+ mOptionalBubbles, mDevicePolicyManager, mUserManager);
+ return getInterfaceFromService(mAppClipsService);
}
private IAppClipsService getInterfaceWithMockContext() {
- AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags,
- mOptionalBubbles, mDevicePolicyManager);
- return getInterfaceFromService(appClipsService);
+ mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags,
+ mOptionalBubbles, mDevicePolicyManager, mUserManager);
+ return getInterfaceFromService(mAppClipsService);
}
private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index ad06dcc6f327..31a33d4ff908 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -49,6 +49,8 @@ import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import androidx.test.rule.ActivityTestRule;
@@ -98,6 +100,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
private UserTracker mUserTracker;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private UserManager mUserManager;
+
@Main
private Handler mMainHandler;
@@ -109,7 +114,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
protected AppClipsTrampolineActivityTestable create(Intent unUsed) {
return new AppClipsTrampolineActivityTestable(mDevicePolicyManager,
mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager,
- mUserTracker, mUiEventLogger, mMainHandler);
+ mUserTracker, mUiEventLogger, mUserManager, mMainHandler);
}
};
@@ -264,6 +269,40 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE);
}
+ @Test
+ public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity()
+ throws NameNotFoundException {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM);
+ mockToSatisfyAllPrerequisites();
+
+ AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ Intent actualIntent = activity.mStartedIntent;
+ assertThat(actualIntent.getComponent()).isEqualTo(
+ new ComponentName(mContext, AppClipsTrampolineActivity.class));
+ assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM);
+ }
+
+ @Test
+ public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed()
+ throws NameNotFoundException {
+ when(mUserManager.isManagedProfile()).thenReturn(true);
+ when(mUserManager.getMainUser()).thenReturn(null);
+
+ mockToSatisfyAllPrerequisites();
+
+ mActivityRule.launchActivity(mActivityIntent);
+ waitForIdleSync();
+
+ ActivityResult actualResult = mActivityRule.getActivityResult();
+ assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
+ assertThat(getStatusCodeExtra(actualResult.getResultData()))
+ .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED);
+ }
+
private void mockToSatisfyAllPrerequisites() throws NameNotFoundException {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
@@ -282,6 +321,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
public static final class AppClipsTrampolineActivityTestable extends
AppClipsTrampolineActivity {
+ Intent mStartedIntent;
+ UserHandle mStartingUser;
+
public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager,
FeatureFlags flags,
Optional<Bubbles> optionalBubbles,
@@ -289,9 +331,10 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
PackageManager packageManager,
UserTracker userTracker,
UiEventLogger uiEventLogger,
+ UserManager userManager,
@Main Handler mainHandler) {
super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager,
- userTracker, uiEventLogger, mainHandler);
+ userTracker, uiEventLogger, userManager, mainHandler);
}
@Override
@@ -303,6 +346,12 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase {
public void startActivity(Intent unUsed) {
// Ignore this intent to avoid App Clips screenshot editing activity from starting.
}
+
+ @Override
+ public void startActivityAsUser(Intent startedIntent, UserHandle startingUser) {
+ mStartedIntent = startedIntent;
+ mStartingUser = startingUser;
+ }
}
private static int getStatusCodeExtra(Intent intent) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 7b37ea0c9a1a..068d933652ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,6 +67,7 @@ import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardClockSwitchController;
+import com.android.keyguard.KeyguardSliceViewController;
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -74,6 +76,7 @@ import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent;
import com.android.keyguard.dagger.KeyguardStatusBarViewComponent;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
+import com.android.keyguard.logging.KeyguardLogger;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
@@ -90,6 +93,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerReposito
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -106,6 +110,7 @@ import com.android.systemui.model.SysUiState;
import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSFragment;
@@ -232,7 +237,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
@Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent;
@Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController;
- @Mock protected KeyguardStatusViewController mKeyguardStatusViewController;
@Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController;
@Mock protected NotificationStackScrollLayoutController
mNotificationStackScrollLayoutController;
@@ -292,9 +296,14 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock protected MotionEvent mDownMotionEvent;
@Mock protected CoroutineDispatcher mMainDispatcher;
+ @Mock protected KeyguardSliceViewController mKeyguardSliceViewController;
+ @Mock protected KeyguardLogger mKeyguardLogger;
+ @Mock protected KeyguardStatusView mKeyguardStatusView;
@Captor
protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
mEmptySpaceClickListenerCaptor;
+ @Mock protected ActivityStarter mActivityStarter;
+ @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
protected KeyguardInteractor mKeyguardInteractor;
@@ -307,6 +316,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
protected Handler mMainHandler;
protected View.OnLayoutChangeListener mLayoutChangeListener;
+ protected KeyguardStatusViewController mKeyguardStatusViewController;
protected final FalsingManagerFake mFalsingManager = new FalsingManagerFake();
protected final Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
@@ -333,6 +343,18 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
+ mKeyguardStatusViewController = spy(new KeyguardStatusViewController(
+ mKeyguardStatusView,
+ mKeyguardSliceViewController,
+ mKeyguardClockSwitchController,
+ mKeyguardStateController,
+ mUpdateMonitor,
+ mConfigurationController,
+ mDozeParameters,
+ mScreenOffAnimationController,
+ mKeyguardLogger,
+ mFeatureFlags,
+ mInteractionJankMonitor));
when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(false);
when(mHeadsUpCallback.getContext()).thenReturn(mContext);
@@ -364,12 +386,15 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
when(mKeyguardBottomArea.animate()).thenReturn(mViewPropertyAnimator);
when(mView.animate()).thenReturn(mViewPropertyAnimator);
+ when(mKeyguardStatusView.animate()).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.translationX(anyFloat())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setInterpolator(any())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setListener(any())).thenReturn(mViewPropertyAnimator);
when(mViewPropertyAnimator.setUpdateListener(any())).thenReturn(mViewPropertyAnimator);
+ when(mViewPropertyAnimator.withEndAction(any())).thenReturn(mViewPropertyAnimator);
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
.thenReturn(mock(KeyguardStatusView.class));
@@ -575,7 +600,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
() -> mMultiShadeInteractor,
mDumpManager,
mKeyuardLongPressViewModel,
- mKeyguardInteractor);
+ mKeyguardInteractor,
+ mActivityStarter,
+ mKeyguardFaceAuthInteractor);
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
null,
@@ -641,15 +668,20 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mMetricsLogger,
mFeatureFlags,
mInteractionJankMonitor,
- mShadeLog
+ mShadeLog,
+ mKeyguardFaceAuthInteractor
);
}
@After
public void tearDown() {
- mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
- mNotificationPanelViewController.cancelHeightAnimator();
- mMainHandler.removeCallbacksAndMessages(null);
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel();
+ mNotificationPanelViewController.cancelHeightAnimator();
+ }
+ if (mMainHandler != null) {
+ mMainHandler.removeCallbacksAndMessages(null);
+ }
}
protected void setBottomPadding(int stackBottom, int lockIconPadding, int indicationPadding,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 2db9c9788bf8..600fb5c2e1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -930,6 +930,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
+ verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked();
verify(mUpdateMonitor).requestFaceAuth(
FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index d8ffe39e427d..908f7cbf4801 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -62,6 +62,7 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -239,7 +240,8 @@ public class QuickSettingsControllerTest extends SysuiTestCase {
mMetricsLogger,
mFeatureFlags,
mInteractionJankMonitor,
- mShadeLogger
+ mShadeLogger,
+ mock(KeyguardFaceAuthInteractor.class)
);
mFragmentListener = mQsController.getQsFragmentListener();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index d5308298202d..b043e97f1054 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -42,11 +42,11 @@ import com.android.systemui.demomode.DemoModeController
import com.android.systemui.dump.DumpManager
import com.android.systemui.qs.ChipVisibilityListener
import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.shade.carrier.ShadeCarrierGroup
+import com.android.systemui.shade.carrier.ShadeCarrierGroupController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
@@ -88,11 +88,12 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
@Mock private lateinit var statusBarIconController: StatusBarIconController
@Mock private lateinit var iconManagerFactory: StatusBarIconController.TintedIconManager.Factory
@Mock private lateinit var iconManager: StatusBarIconController.TintedIconManager
- @Mock private lateinit var qsCarrierGroupController: QSCarrierGroupController
- @Mock private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+ @Mock private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
+ @Mock
+ private lateinit var mShadeCarrierGroupControllerBuilder: ShadeCarrierGroupController.Builder
@Mock private lateinit var clock: Clock
@Mock private lateinit var date: VariableDateView
- @Mock private lateinit var carrierGroup: QSCarrierGroup
+ @Mock private lateinit var carrierGroup: ShadeCarrierGroup
@Mock private lateinit var batteryMeterView: BatteryMeterView
@Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
@Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
@@ -131,7 +132,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
- whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+ whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
.thenReturn(batteryMeterView)
@@ -142,9 +143,10 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
whenever(view.context).thenReturn(viewContext)
whenever(view.resources).thenReturn(context.resources)
whenever(statusIcons.context).thenReturn(context)
- whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
- .thenReturn(qsCarrierGroupControllerBuilder)
- whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+ whenever(mShadeCarrierGroupControllerBuilder.setShadeCarrierGroup(any()))
+ .thenReturn(mShadeCarrierGroupControllerBuilder)
+ whenever(mShadeCarrierGroupControllerBuilder.build())
+ .thenReturn(mShadeCarrierGroupController)
whenever(view.setVisibility(anyInt())).then {
viewVisibility = it.arguments[0] as Int
null
@@ -175,7 +177,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
variableDateViewControllerFactory,
batteryMeterViewController,
dumpManager,
- qsCarrierGroupControllerBuilder,
+ mShadeCarrierGroupControllerBuilder,
combinedShadeHeadersConstraintManager,
demoModeController,
qsBatteryModeController,
@@ -189,7 +191,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
@Test
fun updateListeners_registersWhenVisible() {
makeShadeVisible()
- verify(qsCarrierGroupController).setListening(true)
+ verify(mShadeCarrierGroupController).setListening(true)
verify(statusBarIconController).addIconGroup(any())
}
@@ -213,7 +215,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
@Test
fun singleCarrier_enablesCarrierIconsInStatusIcons() {
- whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(true)
+ whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(true)
makeShadeVisible()
@@ -222,7 +224,7 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
@Test
fun dualCarrier_disablesCarrierIconsInStatusIcons() {
- whenever(qsCarrierGroupController.isSingleCarrier).thenReturn(false)
+ whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false)
makeShadeVisible()
@@ -349,9 +351,9 @@ class ShadeHeaderControllerTest : SysuiTestCase() {
verify(batteryMeterViewController).init()
verify(batteryMeterViewController).ignoreTunerUpdates()
- val inOrder = Mockito.inOrder(qsCarrierGroupControllerBuilder)
- inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
- inOrder.verify(qsCarrierGroupControllerBuilder).build()
+ val inOrder = Mockito.inOrder(mShadeCarrierGroupControllerBuilder)
+ inOrder.verify(mShadeCarrierGroupControllerBuilder).setShadeCarrierGroup(carrierGroup)
+ inOrder.verify(mShadeCarrierGroupControllerBuilder).build()
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
index 75be74b13c87..7a9ef62278a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/CellSignalStateTest.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier
+package com.android.systemui.shade.carrier
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
@@ -45,4 +45,4 @@ class CellSignalStateTest : SysuiTestCase() {
assertNotSame(c, other)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 1e7722ae8395..2ef3d60c7754 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
import static com.google.common.truth.Truth.assertThat;
@@ -63,13 +63,13 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
-public class QSCarrierGroupControllerTest extends LeakCheckedTest {
+public class ShadeCarrierGroupControllerTest extends LeakCheckedTest {
- private QSCarrierGroupController mQSCarrierGroupController;
+ private ShadeCarrierGroupController mShadeCarrierGroupController;
private SignalCallback mSignalCallback;
private CarrierTextManager.CarrierTextCallback mCallback;
@Mock
- private QSCarrierGroup mQSCarrierGroup;
+ private ShadeCarrierGroup mShadeCarrierGroup;
@Mock
private ActivityStarter mActivityStarter;
@Mock
@@ -81,14 +81,14 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
@Mock
private CarrierConfigTracker mCarrierConfigTracker;
@Mock
- private QSCarrier mQSCarrier1;
+ private ShadeCarrier mShadeCarrier1;
@Mock
- private QSCarrier mQSCarrier2;
+ private ShadeCarrier mShadeCarrier2;
@Mock
- private QSCarrier mQSCarrier3;
+ private ShadeCarrier mShadeCarrier3;
private TestableLooper mTestableLooper;
@Mock
- private QSCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
+ private ShadeCarrierGroupController.OnSingleCarrierChangedListener mOnSingleCarrierChangedListener;
private FakeSlotIndexResolver mSlotIndexResolver;
private ClickListenerTextView mNoCarrierTextView;
@@ -116,28 +116,28 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
.setListening(any(CarrierTextManager.CarrierTextCallback.class));
mNoCarrierTextView = new ClickListenerTextView(mContext);
- when(mQSCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
- when(mQSCarrierGroup.getCarrier1View()).thenReturn(mQSCarrier1);
- when(mQSCarrierGroup.getCarrier2View()).thenReturn(mQSCarrier2);
- when(mQSCarrierGroup.getCarrier3View()).thenReturn(mQSCarrier3);
- when(mQSCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext));
- when(mQSCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext));
+ when(mShadeCarrierGroup.getNoSimTextView()).thenReturn(mNoCarrierTextView);
+ when(mShadeCarrierGroup.getCarrier1View()).thenReturn(mShadeCarrier1);
+ when(mShadeCarrierGroup.getCarrier2View()).thenReturn(mShadeCarrier2);
+ when(mShadeCarrierGroup.getCarrier3View()).thenReturn(mShadeCarrier3);
+ when(mShadeCarrierGroup.getCarrierDivider1()).thenReturn(new View(mContext));
+ when(mShadeCarrierGroup.getCarrierDivider2()).thenReturn(new View(mContext));
mSlotIndexResolver = new FakeSlotIndexResolver();
- mQSCarrierGroupController = new QSCarrierGroupController.Builder(
+ mShadeCarrierGroupController = new ShadeCarrierGroupController.Builder(
mActivityStarter, handler, TestableLooper.get(this).getLooper(),
mNetworkController, mCarrierTextControllerBuilder, mContext, mCarrierConfigTracker,
mSlotIndexResolver)
- .setQSCarrierGroup(mQSCarrierGroup)
+ .setShadeCarrierGroup(mShadeCarrierGroup)
.build();
- mQSCarrierGroupController.setListening(true);
+ mShadeCarrierGroupController.setListening(true);
}
@Test
public void testInitiallyMultiCarrier() {
- assertFalse(mQSCarrierGroupController.isSingleCarrier());
+ assertFalse(mShadeCarrierGroupController.isSingleCarrier());
}
@Test // throws no Exception
@@ -257,12 +257,12 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
true /* airplaneMode */);
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
- assertEquals(View.GONE, mQSCarrierGroup.getNoSimTextView().getVisibility());
+ assertEquals(View.GONE, mShadeCarrierGroup.getNoSimTextView().getVisibility());
}
@Test
public void testListenerNotCalledOnRegistreation() {
- mQSCarrierGroupController
+ mShadeCarrierGroupController
.setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
verify(mOnSingleCarrierChangedListener, never()).onSingleCarrierChanged(anyBoolean());
@@ -282,9 +282,9 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
- verify(mQSCarrier1).updateState(any(), eq(true));
- verify(mQSCarrier2).updateState(any(), eq(true));
- verify(mQSCarrier3).updateState(any(), eq(true));
+ verify(mShadeCarrier1).updateState(any(), eq(true));
+ verify(mShadeCarrier2).updateState(any(), eq(true));
+ verify(mShadeCarrier3).updateState(any(), eq(true));
}
@Test
@@ -301,9 +301,9 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
- verify(mQSCarrier1).updateState(any(), eq(false));
- verify(mQSCarrier2).updateState(any(), eq(false));
- verify(mQSCarrier3).updateState(any(), eq(false));
+ verify(mShadeCarrier1).updateState(any(), eq(false));
+ verify(mShadeCarrier2).updateState(any(), eq(false));
+ verify(mShadeCarrier3).updateState(any(), eq(false));
}
@Test
@@ -327,7 +327,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
mCallback.updateCarrierInfo(singleCarrierInfo);
mTestableLooper.processAllMessages();
- mQSCarrierGroupController
+ mShadeCarrierGroupController
.setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
reset(mOnSingleCarrierChangedListener);
@@ -353,7 +353,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
mCallback.updateCarrierInfo(singleCarrierInfo);
mTestableLooper.processAllMessages();
- mQSCarrierGroupController
+ mShadeCarrierGroupController
.setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
mCallback.updateCarrierInfo(singleCarrierInfo);
@@ -375,7 +375,7 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
mCallback.updateCarrierInfo(multiCarrierInfo);
mTestableLooper.processAllMessages();
- mQSCarrierGroupController
+ mShadeCarrierGroupController
.setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
mCallback.updateCarrierInfo(multiCarrierInfo);
@@ -389,12 +389,12 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
ArgumentCaptor<View.OnClickListener> captor =
ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mQSCarrier1).setOnClickListener(captor.capture());
- verify(mQSCarrier2).setOnClickListener(captor.getValue());
- verify(mQSCarrier3).setOnClickListener(captor.getValue());
+ verify(mShadeCarrier1).setOnClickListener(captor.capture());
+ verify(mShadeCarrier2).setOnClickListener(captor.getValue());
+ verify(mShadeCarrier3).setOnClickListener(captor.getValue());
assertThat(mNoCarrierTextView.getOnClickListener()).isSameInstanceAs(captor.getValue());
- verify(mQSCarrierGroup, never()).setOnClickListener(any());
+ verify(mShadeCarrierGroup, never()).setOnClickListener(any());
}
@Test
@@ -402,10 +402,10 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
ArgumentCaptor<View.OnClickListener> captor =
ArgumentCaptor.forClass(View.OnClickListener.class);
- verify(mQSCarrier1).setOnClickListener(captor.capture());
- when(mQSCarrier1.isVisibleToUser()).thenReturn(false);
+ verify(mShadeCarrier1).setOnClickListener(captor.capture());
+ when(mShadeCarrier1.isVisibleToUser()).thenReturn(false);
- captor.getValue().onClick(mQSCarrier1);
+ captor.getValue().onClick(mShadeCarrier1);
verifyZeroInteractions(mActivityStarter);
}
@@ -415,17 +415,17 @@ public class QSCarrierGroupControllerTest extends LeakCheckedTest {
ArgumentCaptor.forClass(View.OnClickListener.class);
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
- verify(mQSCarrier1).setOnClickListener(listenerCaptor.capture());
- when(mQSCarrier1.isVisibleToUser()).thenReturn(true);
+ verify(mShadeCarrier1).setOnClickListener(listenerCaptor.capture());
+ when(mShadeCarrier1.isVisibleToUser()).thenReturn(true);
- listenerCaptor.getValue().onClick(mQSCarrier1);
+ listenerCaptor.getValue().onClick(mShadeCarrier1);
verify(mActivityStarter)
.postStartActivityDismissingKeyguard(intentCaptor.capture(), anyInt());
assertThat(intentCaptor.getValue().getAction())
.isEqualTo(Settings.ACTION_WIRELESS_SETTINGS);
}
- private class FakeSlotIndexResolver implements QSCarrierGroupController.SlotIndexResolver {
+ private class FakeSlotIndexResolver implements ShadeCarrierGroupController.SlotIndexResolver {
public boolean overrideInvalid;
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
index 9115ab3bacca..44613103a5b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2020 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.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.carrier;
+package com.android.systemui.shade.carrier;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -39,9 +39,9 @@ import org.junit.runner.RunWith;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
-public class QSCarrierTest extends SysuiTestCase {
+public class ShadeCarrierTest extends SysuiTestCase {
- private QSCarrier mQSCarrier;
+ private ShadeCarrier mShadeCarrier;
private TestableLooper mTestableLooper;
private int mSignalIconId;
@@ -51,7 +51,7 @@ public class QSCarrierTest extends SysuiTestCase {
LayoutInflater inflater = LayoutInflater.from(mContext);
mContext.ensureTestableResources();
mTestableLooper.runWithLooper(() ->
- mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
+ mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null));
// In this case, the id is an actual drawable id
mSignalIconId = TelephonyIcons.MOBILE_CALL_STRENGTH_ICONS[0];
@@ -61,76 +61,76 @@ public class QSCarrierTest extends SysuiTestCase {
public void testUpdateState_first() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- assertTrue(mQSCarrier.updateState(c, false));
+ assertTrue(mShadeCarrier.updateState(c, false));
}
@Test
public void testUpdateState_same() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- assertTrue(mQSCarrier.updateState(c, false));
- assertFalse(mQSCarrier.updateState(c, false));
+ assertTrue(mShadeCarrier.updateState(c, false));
+ assertFalse(mShadeCarrier.updateState(c, false));
}
@Test
public void testUpdateState_changed() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- assertTrue(mQSCarrier.updateState(c, false));
+ assertTrue(mShadeCarrier.updateState(c, false));
CellSignalState other = c.changeVisibility(false);
- assertTrue(mQSCarrier.updateState(other, false));
+ assertTrue(mShadeCarrier.updateState(other, false));
}
@Test
public void testUpdateState_singleCarrier_first() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- assertTrue(mQSCarrier.updateState(c, true));
+ assertTrue(mShadeCarrier.updateState(c, true));
}
@Test
public void testUpdateState_singleCarrier_noShowIcon() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- mQSCarrier.updateState(c, true);
+ mShadeCarrier.updateState(c, true);
- assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
+ assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility());
}
@Test
public void testUpdateState_multiCarrier_showIcon() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- mQSCarrier.updateState(c, false);
+ mShadeCarrier.updateState(c, false);
- assertEquals(View.VISIBLE, mQSCarrier.getRSSIView().getVisibility());
+ assertEquals(View.VISIBLE, mShadeCarrier.getRSSIView().getVisibility());
}
@Test
public void testUpdateState_changeSingleMultiSingle() {
CellSignalState c = new CellSignalState(true, mSignalIconId, "", "", false);
- mQSCarrier.updateState(c, true);
- assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
+ mShadeCarrier.updateState(c, true);
+ assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility());
- mQSCarrier.updateState(c, false);
- assertEquals(View.VISIBLE, mQSCarrier.getRSSIView().getVisibility());
+ mShadeCarrier.updateState(c, false);
+ assertEquals(View.VISIBLE, mShadeCarrier.getRSSIView().getVisibility());
- mQSCarrier.updateState(c, true);
- assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
+ mShadeCarrier.updateState(c, true);
+ assertEquals(View.GONE, mShadeCarrier.getRSSIView().getVisibility());
}
@Test
public void testCarrierNameMaxWidth_smallScreen_fromResource() {
int maxEms = 10;
- mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources().addOverride(R.integer.shade_carrier_max_em, maxEms);
mContext.getOrCreateTestableResources()
.addOverride(R.bool.config_use_large_screen_shade_header, false);
- TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+ TextView carrierText = mShadeCarrier.requireViewById(R.id.shade_carrier_text);
- mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+ mShadeCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
assertEquals(maxEms, carrierText.getMaxEms());
}
@@ -138,12 +138,12 @@ public class QSCarrierTest extends SysuiTestCase {
@Test
public void testCarrierNameMaxWidth_largeScreen_maxInt() {
int maxEms = 10;
- mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+ mContext.getOrCreateTestableResources().addOverride(R.integer.shade_carrier_max_em, maxEms);
mContext.getOrCreateTestableResources()
.addOverride(R.bool.config_use_large_screen_shade_header, true);
- TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+ TextView carrierText = mShadeCarrier.requireViewById(R.id.shade_carrier_text);
- mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+ mShadeCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 569f90b64609..4438b98f6fad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -614,9 +614,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void onBiometricHelp_coEx_faceFailure() {
createController();
- // GIVEN unlocking with fingerprint is possible
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
- .thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -641,9 +640,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void onBiometricHelp_coEx_faceUnavailable() {
createController();
- // GIVEN unlocking with fingerprint is possible
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
- .thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -664,6 +662,35 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mContext.getString(R.string.keyguard_suggest_fingerprint));
}
+
+ @Test
+ public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() {
+ createController();
+
+ // GIVEN unlocking with fingerprint is possible but not allowed
+ setupFingerprintUnlockPossible(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed())
+ .thenReturn(false);
+
+ String message = "A message";
+ mController.setVisible(true);
+
+ // WHEN there's a face unavailable message
+ mController.getKeyguardCallback().onBiometricHelp(
+ BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
+ message,
+ BiometricSourceType.FACE);
+
+ // THEN show sequential messages such as: 'face unlock unavailable' and
+ // 'try fingerprint instead'
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE,
+ message);
+ verifyIndicationMessage(
+ INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
+ mContext.getString(R.string.keyguard_unlock));
+ }
+
@Test
public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
createController();
@@ -818,8 +845,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
createController();
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ fingerprintUnlockIsPossibleAndAllowed();
String message = "A message";
mController.setVisible(true);
@@ -832,9 +858,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void sendFaceHelpMessages_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help messages received that are allowed to show
final String helpString = "helpString";
@@ -859,9 +884,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help messages received that aren't supposed to show
final String helpString = "helpString";
@@ -886,9 +910,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void sendAllFaceHelpMessages_fingerprintNotEnrolled() {
createController();
- // GIVEN fingerprint NOT enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(false);
+ // GIVEN fingerprint NOT possible
+ fingerprintUnlockIsNotPossible();
// WHEN help messages received
final Set<CharSequence> helpStrings = new HashSet<>();
@@ -917,9 +940,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() {
createController();
- // GIVEN fingerprint NOT enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(false);
+ // GIVEN fingerprint not possible
+ fingerprintUnlockIsNotPossible();
// WHEN help message received and deferred message is valid
final String helpString = "helpMsg";
@@ -948,9 +970,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() {
createController();
- // GIVEN fingerprint enrolled
- when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
- getCurrentUser())).thenReturn(true);
+ // GIVEN unlocking with fingerprint is possible and allowed
+ fingerprintUnlockIsPossibleAndAllowed();
// WHEN help message received and deferredMessage is valid
final String helpString = "helpMsg";
@@ -1500,7 +1521,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() {
createController();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("first lockout");
verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
@@ -1559,7 +1580,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
@Test
public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() {
createController();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("first lockout");
clearInvocations(mRotateTextViewController);
@@ -1668,7 +1689,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() {
createController();
screenIsTurningOn();
- fingerprintUnlockIsPossible();
+ fingerprintUnlockIsPossibleAndAllowed();
onFaceLockoutError("lockout error");
verifyNoMoreInteractions(mRotateTextViewController);
@@ -1746,8 +1767,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
setupFingerprintUnlockPossible(false);
}
- private void fingerprintUnlockIsPossible() {
+ private void fingerprintUnlockIsPossibleAndAllowed() {
setupFingerprintUnlockPossible(true);
+ when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true);
}
private void setupFingerprintUnlockPossible(boolean possible) {
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 7b59cc284181..08a9f3139d71 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
@@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler
private val fakeFeatureFlags = FakeFeatureFlags()
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
index be3b7234a1a2..aff705f1f3bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
-import androidx.core.animation.AnimatorTestRule2
+import androidx.core.animation.AnimatorTestRule
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -51,7 +51,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class NotificationWakeUpCoordinatorTest : SysuiTestCase() {
- @get:Rule val animatorTestRule = AnimatorTestRule2()
+ @get:Rule val animatorTestRule = AnimatorTestRule()
private val dumpManager: DumpManager = mock()
private val headsUpManager: HeadsUpManager = mock()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
index 30708a7cb2fe..ac66ad9e9c8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -97,6 +97,59 @@ public class HighPriorityProviderTest extends SysuiTestCase {
}
@Test
+ public void highImportanceConversation() {
+ // GIVEN notification is high importance and is a people notification
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .build();
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_HIGH)
+ .build();
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry))
+ .thenReturn(TYPE_PERSON);
+
+ // THEN it is high priority conversation
+ assertTrue(mHighPriorityProvider.isHighPriorityConversation(entry));
+ }
+
+ @Test
+ public void lowImportanceConversation() {
+ // GIVEN notification is high importance and is a people notification
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .build();
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(entry))
+ .thenReturn(TYPE_PERSON);
+
+ // THEN it is low priority conversation
+ assertFalse(mHighPriorityProvider.isHighPriorityConversation(entry));
+ }
+
+ @Test
+ public void highImportanceConversationWhenAnyOfChildIsHighPriority() {
+ // GIVEN notification is high importance and is a people notification
+ final NotificationEntry summary = createNotifEntry(false);
+ final NotificationEntry lowPriorityChild = createNotifEntry(false);
+ final NotificationEntry highPriorityChild = createNotifEntry(true);
+ when(mPeopleNotificationIdentifier
+ .getPeopleNotificationType(summary))
+ .thenReturn(TYPE_PERSON);
+ final GroupEntry groupEntry = new GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(List.of(lowPriorityChild, highPriorityChild))
+ .build();
+
+ // THEN the groupEntry is high priority conversation since it has a high priority child
+ assertTrue(mHighPriorityProvider.isHighPriorityConversation(groupEntry));
+ }
+
+ @Test
public void messagingStyle() {
// GIVEN notification is low importance but has messaging style
final Notification notification = new Notification.Builder(mContext, "test")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
index 742fcf5e03c3..55ea31571dfe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt
@@ -17,6 +17,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.NotificationChannel
+import android.app.NotificationManager.IMPORTANCE_DEFAULT
+import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.NotificationManager.IMPORTANCE_LOW
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -31,10 +34,13 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
+import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManagerImpl
import com.android.systemui.statusbar.notification.collection.render.NodeController
import com.android.systemui.statusbar.notification.icon.ConversationIconManager
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
@@ -55,7 +61,8 @@ import org.mockito.Mockito.`when` as whenever
class ConversationCoordinatorTest : SysuiTestCase() {
// captured listeners and pluggables:
private lateinit var promoter: NotifPromoter
- private lateinit var peopleSectioner: NotifSectioner
+ private lateinit var peopleAlertingSectioner: NotifSectioner
+ private lateinit var peopleSilentSectioner: NotifSectioner
private lateinit var peopleComparator: NotifComparator
private lateinit var beforeRenderListListener: OnBeforeRenderListListener
@@ -76,6 +83,7 @@ class ConversationCoordinatorTest : SysuiTestCase() {
coordinator = ConversationCoordinator(
peopleNotificationIdentifier,
conversationIconManager,
+ HighPriorityProvider(peopleNotificationIdentifier, GroupMembershipManagerImpl()),
headerController
)
whenever(channel.isImportantConversation).thenReturn(true)
@@ -90,12 +98,13 @@ class ConversationCoordinatorTest : SysuiTestCase() {
verify(pipeline).addOnBeforeRenderListListener(capture())
}
- peopleSectioner = coordinator.sectioner
- peopleComparator = peopleSectioner.comparator!!
+ peopleAlertingSectioner = coordinator.peopleAlertingSectioner
+ peopleSilentSectioner = coordinator.peopleSilentSectioner
+ peopleComparator = peopleAlertingSectioner.comparator!!
entry = NotificationEntryBuilder().setChannel(channel).build()
- val section = NotifSection(peopleSectioner, 0)
+ val section = NotifSection(peopleAlertingSectioner, 0)
entryA = NotificationEntryBuilder().setChannel(channel)
.setSection(section).setTag("A").build()
entryB = NotificationEntryBuilder().setChannel(channel)
@@ -129,13 +138,67 @@ class ConversationCoordinatorTest : SysuiTestCase() {
}
@Test
- fun testInPeopleSection() {
+ fun testInAlertingPeopleSectionWhenTheImportanceIsAtLeastDefault() {
+ // GIVEN
+ val alertingEntry = NotificationEntryBuilder().setChannel(channel)
+ .setImportance(IMPORTANCE_DEFAULT).build()
+ whenever(peopleNotificationIdentifier.getPeopleNotificationType(alertingEntry))
+ .thenReturn(TYPE_PERSON)
+
+ // put alerting people notifications in this section
+ assertThat(peopleAlertingSectioner.isInSection(alertingEntry)).isTrue()
+ }
+
+ @Test
+ fun testInSilentPeopleSectionWhenTheImportanceIsLowerThanDefault() {
+ // GIVEN
+ val silentEntry = NotificationEntryBuilder().setChannel(channel)
+ .setImportance(IMPORTANCE_LOW).build()
+ whenever(peopleNotificationIdentifier.getPeopleNotificationType(silentEntry))
+ .thenReturn(TYPE_PERSON)
+
+ // THEN put silent people notifications in this section
+ assertThat(peopleSilentSectioner.isInSection(silentEntry)).isTrue()
+ // People Alerting sectioning happens before the silent one.
+ // It claims high important conversations and rest of conversations will be considered as silent.
+ assertThat(peopleAlertingSectioner.isInSection(silentEntry)).isFalse()
+ }
+
+ @Test
+ fun testNotInPeopleSection() {
+ // GIVEN
+ val entry = NotificationEntryBuilder().setChannel(channel)
+ .setImportance(IMPORTANCE_LOW).build()
+ val importantEntry = NotificationEntryBuilder().setChannel(channel)
+ .setImportance(IMPORTANCE_HIGH).build()
whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry))
- .thenReturn(TYPE_PERSON)
+ .thenReturn(TYPE_NON_PERSON)
+ whenever(peopleNotificationIdentifier.getPeopleNotificationType(importantEntry))
+ .thenReturn(TYPE_NON_PERSON)
- // only put people notifications in this section
- assertTrue(peopleSectioner.isInSection(entry))
- assertFalse(peopleSectioner.isInSection(NotificationEntryBuilder().build()))
+ // THEN - only put people notification either silent or alerting
+ assertThat(peopleSilentSectioner.isInSection(entry)).isFalse()
+ assertThat(peopleAlertingSectioner.isInSection(importantEntry)).isFalse()
+ }
+
+ @Test
+ fun testInAlertingPeopleSectionWhenThereIsAnImportantChild(){
+ // GIVEN
+ val altChildA = NotificationEntryBuilder().setTag("A")
+ .setImportance(IMPORTANCE_DEFAULT).build()
+ val altChildB = NotificationEntryBuilder().setTag("B")
+ .setImportance(IMPORTANCE_LOW).build()
+ val summary = NotificationEntryBuilder().setId(2)
+ .setImportance(IMPORTANCE_LOW).setChannel(channel).build()
+ val groupEntry = GroupEntryBuilder()
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .setSummary(summary)
+ .setChildren(listOf(altChildA, altChildB))
+ .build()
+ whenever(peopleNotificationIdentifier.getPeopleNotificationType(summary))
+ .thenReturn(TYPE_PERSON)
+ // THEN
+ assertThat(peopleAlertingSectioner.isInSection(groupEntry)).isTrue()
}
@Test
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 8109e24a1e52..c2a2a40b7e5d 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
@@ -25,6 +25,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.advanceTimeBy
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+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.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.notification.NotifPipelineFlags
@@ -69,6 +73,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
private val headsUpManager: HeadsUpManager = mock()
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
private val keyguardRepository = FakeKeyguardRepository()
+ private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
private val notifPipelineFlags: NotifPipelineFlags = mock()
private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
@@ -118,6 +123,33 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() {
+ whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
+
+ // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present
+ keyguardRepository.setKeyguardShowing(false)
+ whenever(statusBarStateController.isExpanded).thenReturn(false)
+ runKeyguardCoordinatorTest {
+ val fakeEntry = NotificationEntryBuilder().build()
+ collectionListener.onEntryAdded(fakeEntry)
+
+ // WHEN: The device transitions to AOD
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED),
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: The shade is expanded
+ whenever(statusBarStateController.isExpanded).thenReturn(true)
+ statusBarStateListener.onExpandedChanged(true)
+ testScheduler.runCurrent()
+
+ // THEN: The notification is still treated as "unseen" and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
fun unseenFilter_headsUpMarkedAsSeen() {
whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true)
@@ -373,6 +405,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
headsUpManager,
keyguardNotifVisibilityProvider,
keyguardRepository,
+ keyguardTransitionRepository,
notifPipelineFlags,
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index d5c0c5564af6..3d1253e2b05d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -52,7 +52,6 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -73,7 +72,6 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private SectionStyleProvider mSectionStyleProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
@@ -100,7 +98,6 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mRankingCoordinator = new RankingCoordinator(
mStatusBarStateController,
mHighPriorityProvider,
- mSectionStyleProvider,
mAlertingHeaderController,
mSilentHeaderController,
mSilentNodeController);
@@ -108,7 +105,6 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mEntry.setRanking(getRankingForUnfilteredNotif().build());
mRankingCoordinator.attach(mNotifPipeline);
- verify(mSectionStyleProvider).setMinimizedSections(any());
verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
new file mode 100644
index 000000000000..cbb08946a1b0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -0,0 +1,78 @@
+package com.android.systemui.statusbar.notification.interruption
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_NOT_IMPORTANT_ENOUGH
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_BY_DND
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.NO_FSI_SUPPRESSED_ONLY_BY_DND
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.DecisionImpl
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper.FullScreenIntentDecisionImpl
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationInterruptStateProviderWrapperTest : SysuiTestCase() {
+
+ @Test
+ fun decisionOfTrue() {
+ assertTrue(DecisionImpl.of(true).shouldInterrupt)
+ }
+
+ @Test
+ fun decisionOfFalse() {
+ assertFalse(DecisionImpl.of(false).shouldInterrupt)
+ }
+
+ @Test
+ fun decisionOfTrueInterned() {
+ assertEquals(DecisionImpl.of(true), DecisionImpl.of(true))
+ }
+
+ @Test
+ fun decisionOfFalseInterned() {
+ assertEquals(DecisionImpl.of(false), DecisionImpl.of(false))
+ }
+
+ @Test
+ fun fullScreenIntentDecisionShouldInterrupt() {
+ makeFsiDecision(FSI_DEVICE_NOT_INTERACTIVE).let {
+ assertTrue(it.shouldInterrupt)
+ assertFalse(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ @Test
+ fun fullScreenIntentDecisionShouldNotInterrupt() {
+ makeFsiDecision(NO_FSI_NOT_IMPORTANT_ENOUGH).let {
+ assertFalse(it.shouldInterrupt)
+ assertFalse(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ @Test
+ fun fullScreenIntentDecisionWouldInterruptWithoutDnd() {
+ makeFsiDecision(NO_FSI_SUPPRESSED_ONLY_BY_DND).let {
+ assertFalse(it.shouldInterrupt)
+ assertTrue(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ @Test
+ fun fullScreenIntentDecisionWouldNotInterruptEvenWithoutDnd() {
+ makeFsiDecision(NO_FSI_SUPPRESSED_BY_DND).let {
+ assertFalse(it.shouldInterrupt)
+ assertFalse(it.wouldInterruptWithoutDnd)
+ }
+ }
+
+ private fun makeFsiDecision(originalDecision: FullScreenIntentDecision) =
+ FullScreenIntentDecisionImpl(NotificationEntryBuilder().build(), originalDecision)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 7d022192f935..9186c35e2b47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -100,7 +100,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL);
FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
- fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
new file mode 100644
index 000000000000..2cc375b3d0ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.shelf.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.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfInteractorTest : SysuiTestCase() {
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ private val underTest =
+ NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+
+ @Test
+ fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(shelfStatic).isFalse()
+ }
+
+ @Test
+ fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+ assertThat(shelfStatic).isFalse()
+ }
+
+ @Test
+ fun shelfIsStatic_whenBypass() = runTest {
+ val shelfStatic by collectLastValue(underTest.isShelfStatic)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(shelfStatic).isTrue()
+ }
+
+ @Test
+ fun shelfOnKeyguard_whenKeyguardShowing() = runTest {
+ val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+ keyguardRepository.setKeyguardShowing(true)
+
+ assertThat(onKeyguard).isTrue()
+ }
+
+ @Test
+ fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest {
+ val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(onKeyguard).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
new file mode 100644
index 000000000000..439edaf3faaf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.shelf.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.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class NotificationShelfViewModelTest : SysuiTestCase() {
+
+ private val keyguardRepository = FakeKeyguardRepository()
+ private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ private val interactor =
+ NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository)
+ private val underTest = NotificationShelfViewModel(interactor)
+
+ @Test
+ fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(canModifyNotifColor).isTrue()
+ }
+
+ @Test
+ fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = false
+
+ assertThat(canModifyNotifColor).isTrue()
+ }
+
+ @Test
+ fun cannotModifyColorOfNotifications_whenBypass() = runTest {
+ val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications)
+
+ keyguardRepository.setKeyguardShowing(true)
+ deviceEntryFaceAuthRepository.isBypassEnabled.value = true
+
+ assertThat(canModifyNotifColor).isFalse()
+ }
+
+ @Test
+ fun isClickable_whenKeyguardShowing() = runTest {
+ val isClickable by collectLastValue(underTest.isClickable)
+
+ keyguardRepository.setKeyguardShowing(true)
+
+ assertThat(isClickable).isTrue()
+ }
+
+ @Test
+ fun isNotClickable_whenKeyguardNotShowing() = runTest {
+ val isClickable by collectLastValue(underTest.isClickable)
+
+ keyguardRepository.setKeyguardShowing(false)
+
+ assertThat(isClickable).isFalse()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index e6f10cdafe03..5279740a8702 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -23,6 +23,7 @@ import android.view.View.VISIBLE
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.media.controls.pipeline.MediaDataManager
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -48,6 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
@Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
@Mock
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+ @Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var stackLayout: NotificationStackScrollLayout
private val testableResources = mContext.orCreateTestableResources
@@ -67,7 +69,9 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
NotificationStackSizeCalculator(
statusBarStateController = sysuiStatusBarStateController,
lockscreenShadeTransitionController = lockscreenShadeTransitionController,
- testableResources.resources)
+ mediaDataManager = mediaDataManager,
+ testableResources.resources
+ )
}
@Test
@@ -76,7 +80,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
val maxNotifications =
computeMaxKeyguardNotifications(
- rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f)
+ rows,
+ spaceForNotifications = 0f,
+ spaceForShelf = 0f,
+ shelfHeight = 0f
+ )
assertThat(maxNotifications).isEqualTo(0)
}
@@ -91,7 +99,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
rows,
spaceForNotifications = Float.MAX_VALUE,
spaceForShelf = Float.MAX_VALUE,
- shelfHeight)
+ shelfHeight
+ )
assertThat(maxNotifications).isEqualTo(numberOfRows)
}
@@ -111,6 +120,28 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
+ fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f)
+
+ val row = createMockRow(10f, isSticky = true)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val maxNotifications =
+ computeMaxKeyguardNotifications(
+ listOf(row),
+ /* spaceForNotifications= */ 5f,
+ /* spaceForShelf= */ 0f,
+ /* shelfHeight= */ 0f
+ )
+
+ assertThat(maxNotifications).isEqualTo(1)
+ }
+
+ @Test
fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() {
setGapHeight(gapHeight)
val shelfHeight = shelfHeight + dividerHeight
@@ -126,7 +157,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
val maxNotifications =
computeMaxKeyguardNotifications(
- rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+ rows,
+ spaceForNotifications + 1,
+ spaceForShelf,
+ shelfHeight
+ )
assertThat(maxNotifications).isEqualTo(2)
}
@@ -136,24 +171,23 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
// Each row in separate section.
setGapHeight(gapHeight)
- val spaceForNotifications =
+ val notifSpace =
listOf(
rowHeight,
dividerHeight + gapHeight + rowHeight,
)
.sum()
- val spaceForShelf = dividerHeight + gapHeight + shelfHeight
- val spaceUsed = spaceForNotifications + spaceForShelf
+ val shelfSpace = dividerHeight + gapHeight + shelfHeight
+ val spaceUsed = notifSpace + shelfSpace
val rows =
listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
val maxNotifications =
- computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight)
+ computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight)
assertThat(maxNotifications).isEqualTo(2)
- val height =
- sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+ val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
assertThat(height).isEqualTo(spaceUsed)
}
@@ -170,11 +204,14 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
// test that we only use space required
val maxNotifications =
computeMaxKeyguardNotifications(
- rows, spaceForNotifications + 1, spaceForShelf, shelfHeight)
+ rows,
+ spaceForNotifications + 1,
+ spaceForShelf,
+ shelfHeight
+ )
assertThat(maxNotifications).isEqualTo(1)
- val height =
- sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
+ val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight)
assertThat(height).isEqualTo(spaceUsed)
}
@@ -200,60 +237,101 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
- fun spaceNeeded_onLockscreen_usesMinHeight() {
+ fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
- val expandableView = createMockRow(rowHeight)
+ val row = createMockRow(10f, isSticky = true)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val space =
+ sizeCalculator.getSpaceNeeded(
+ row,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(10f)
+ }
+
+ @Test
+ fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ val row = createMockRow(rowHeight)
+ whenever(row.heightWithoutLockscreenConstraints).thenReturn(10)
+ whenever(row.getMinHeight(any())).thenReturn(5)
+
+ val space =
+ sizeCalculator.getSpaceNeeded(
+ row,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(5)
+ }
+
+ @Test
+ fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() {
+ setGapHeight(0f)
+ // No divider height since we're testing one element where index = 0
+
+ val expandableView = createMockRow(10f, isSticky = true)
whenever(expandableView.getMinHeight(any())).thenReturn(5)
- whenever(expandableView.intrinsicHeight).thenReturn(10)
val space =
- sizeCalculator.spaceNeeded(
+ sizeCalculator.getSpaceNeeded(
expandableView,
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true)
- assertThat(space).isEqualTo(5)
+ onLockscreen = true
+ )
+ assertThat(space.whenSavingSpace).isEqualTo(5)
}
@Test
- fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() {
+ fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
- val expandableView = createMockStickyRow(rowHeight)
+ val expandableView = createMockRow(rowHeight)
whenever(expandableView.getMinHeight(any())).thenReturn(5)
whenever(expandableView.intrinsicHeight).thenReturn(10)
val space =
- sizeCalculator.spaceNeeded(
- expandableView,
- visibleIndex = 0,
- previousView = null,
- stack = stackLayout,
- onLockscreen = true)
- assertThat(space).isEqualTo(10)
+ sizeCalculator.getSpaceNeeded(
+ expandableView,
+ visibleIndex = 0,
+ previousView = null,
+ stack = stackLayout,
+ onLockscreen = true
+ )
+ assertThat(space.whenSavingSpace).isEqualTo(5)
}
@Test
- fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() {
+ fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
val expandableView = createMockRow(rowHeight)
- whenever(expandableView.getMinHeight(any())).thenReturn(5)
- whenever(expandableView.intrinsicHeight).thenReturn(10)
+ whenever(expandableView.getMinHeight(any())).thenReturn(1)
val space =
- sizeCalculator.spaceNeeded(
+ sizeCalculator.getSpaceNeeded(
expandableView,
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = false)
- assertThat(space).isEqualTo(10)
+ onLockscreen = false
+ )
+ assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
+ assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
}
private fun computeMaxKeyguardNotifications(
@@ -264,7 +342,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
): Int {
setupChildren(rows)
return sizeCalculator.computeMaxKeyguardNotifications(
- stackLayout, spaceForNotifications, spaceForShelf, shelfHeight)
+ stackLayout,
+ spaceForNotifications,
+ spaceForShelf,
+ shelfHeight
+ )
}
private fun setupChildren(children: List<ExpandableView>) {
@@ -280,30 +362,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
private fun createMockRow(
height: Float = rowHeight,
+ isSticky: Boolean = false,
isRemoved: Boolean = false,
- visibility: Int = VISIBLE
- ): ExpandableNotificationRow {
- val row = mock(ExpandableNotificationRow::class.java)
- val entry = mock(NotificationEntry::class.java)
- val sbn = mock(StatusBarNotification::class.java)
- whenever(entry.sbn).thenReturn(sbn)
- whenever(row.entry).thenReturn(entry)
- whenever(row.isRemoved).thenReturn(isRemoved)
- whenever(row.visibility).thenReturn(visibility)
- whenever(row.getMinHeight(any())).thenReturn(height.toInt())
- whenever(row.intrinsicHeight).thenReturn(height.toInt())
- return row
- }
-
- private fun createMockStickyRow(
- height: Float = rowHeight,
- isRemoved: Boolean = false,
- visibility: Int = VISIBLE
+ visibility: Int = VISIBLE,
): ExpandableNotificationRow {
val row = mock(ExpandableNotificationRow::class.java)
val entry = mock(NotificationEntry::class.java)
- whenever(entry.isStickyAndNotDemoted).thenReturn(true)
-
+ whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky)
val sbn = mock(StatusBarNotification::class.java)
whenever(entry.sbn).thenReturn(sbn)
whenever(row.entry).thenReturn(entry)
@@ -311,6 +376,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
whenever(row.visibility).thenReturn(visibility)
whenever(row.getMinHeight(any())).thenReturn(height.toInt())
whenever(row.intrinsicHeight).thenReturn(height.toInt())
+ whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt())
return row
}
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 32f0adfa1954..48710a42f616 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
@@ -133,6 +133,7 @@ import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -221,6 +222,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotificationListContainer mNotificationListContainer;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationPanelViewController mNotificationPanelViewController;
+ @Mock private ShadeLogger mShadeLogger;
@Mock private NotificationPanelView mNotificationPanelView;
@Mock private QuickSettingsController mQuickSettingsController;
@Mock private IStatusBarService mBarService;
@@ -469,6 +471,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mKeyguardViewMediator,
new DisplayMetrics(),
mMetricsLogger,
+ mShadeLogger,
mUiBgExecutor,
mNotificationMediaManager,
mLockscreenUserManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 746c92e485b7..02c459b11d07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -16,12 +16,10 @@
package com.android.systemui.statusbar.phone
-import android.animation.Animator
import android.os.Handler
import android.os.PowerManager
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
-import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
@@ -39,10 +37,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
@@ -111,27 +107,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
controller.onStartedWakingUp()
}
- @Test
- fun testAnimClearsEndListener() {
- val keyguardView = View(context)
- val animator = spy(keyguardView.animate())
- val keyguardSpy = spy(keyguardView)
- Mockito.`when`(keyguardSpy.animate()).thenReturn(animator)
- val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java)
- val endAction = ArgumentCaptor.forClass(Runnable::class.java)
- controller.animateInKeyguard(keyguardSpy, Runnable {})
- Mockito.verify(animator).setListener(listener.capture())
- Mockito.verify(animator).withEndAction(endAction.capture())
-
- // Verify that the listener is cleared if we cancel it.
- listener.value.onAnimationCancel(null)
- Mockito.verify(animator).setListener(null)
-
- // Verify that the listener is also cleared if the end action is triggered.
- endAction.value.run()
- verify(animator, times(2)).setListener(null)
- }
-
/**
* The AOD UI is shown during the screen off animation, after a delay to allow the light reveal
* animation to start. If the device is woken up during the screen off, we should *never* do
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index f9c72d523673..3591c1740329 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -67,6 +67,8 @@ class FakeMobileConnectionsRepository(
override val mobileIsDefault = MutableStateFlow(false)
+ override val hasCarrierMergedConnection = MutableStateFlow(false)
+
override val defaultConnectionIsValidated = MutableStateFlow(false)
private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 9d294cf098e2..7cc59b67414b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -68,6 +68,7 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
@@ -766,9 +767,66 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
+ runBlocking(IMMEDIATE) {
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileIsDefault_wifiDefault_mobileNotDefault() =
+ runBlocking(IMMEDIATE) {
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileIsDefault_ethernetDefault_mobileNotDefault() =
+ runBlocking(IMMEDIATE) {
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+ }
+
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
/** Regression test for b/272586234. */
@Test
- fun mobileIsDefault_carrierMergedViaWifi_isDefault() =
+ fun hasCarrierMergedConnection_carrierMergedViaWifi_isTrue() =
runBlocking(IMMEDIATE) {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -779,9 +837,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
assertThat(latest).isTrue()
@@ -789,7 +848,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
- fun mobileIsDefault_carrierMergedViaMobile_isDefault() =
+ fun hasCarrierMergedConnection_carrierMergedViaMobile_isTrue() =
runBlocking(IMMEDIATE) {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -800,9 +859,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
assertThat(latest).isTrue()
@@ -811,7 +871,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
/** Regression test for b/272586234. */
@Test
- fun mobileIsDefault_carrierMergedViaWifiWithVcnTransport_isDefault() =
+ fun hasCarrierMergedConnection_carrierMergedViaWifiWithVcnTransport_isTrue() =
runBlocking(IMMEDIATE) {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -822,9 +882,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
assertThat(latest).isTrue()
@@ -832,7 +893,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
- fun mobileIsDefault_carrierMergedViaMobileWithVcnTransport_isDefault() =
+ fun hasCarrierMergedConnection_carrierMergedViaMobileWithVcnTransport_isTrue() =
runBlocking(IMMEDIATE) {
val carrierMergedInfo =
mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
@@ -843,9 +904,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
assertThat(latest).isTrue()
@@ -853,37 +915,100 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
- fun mobileIsDefault_wifiDefault_mobileNotDefault() =
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingWifi_isTrue() =
runBlocking(IMMEDIATE) {
- val caps =
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingWifiCapabilities =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
}
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
- var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ yield()
- assertThat(latest).isFalse()
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
job.cancel()
}
@Test
- fun mobileIsDefault_ethernetDefault_mobileNotDefault() =
+ fun hasCarrierMergedConnection_isCarrierMergedViaUnderlyingCellular_isTrue() =
runBlocking(IMMEDIATE) {
- val caps =
+ var latest: Boolean? = null
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
mock<NetworkCapabilities>().also {
- whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
}
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ yield()
+
+ // THEN there's a carrier merged connection
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun hasCarrierMergedConnection_defaultNotCarrierMerged_butWifiRepoHasCarrierMerged_isTrue() =
+ runBlocking(IMMEDIATE) {
var latest: Boolean? = null
- val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+ val job = underTest.hasCarrierMergedConnection.onEach { latest = it }.launchIn(this)
+ // WHEN the default callback isn't carrier merged
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(false) }
+ val caps =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+ yield()
- assertThat(latest).isFalse()
+ // BUT the wifi repo has gotten updates that it *is* carrier merged
+ wifiRepository.setWifiNetwork(WIFI_NETWORK_CM)
+ yield()
+
+ // THEN hasCarrierMergedConnection is true
+ assertThat(latest).isTrue()
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 3ced7b2c4e6e..b2bbcfd3d6ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -49,6 +49,8 @@ class FakeMobileIconsInteractor(
FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
)
+ private val interactorCache: MutableMap<Int, FakeMobileIconInteractor> = mutableMapOf()
+
override val isDefaultConnectionFailed = MutableStateFlow(false)
override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
@@ -75,7 +77,15 @@ class FakeMobileIconsInteractor(
/** Always returns a new fake interactor */
override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
- return FakeMobileIconInteractor(tableLogBuffer)
+ return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it }
+ }
+
+ /**
+ * Returns the most recently created interactor for the given subId, or null if an interactor
+ * has never been created for that sub.
+ */
+ fun getInteractorForSubId(subId: Int): FakeMobileIconInteractor? {
+ return interactorCache[subId]
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index c5ceacac6446..dc683868f727 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -456,20 +456,63 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
@Test
- fun mobileIsDefault_usesRepoValue() =
+ fun mobileIsDefault_mobileFalseAndCarrierMergedFalse_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
+ connectionsRepository.mobileIsDefault.value = false
+ connectionsRepository.hasCarrierMergedConnection.value = false
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileIsDefault_mobileTrueAndCarrierMergedFalse_true() =
testScope.runTest {
var latest: Boolean? = null
val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
connectionsRepository.mobileIsDefault.value = true
+ connectionsRepository.hasCarrierMergedConnection.value = false
+
assertThat(latest).isTrue()
+ job.cancel()
+ }
+
+ /** Regression test for b/272586234. */
+ @Test
+ fun mobileIsDefault_mobileFalseAndCarrierMergedTrue_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
+
connectionsRepository.mobileIsDefault.value = false
- assertThat(latest).isFalse()
+ connectionsRepository.hasCarrierMergedConnection.value = true
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun mobileIsDefault_updatesWhenRepoUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job = underTest.mobileIsDefault.onEach { latest = it }.launchIn(this)
connectionsRepository.mobileIsDefault.value = true
assertThat(latest).isTrue()
+ connectionsRepository.mobileIsDefault.value = false
+ assertThat(latest).isFalse()
+
+ connectionsRepository.hasCarrierMergedConnection.value = true
+ assertThat(latest).isTrue()
+
job.cancel()
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index 1b6ab4d7af96..297cb9d691ba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -179,15 +179,71 @@ class MobileIconViewModelTest : SysuiTestCase() {
}
@Test
- fun iconId_cutout_whenDefaultDataDisabled() =
+ fun icon_usesLevelFromInteractor() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+ interactor.level.value = 3
+ assertThat(latest!!.level).isEqualTo(3)
+
+ interactor.level.value = 1
+ assertThat(latest!!.level).isEqualTo(1)
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_usesNumberOfLevelsFromInteractor() =
+ testScope.runTest {
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+ interactor.numberOfLevels.value = 5
+ assertThat(latest!!.numberOfLevels).isEqualTo(5)
+
+ interactor.numberOfLevels.value = 2
+ assertThat(latest!!.numberOfLevels).isEqualTo(2)
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_defaultDataDisabled_showExclamationTrue() =
testScope.runTest {
interactor.setIsDefaultDataEnabled(false)
var latest: SignalIconModel? = null
val job = underTest.icon.onEach { latest = it }.launchIn(this)
- val expected = defaultSignal(level = 1, connected = false)
- assertThat(latest).isEqualTo(expected)
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_defaultConnectionFailed_showExclamationTrue() =
+ testScope.runTest {
+ interactor.isDefaultConnectionFailed.value = true
+
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun icon_enabledAndNotFailed_showExclamationFalse() =
+ testScope.runTest {
+ interactor.setIsDefaultDataEnabled(true)
+ interactor.isDefaultConnectionFailed.value = false
+
+ var latest: SignalIconModel? = null
+ val job = underTest.icon.onEach { latest = it }.launchIn(this)
+
+ assertThat(latest!!.showExclamationMark).isFalse()
job.cancel()
}
@@ -572,16 +628,14 @@ class MobileIconViewModelTest : SysuiTestCase() {
companion object {
private const val SUB_1_ID = 1
+ private const val NUM_LEVELS = 4
/** Convenience constructor for these tests */
- fun defaultSignal(
- level: Int = 1,
- connected: Boolean = true,
- ): SignalIconModel {
- return SignalIconModel(level, numberOfLevels = 4, showExclamationMark = !connected)
+ fun defaultSignal(level: Int = 1): SignalIconModel {
+ return SignalIconModel(level, NUM_LEVELS, showExclamationMark = false)
}
fun emptySignal(): SignalIconModel =
- SignalIconModel(level = 0, numberOfLevels = 4, showExclamationMark = true)
+ SignalIconModel(level = 0, numberOfLevels = NUM_LEVELS, showExclamationMark = true)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
index ddb7f4d88d30..f8e1aa94c387 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.TelephonyIcons
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
@@ -24,6 +25,7 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirp
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel
import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -32,9 +34,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -69,14 +70,8 @@ class MobileIconsViewModelTest : SysuiTestCase() {
FakeConnectivityRepository(),
)
- val subscriptionIdsFlow =
- interactor.filteredSubscriptions
- .map { subs -> subs.map { it.subscriptionId } }
- .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
-
underTest =
MobileIconsViewModel(
- subscriptionIdsFlow,
logger,
verboseLogger,
interactor,
@@ -90,6 +85,32 @@ class MobileIconsViewModelTest : SysuiTestCase() {
}
@Test
+ fun subscriptionIdsFlow_matchesInteractor() =
+ testScope.runTest {
+ var latest: List<Int>? = null
+ val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value =
+ listOf(
+ SubscriptionModel(subscriptionId = 1, isOpportunistic = false),
+ )
+ assertThat(latest).isEqualTo(listOf(1))
+
+ interactor.filteredSubscriptions.value =
+ listOf(
+ SubscriptionModel(subscriptionId = 2, isOpportunistic = false),
+ SubscriptionModel(subscriptionId = 5, isOpportunistic = true),
+ SubscriptionModel(subscriptionId = 7, isOpportunistic = true),
+ )
+ assertThat(latest).isEqualTo(listOf(2, 5, 7))
+
+ interactor.filteredSubscriptions.value = emptyList()
+ assertThat(latest).isEmpty()
+
+ job.cancel()
+ }
+
+ @Test
fun `caching - mobile icon view model is reused for same sub id`() =
testScope.runTest {
val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
@@ -116,8 +137,179 @@ class MobileIconsViewModelTest : SysuiTestCase() {
assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
}
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = emptyList()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1)
+ // The unknown icon group doesn't show a RAT
+ interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1)
+ // The 3G icon group will show a RAT
+ interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1)
+ val sub1Interactor = interactor.getInteractorForSubId(1)!!
+
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ assertThat(latest).isTrue()
+
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ assertThat(latest).isFalse()
+
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.LTE)
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+ assertThat(latest).isTrue()
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+
+ assertThat(latest).isTrue()
+
+ // WHEN the sub list gets new subscriptions where the last subscription is not showing
+ // the network type icon
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3)
+ interactor.getInteractorForSubId(3)!!.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+
+ // THEN the flow updates
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() =
+ testScope.runTest {
+ var latest: Boolean? = null
+ val job =
+ underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this)
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ // Immediately switch the order so that we've created both interactors
+ interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
+ val sub1Interactor = interactor.getInteractorForSubId(1)!!
+ val sub2Interactor = interactor.getInteractorForSubId(2)!!
+
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+ sub1Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN)
+ sub2Interactor.networkTypeIconGroup.value =
+ NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G)
+ assertThat(latest).isTrue()
+
+ // WHEN sub1 becomes last and sub1 has no network type icon
+ interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1)
+
+ // THEN the flow updates
+ assertThat(latest).isFalse()
+
+ // WHEN sub2 becomes last and sub2 has a network type icon
+ interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+
+ // THEN the flow updates
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
companion object {
private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false)
private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false)
+ private val SUB_3 = SubscriptionModel(subscriptionId = 3, isOpportunistic = false)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 87d4f5c618a7..661002d275b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo
import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.DEFAULT_HIDDEN_ICONS_RESOURCE
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.HIDDEN_ICONS_TUNABLE_KEY
+import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -491,6 +492,111 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun defaultConnections_nullUnderlyingInfo_noError() {
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(null)
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ // No assert, just verify no error
+ }
+
+ @Test
+ fun defaultConnections_underlyingInfoHasNullCapabilities_noError() {
+ val underlyingNetworkWithNull = mock<Network>()
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetworkWithNull))
+ .thenReturn(null)
+
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetworkWithNull))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+ // No assert, just verify no error
+ }
+
+ // This test verifies our internal API for completeness, but we don't expect this case to ever
+ // happen in practice.
+ @Test
+ fun defaultConnections_cellular_underlyingCarrierMergedViaWifi_allDefault() =
+ testScope.runTest {
+ var latest: DefaultConnectionModel? = null
+ val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network with underlying network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ assertThat(latest!!.mobile.isDefault).isTrue()
+ assertThat(latest!!.carrierMerged.isDefault).isTrue()
+ assertThat(latest!!.wifi.isDefault).isTrue()
+
+ job.cancel()
+ }
+
+ /** Test for b/225902574. */
+ @Test
+ fun defaultConnections_cellular_underlyingCarrierMergedViaMobileWithVcnTransport_allDefault() =
+ testScope.runTest {
+ var latest: DefaultConnectionModel? = null
+ val job = underTest.defaultConnections.onEach { latest = it }.launchIn(this)
+
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network with underlying network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ assertThat(latest!!.mobile.isDefault).isTrue()
+ assertThat(latest!!.carrierMerged.isDefault).isTrue()
+ assertThat(latest!!.wifi.isDefault).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun defaultConnections_multipleTransports_multipleDefault() =
testScope.runTest {
var latest: DefaultConnectionModel? = null
@@ -548,6 +654,279 @@ class ConnectivityRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun getMainOrUnderlyingWifiInfo_wifi_hasInfo() {
+ val wifiInfo = mock<WifiInfo>()
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(wifiInfo)
+ }
+
+ val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isEqualTo(wifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_vcnWithWifi_hasInfo() {
+ val wifiInfo = mock<WifiInfo>()
+ val vcnInfo = VcnTransportInfo(wifiInfo)
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(vcnInfo)
+ }
+
+ val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isEqualTo(wifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_notCellularOrWifiTransport_noInfo() {
+ val capabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(mock<WifiInfo>())
+ }
+
+ val result = capabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_cellular_underlyingWifi_hasInfo() {
+ val underlyingNetwork = mock<Network>()
+ val underlyingWifiInfo = mock<WifiInfo>()
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying wifi network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we fetch the underlying wifi info
+ assertThat(result).isEqualTo(underlyingWifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_notCellular_underlyingWifi_noInfo() {
+ val underlyingNetwork = mock<Network>()
+ val underlyingWifiInfo = mock<WifiInfo>()
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying wifi network but is *not* CELLULAR
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we DON'T fetch the underlying wifi info
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_cellular_underlyingVcnWithWifi_hasInfo() {
+ val wifiInfo = mock<WifiInfo>()
+ val underlyingNetwork = mock<Network>()
+ val underlyingVcnInfo = VcnTransportInfo(wifiInfo)
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying VCN network with wifi
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we fetch the wifi info
+ assertThat(result).isEqualTo(wifiInfo)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_notCellular_underlyingVcnWithWifi_noInfo() {
+ val underlyingNetwork = mock<Network>()
+ val underlyingVcnInfo = VcnTransportInfo(mock<WifiInfo>())
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingVcnInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying wifi network but it is *not* CELLULAR
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_ETHERNET)).thenReturn(true)
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN we DON'T fetch the underlying wifi info
+ assertThat(result).isNull()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_cellular_underlyingCellularWithCarrierMerged_hasInfo() {
+ // Underlying carrier merged network
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network with underlying network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ assertThat(result).isEqualTo(carrierMergedInfo)
+ assertThat(result!!.isCarrierMerged).isTrue()
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_multipleUnderlying_usesFirstNonNull() {
+ // First underlying: Not wifi
+ val underlyingNotWifiNetwork = mock<Network>()
+ val underlyingNotWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(false)
+ whenever(it.transportInfo).thenReturn(null)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNotWifiNetwork))
+ .thenReturn(underlyingNotWifiCapabilities)
+
+ // Second underlying: wifi
+ val underlyingWifiNetwork1 = mock<Network>()
+ val underlyingWifiInfo1 = mock<WifiInfo>()
+ val underlyingWifiCapabilities1 =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo1)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork1))
+ .thenReturn(underlyingWifiCapabilities1)
+
+ // Third underlying: also wifi
+ val underlyingWifiNetwork2 = mock<Network>()
+ val underlyingWifiInfo2 = mock<WifiInfo>()
+ val underlyingWifiCapabilities2 =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(underlyingWifiInfo2)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingWifiNetwork2))
+ .thenReturn(underlyingWifiCapabilities2)
+
+ // WHEN the main capabilities has multiple underlying networks
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(
+ listOf(
+ underlyingNotWifiNetwork,
+ underlyingWifiNetwork1,
+ underlyingWifiNetwork2,
+ )
+ )
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN the first wifi one is used
+ assertThat(result).isEqualTo(underlyingWifiInfo1)
+ }
+
+ @Test
+ fun getMainOrUnderlyingWifiInfo_nestedUnderlying_doesNotLookAtNested() {
+ // WHEN there are two layers of underlying networks...
+
+ // Nested network
+ val nestedUnderlyingNetwork = mock<Network>()
+ val nestedWifiInfo = mock<WifiInfo>()
+ val nestedCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(nestedWifiInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(nestedUnderlyingNetwork))
+ .thenReturn(nestedCapabilities)
+
+ // Underlying network containing the nested network
+ val underlyingNetwork = mock<Network>()
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(nestedUnderlyingNetwork))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // Main network containing the underlying network, which contains the nested network
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ val result = mainCapabilities.getMainOrUnderlyingWifiInfo(connectivityManager)
+
+ // THEN only the first layer is checked, and the first layer has no wifi info
+ assertThat(result).isNull()
+ }
+
private fun createAndSetRepo() {
underTest =
ConnectivityRepositoryImpl(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index f69e9a39909b..d30e0246c2dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -29,6 +29,7 @@ import android.net.vcn.VcnTransportInfo
import android.net.wifi.WifiInfo
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
+import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -49,16 +50,14 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
-import org.junit.After
+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.mockito.ArgumentMatchers.anyInt
@@ -80,9 +79,10 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
private lateinit var executor: Executor
- private lateinit var scope: CoroutineScope
private lateinit var connectivityRepository: ConnectivityRepository
+ private val testScope = TestScope(UnconfinedTestDispatcher())
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -96,7 +96,6 @@ class WifiRepositoryImplTest : SysuiTestCase() {
)
.thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
- scope = CoroutineScope(IMMEDIATE)
connectivityRepository =
ConnectivityRepositoryImpl(
@@ -105,21 +104,16 @@ class WifiRepositoryImplTest : SysuiTestCase() {
context,
mock(),
mock(),
- scope,
+ testScope.backgroundScope,
mock(),
)
underTest = createRepo()
}
- @After
- fun tearDown() {
- scope.cancel()
- }
-
@Test
fun isWifiEnabled_initiallyGetsWifiManagerValue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
whenever(wifiManager.isWifiEnabled).thenReturn(true)
underTest = createRepo()
@@ -129,7 +123,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
// We need to call launch on the flows so that they start updating
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
@@ -152,7 +146,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiEnabled_networkLost_valueUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
// We need to call launch on the flows so that they start updating
val networkJob = underTest.wifiNetwork.launchIn(this)
val enabledJob = underTest.isWifiEnabled.launchIn(this)
@@ -173,7 +167,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiEnabled_intentsReceived_valueUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val intentFlow = MutableSharedFlow<Unit>()
whenever(
broadcastDispatcher.broadcastFlow(
@@ -203,7 +197,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val intentFlow = MutableSharedFlow<Unit>()
whenever(
broadcastDispatcher.broadcastFlow(
@@ -242,7 +236,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_initiallyGetsDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
assertThat(underTest.isWifiDefault.value).isFalse()
@@ -252,7 +246,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_wifiNetwork_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val wifiInfo = mock<WifiInfo>().apply { whenever(this.ssid).thenReturn(SSID) }
@@ -268,7 +262,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
/** Regression test for b/266628069. */
@Test
fun isWifiDefault_transportInfoIsNotWifi_andNoWifiTransport_false() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val transportInfo =
@@ -294,7 +288,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
/** Regression test for b/266628069. */
@Test
fun isWifiDefault_transportInfoIsNotWifi_butHasWifiTransport_true() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val transportInfo =
@@ -319,7 +313,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_carrierMergedViaCellular_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val carrierMergedInfo =
@@ -341,7 +335,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_carrierMergedViaCellular_withVcnTransport_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -360,7 +354,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_carrierMergedViaWifi_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val carrierMergedInfo =
@@ -382,7 +376,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_carrierMergedViaWifi_withVcnTransport_isTrue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -400,8 +394,8 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun wifiNetwork_cellularAndWifiTransports_usesCellular_isTrue() =
- runBlocking(IMMEDIATE) {
+ fun isWifiDefault_cellularAndWifiTransports_usesCellular_isTrue() =
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -420,7 +414,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun isWifiDefault_cellularNotVcnNetwork_isFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
val capabilities =
@@ -437,8 +431,77 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isWifiDefault_isCarrierMergedViaUnderlyingWifi_isTrue() =
+ testScope.runTest {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply {
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ }
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged, so wifi is default
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiDefault_isCarrierMergedViaUnderlyingCellular_isTrue() =
+ testScope.runTest {
+ val job = underTest.isWifiDefault.launchIn(this)
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply { whenever(this.isCarrierMerged).thenReturn(true) }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged, so wifi is default
+ assertThat(underTest.isWifiDefault.value).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun isWifiDefault_wifiNetworkLost_isFalse() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
val job = underTest.isWifiDefault.launchIn(this)
// First, add a network
@@ -457,7 +520,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_initiallyGetsDefault() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -468,7 +531,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_primaryWifiNetworkAdded_flowHasNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -492,7 +555,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -511,8 +574,83 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun wifiNetwork_isCarrierMergedViaUnderlyingWifi_flowHasCarrierMerged() =
+ testScope.runTest {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val underlyingNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+ val underlyingWifiCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(carrierMergedInfo)
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingNetwork))
+ .thenReturn(underlyingWifiCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via WIFI
+ // transport and WifiInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks).thenReturn(listOf(underlyingNetwork))
+ }
+
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiNetwork_isCarrierMergedViaUnderlyingCellular_flowHasCarrierMerged() =
+ testScope.runTest {
+ var latest: WifiNetworkModel? = null
+ val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
+
+ val underlyingCarrierMergedNetwork = mock<Network>()
+ val carrierMergedInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.isPrimary).thenReturn(true)
+ }
+ val underlyingCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(VcnTransportInfo(carrierMergedInfo))
+ }
+ whenever(connectivityManager.getNetworkCapabilities(underlyingCarrierMergedNetwork))
+ .thenReturn(underlyingCapabilities)
+
+ // WHEN the main capabilities have an underlying carrier merged network via CELLULAR
+ // transport and VcnTransportInfo
+ val mainCapabilities =
+ mock<NetworkCapabilities>().also {
+ whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(true)
+ whenever(it.transportInfo).thenReturn(null)
+ whenever(it.underlyingNetworks)
+ .thenReturn(listOf(underlyingCarrierMergedNetwork))
+ }
+
+ getNetworkCallback().onCapabilitiesChanged(NETWORK, mainCapabilities)
+
+ // THEN the wifi network is carrier merged
+ assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -536,7 +674,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_isCarrierMerged_getsCorrectValues() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -571,7 +709,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_notValidated_networkNotValidated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -588,7 +726,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_validated_networkValidated() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -605,7 +743,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_nonPrimaryWifiNetworkAdded_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -626,7 +764,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
/** Regression test for b/266628069. */
@Test
fun wifiNetwork_transportInfoIsNotWifi_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -645,7 +783,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_cellularVcnNetworkAdded_flowHasNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -667,7 +805,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_nonPrimaryCellularVcnNetworkAdded_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -691,7 +829,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_cellularNotVcnNetworkAdded_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -710,7 +848,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_cellularAndWifiTransports_usesCellular() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -733,7 +871,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_newPrimaryWifiNetwork_flowHasNewNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -766,7 +904,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_newNonPrimaryWifiNetwork_flowHasOldNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -799,7 +937,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_newNetworkCapabilities_flowHasNewData() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -842,7 +980,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_noCurrentNetwork_networkLost_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -857,7 +995,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_currentNetworkLost_flowHasNoNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -876,7 +1014,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_unknownNetworkLost_flowHasPreviousNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -899,7 +1037,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiNetwork_notCurrentNetworkLost_flowHasCurrentNetwork() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: WifiNetworkModel? = null
val job = underTest.wifiNetwork.onEach { latest = it }.launchIn(this)
@@ -925,7 +1063,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
/** Regression test for b/244173280. */
@Test
fun wifiNetwork_multipleSubscribers_newSubscribersGetCurrentValue() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest1: WifiNetworkModel? = null
val job1 = underTest.wifiNetwork.onEach { latest1 = it }.launchIn(this)
@@ -952,8 +1090,151 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.ssid).thenReturn(SSID)
+ // A non-primary network is inactive
+ whenever(this.isPrimary).thenReturn(false)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_carrierMergedNetwork_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.isCarrierMerged).thenReturn(true)
+ whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(
+ NETWORK,
+ createWifiNetworkCapabilities(wifiInfo),
+ )
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn(null)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn(UNKNOWN_SSID)
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn("FakeSsid")
+ }
+
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiConnectedWithValidSsid_activeToInactive_trueToFalse() =
+ testScope.runTest {
+ val job = underTest.wifiNetwork.launchIn(this)
+
+ // Start with active
+ val wifiInfo =
+ mock<WifiInfo>().apply {
+ whenever(this.isPrimary).thenReturn(true)
+ whenever(this.ssid).thenReturn("FakeSsid")
+ }
+ getNetworkCallback()
+ .onCapabilitiesChanged(NETWORK, createWifiNetworkCapabilities(wifiInfo))
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isTrue()
+
+ // WHEN the network is lost
+ getNetworkCallback().onLost(NETWORK)
+
+ // THEN the isWifiConnected updates
+ assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -967,7 +1248,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -981,7 +1262,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -995,7 +1276,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
- runBlocking(IMMEDIATE) {
+ testScope.runTest {
var latest: DataActivityModel? = null
val job = underTest.wifiActivity.onEach { latest = it }.launchIn(this)
@@ -1015,7 +1296,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
logger,
tableLogger,
executor,
- scope,
+ testScope.backgroundScope,
wifiManager,
)
}
@@ -1060,5 +1341,3 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
}
}
-
-private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index ab4e93ceee84..4e0c309512e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.wifi.shared.model
+import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -50,6 +51,42 @@ class WifiNetworkModelTest : SysuiTestCase() {
WifiNetworkModel.CarrierMerged(NETWORK_ID, INVALID_SUBSCRIPTION_ID, 1)
}
+ @Test
+ fun active_hasValidSsid_nullSsid_false() {
+ val network =
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ level = MAX_VALID_LEVEL,
+ ssid = null,
+ )
+
+ assertThat(network.hasValidSsid()).isFalse()
+ }
+
+ @Test
+ fun active_hasValidSsid_unknownSsid_false() {
+ val network =
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ level = MAX_VALID_LEVEL,
+ ssid = UNKNOWN_SSID,
+ )
+
+ assertThat(network.hasValidSsid()).isFalse()
+ }
+
+ @Test
+ fun active_hasValidSsid_validSsid_true() {
+ val network =
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ level = MAX_VALID_LEVEL,
+ ssid = "FakeSsid",
+ )
+
+ assertThat(network.hasValidSsid()).isTrue()
+ }
+
// Non-exhaustive logDiffs test -- just want to make sure the logging logic isn't totally broken
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 5c19108cc17e..0d51af2754f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -50,6 +50,7 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,12 +99,12 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() {
val viewModelCommon =
WifiViewModel(
airplaneModeViewModel,
+ shouldShowSignalSpacerProvider = { MutableStateFlow(false) },
connectivityConstants,
context,
tableLogBuffer,
interactor,
scope,
- statusBarPipelineFlags,
wifiConstants,
)
viewModel =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index ffe990bf1cf6..e6724d86ec55 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -27,7 +27,6 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
-import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
@@ -46,6 +45,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
@@ -66,7 +66,6 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
private lateinit var underTest: WifiViewModel
- @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var tableLogBuffer: TableLogBuffer
@Mock private lateinit var connectivityConstants: ConnectivityConstants
@Mock private lateinit var wifiConstants: WifiConstants
@@ -121,12 +120,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase
underTest =
WifiViewModel(
airplaneModeViewModel,
+ shouldShowSignalSpacerProvider = { MutableStateFlow(false) },
connectivityConstants,
context,
tableLogBuffer,
interactor,
scope,
- statusBarPipelineFlags,
wifiConstants,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 802e360797a4..0e303b244094 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -39,8 +39,8 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -53,7 +53,6 @@ import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiViewModelTest : SysuiTestCase() {
@@ -68,6 +67,7 @@ class WifiViewModelTest : SysuiTestCase() {
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
private lateinit var airplaneModeViewModel: AirplaneModeViewModel
+ private val shouldShowSignalSpacerProviderFlow = MutableStateFlow(false)
private lateinit var scope: CoroutineScope
@Before
@@ -473,6 +473,34 @@ class WifiViewModelTest : SysuiTestCase() {
job.cancel()
}
+ @Test
+ fun signalSpacer_firstSubNotShowingNetworkTypeIcon_outputsFalse() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+
+ shouldShowSignalSpacerProviderFlow.value = false
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun signalSpacer_firstSubIsShowingNetworkTypeIcon_outputsTrue() =
+ runBlocking(IMMEDIATE) {
+ var latest: Boolean? = null
+ val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this)
+
+ shouldShowSignalSpacerProviderFlow.value = true
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
private fun createAndSetViewModel() {
// [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
// creations rely on certain config values that we mock out in individual tests. This method
@@ -480,12 +508,12 @@ class WifiViewModelTest : SysuiTestCase() {
underTest =
WifiViewModel(
airplaneModeViewModel,
+ { shouldShowSignalSpacerProviderFlow },
connectivityConstants,
context,
tableLogBuffer,
interactor,
scope,
- statusBarPipelineFlags,
wifiConstants,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index 1eee08c22187..91c88cebff79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -21,6 +21,7 @@ import static android.os.BatteryManager.EXTRA_PRESENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker;
+import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
@@ -167,8 +168,10 @@ public class BatteryControllerTest extends SysuiTestCase {
mBatteryController.setPowerSaveMode(false, mView);
StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class));
- inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true));
- inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true,
+ SAVER_ENABLED_QS));
+ inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true,
+ SAVER_ENABLED_QS));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 01e94baab7c3..391c8ca4d286 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;
import androidx.annotation.NonNull;
-import androidx.core.animation.AnimatorTestRule2;
+import androidx.core.animation.AnimatorTestRule;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
@@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase {
private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
@ClassRule
- public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2();
+ public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
@Before
public void setUp() throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
new file mode 100644
index 000000000000..9cf3e443320d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -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.volume;
+
+import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X;
+import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.media.AudioManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CsdWarningDialogTest extends SysuiTestCase {
+
+ private NotificationManager mNotificationManager;
+ private AudioManager mAudioManager;
+
+ @Before
+ public void setup() {
+ mNotificationManager = mock(NotificationManager.class);
+ getContext().addMockSystemService(NotificationManager.class, mNotificationManager);
+
+ mAudioManager = mock(AudioManager.class);
+ getContext().addMockSystemService(AudioManager.class, mAudioManager);
+ }
+
+ @Test
+ public void create1XCsdDialogAndWait_sendsNotification() {
+ FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
+ // instantiate directly instead of via factory; we don't want executor to be @Background
+ CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
+ mAudioManager, mNotificationManager, executor, null);
+
+ dialog.show();
+ executor.advanceClockToLast();
+ executor.runAllReady();
+ dialog.dismiss();
+
+ verify(mNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+ }
+
+ @Test
+ public void create5XCsdDiSalogAndWait_willNotSendNotification() {
+ FakeExecutor executor = new FakeExecutor(new FakeSystemClock());
+ CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+ mAudioManager, mNotificationManager, executor, null);
+
+ dialog.show();
+ executor.advanceClockToLast();
+ executor.runAllReady();
+ dialog.dismiss();
+
+ verify(mNotificationManager, never()).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d419095921b8..eb2688894cb0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -102,6 +102,15 @@ public class VolumeDialogImplTest extends SysuiTestCase {
InteractionJankMonitor mInteractionJankMonitor;
@Mock
private DumpManager mDumpManager;
+ @Mock CsdWarningDialog mCsdWarningDialog;
+
+ private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
+ new CsdWarningDialog.Factory() {
+ @Override
+ public CsdWarningDialog create(int warningType, Runnable onCleanup) {
+ return mCsdWarningDialog;
+ }
+ };
@Before
public void setup() throws Exception {
@@ -124,6 +133,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mInteractionJankMonitor,
mDeviceConfigProxy,
mExecutor,
+ mCsdWarningDialogFactory,
mDumpManager
);
mDialog.init(0, null);
@@ -352,6 +362,14 @@ public class VolumeDialogImplTest extends SysuiTestCase {
mDialog.getDialogView().animate().cancel();
}
+ @Test
+ public void showCsdWarning_dialogShown() {
+ mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X,
+ CsdWarningDialog.NO_ACTION_TIMEOUT_MS);
+
+ verify(mCsdWarningDialog).show();
+ }
+
/*
@Test
public void testContentDescriptions() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
new file mode 100644
index 000000000000..738f09ddce3d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.data.repository
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+
+class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
+
+ override val isAuthenticated = MutableStateFlow(false)
+ override val canRunFaceAuth = MutableStateFlow(false)
+ private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null)
+ override val authenticationStatus: Flow<AuthenticationStatus> =
+ _authenticationStatus.filterNotNull()
+ fun setAuthenticationStatus(status: AuthenticationStatus) {
+ _authenticationStatus.value = status
+ }
+ private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null)
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = _detectionStatus.filterNotNull()
+ fun setDetectionStatus(status: DetectionStatus) {
+ _detectionStatus.value = status
+ }
+ override val isLockedOut = MutableStateFlow(false)
+ private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
+ val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
+ _runningAuthRequest.asStateFlow()
+
+ private val _isAuthRunning = MutableStateFlow(false)
+ override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
+ override val isBypassEnabled = MutableStateFlow(false)
+
+ override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
+ _runningAuthRequest.value = uiEvent to fallbackToDetection
+ _isAuthRunning.value = true
+ }
+
+ override fun cancel() {
+ _isAuthRunning.value = false
+ _runningAuthRequest.value = null
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
new file mode 100644
index 000000000000..9383a0a68844
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeQSFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * 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
+
+import android.content.Context
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.qs.QSTileView
+
+class FakeQSFactory(private val tileCreator: (String) -> QSTile?) : QSFactory {
+ override fun createTile(tileSpec: String): QSTile? {
+ return tileCreator(tileSpec)
+ }
+
+ override fun createTileView(
+ context: Context?,
+ tile: QSTile?,
+ collapsedView: Boolean
+ ): QSTileView {
+ throw NotImplementedError("Not implemented")
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt
new file mode 100644
index 000000000000..777130409aad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeCustomTileAddedRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.pipeline.data.repository
+
+import android.content.ComponentName
+
+class FakeCustomTileAddedRepository : CustomTileAddedRepository {
+
+ private val tileAddedRegistry = mutableSetOf<Pair<Int, ComponentName>>()
+
+ override fun isTileAdded(componentName: ComponentName, userId: Int): Boolean {
+ return (userId to componentName) in tileAddedRegistry
+ }
+
+ override fun setTileAdded(componentName: ComponentName, userId: Int, added: Boolean) {
+ if (added) {
+ tileAddedRegistry.add(userId to componentName)
+ } else {
+ tileAddedRegistry.remove(userId to componentName)
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
new file mode 100644
index 000000000000..2865710c2eae
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.pipeline.data.repository
+
+import android.util.Log
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeTileSpecRepository : TileSpecRepository {
+
+ private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
+
+ override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
+ return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") }
+ }
+
+ override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
+ if (tile == TileSpec.Invalid) return
+ with(getFlow(userId)) {
+ value =
+ value.toMutableList().apply {
+ if (position == POSITION_AT_END) {
+ add(tile)
+ } else {
+ add(position, tile)
+ }
+ }
+ }
+ }
+
+ override suspend fun removeTiles(userId: Int, tiles: Collection<TileSpec>) {
+ with(getFlow(userId)) {
+ value =
+ value.toMutableList().apply { removeAll(tiles.filter { it != TileSpec.Invalid }) }
+ }
+ }
+
+ override suspend fun setTiles(userId: Int, tiles: List<TileSpec>) {
+ getFlow(userId).value = tiles.filter { it != TileSpec.Invalid }
+ }
+
+ private fun getFlow(userId: Int): MutableStateFlow<List<TileSpec>> =
+ tilesPerUser.getOrPut(userId) { MutableStateFlow(emptyList()) }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
index 380c1fcbf732..156d2fc2cb3a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -25,14 +25,18 @@ import java.util.concurrent.Executor
* It could be used when no activity context is available
* TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873
*/
-class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
+class ScreenSizeFoldProvider(context: Context) : FoldProvider {
+ private var isFolded: Boolean = false
private var callbacks: MutableList<FoldCallback> = arrayListOf()
- private var lastWidth: Int = 0
+
+ init {
+ onConfigurationChange(context.resources.configuration)
+ }
override fun registerCallback(callback: FoldCallback, executor: Executor) {
callbacks += callback
- onConfigurationChange(context.resources.configuration)
+ callback.onFoldUpdated(isFolded)
}
override fun unregisterCallback(callback: FoldCallback) {
@@ -40,16 +44,14 @@ class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
}
fun onConfigurationChange(newConfig: Configuration) {
- if (lastWidth == newConfig.smallestScreenWidthDp) {
+ val newIsFolded =
+ newConfig.smallestScreenWidthDp < INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP
+ if (newIsFolded == isFolded) {
return
}
- if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) {
- callbacks.forEach { it.onFoldUpdated(false) }
- } else {
- callbacks.forEach { it.onFoldUpdated(true) }
- }
- lastWidth = newConfig.smallestScreenWidthDp
+ isFolded = newIsFolded
+ callbacks.forEach { it.onFoldUpdated(isFolded) }
}
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
index 0b019d1285e3..30418883eaf8 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
@@ -35,7 +35,7 @@ class UnfoldRemoteFilter(
private var inProgress = false
- private var processedProgress: Float = 0.0f
+ private var processedProgress: Float = 1.0f
set(newProgress) {
if (inProgress) {
logCounter({ "$TAG#filtered_progress" }, newProgress)
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 a633a5e41882..46001a73e518 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
@@ -256,6 +256,12 @@ constructor(
}
}
+ override fun markScreenAsTurnedOn() {
+ if (!isFolded) {
+ isUnfoldHandled = true
+ }
+ }
+
override fun onScreenTurningOn() {
isScreenOn = true
updateHingeAngleProviderState()
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index f09b53dc8436..2ee2940fa2a6 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -35,5 +35,12 @@ interface ScreenStatusProvider : CallbackController<ScreenListener> {
* Called when the screen is starting to be turned on.
*/
fun onScreenTurningOn()
+
+ /**
+ * Called when the screen is already turned on but it happened before the creation
+ * of the unfold progress provider, so we won't play the actual animation but we treat
+ * the current state of the screen as 'turned on'
+ */
+ fun markScreenAsTurnedOn()
}
}
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index cbca3f03fb0f..a41d0e57cd21 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -32,6 +32,7 @@ LOCAL_REQUIRED_MODULES := \
NavigationBarModeGesturalOverlayWideBack \
NavigationBarModeGesturalOverlayExtraWideBack \
TransparentNavigationBarOverlay \
+ NotesRoleEnabledOverlay \
preinstalled-packages-platform-overlays.xml
include $(BUILD_PHONY_PACKAGE)
diff --git a/packages/overlays/NotesRoleEnabledOverlay/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
new file mode 100644
index 000000000000..68ebd9652399
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+runtime_resource_overlay {
+ name: "NotesRoleEnabledOverlay",
+ theme: "NotesRoleEnabled",
+ product_specific: true,
+}
diff --git a/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
new file mode 100644
index 000000000000..c01178d1727d
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml
@@ -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.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.role.notes.enabled"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android"
+ android:priority="1"/>
+
+ <application android:label="@string/notes_role_enabled_overlay_title" android:hasCode="false"/>
+</manifest> \ No newline at end of file
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
new file mode 100644
index 000000000000..f27f5f42ae6b
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources>
+ <!-- Whether the default notes role should be enabled. In builds without
+ device-specific overlays, this can be controlled in developer options. -->
+ <bool name="config_enableDefaultNotes">true</bool>
+
+ <!-- Whether the default notes role for work profile should be enabled.
+ In builds without device-specific overlays, this can be controlled in
+ developer options. -->
+ <bool name="config_enableDefaultNotesForWorkProfile">true</bool>
+</resources>
diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
new file mode 100644
index 000000000000..3edbb571c4d1
--- /dev/null
+++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of overlay [CHAR LIMIT=64] -->
+ <string name="notes_role_enabled_overlay_title" translatable="false">Notes Role enabled</string>
+</resources> \ No newline at end of file
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 47027342974d..21d09792f1c7 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -402,5 +402,7 @@ message SystemMessage {
// Package: android
NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006;
+ // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
+ NOTE_CSD_LOWER_AUDIO = 1007;
}
}
diff --git a/services/Android.bp b/services/Android.bp
index 6e6c55325e3d..b0a0e5e44a8c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -112,6 +112,7 @@ filegroup {
":services.searchui-sources",
":services.selectiontoolbar-sources",
":services.smartspace-sources",
+ ":services.soundtrigger-sources",
":services.systemcaptions-sources",
":services.translation-sources",
":services.texttospeech-sources",
@@ -169,6 +170,7 @@ java_library {
"services.searchui",
"services.selectiontoolbar",
"services.smartspace",
+ "services.soundtrigger",
"services.systemcaptions",
"services.translation",
"services.texttospeech",
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 51325e72204d..0bdb0c80d219 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -112,6 +112,7 @@ import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.IWindow;
import android.view.InputDevice;
@@ -160,6 +161,7 @@ import com.android.server.accessibility.magnification.WindowMagnificationManager
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import com.android.settingslib.RestrictedLockUtils;
@@ -301,7 +303,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables =
new ArrayList<>();
- private int mCurrentUserId = UserHandle.USER_SYSTEM;
+ @GuardedBy("mLock")
+ private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation:
+ // when the UiAutomation is set in a visible background user, mCurrentUserId points to that user
+ // and mRealCurrentUserId points to the "real" current user; otherwise, mRealCurrentUserId
+ // is set as UserHandle.USER_CURRENT.
+ @GuardedBy("mLock")
+ private @UserIdInt int mRealCurrentUserId = UserHandle.USER_CURRENT;
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+ // purposes - in the long term, the whole service should be refactored so it handles "visible"
+ // users, not current user. Notice that because this is temporary, it's not trying to optimize
+ // performance / utilization (for example, it's not using an IntArray)
+ @GuardedBy("mLock")
+ @Nullable // only set when device supports visible background users
+ private final SparseBooleanArray mVisibleBgUserIds;
//TODO: Remove this hack
private boolean mInitialized;
@@ -316,6 +334,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
private final FlashNotificationsController mFlashNotificationsController;
+ private final UserManagerInternal mUmi;
private AccessibilityUserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
@@ -445,6 +464,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mHasInputFilter = true;
}
mFlashNotificationsController = new FlashNotificationsController(mContext);
+ mUmi = LocalServices.getService(UserManagerInternal.class);
+ // TODO(b/255426725): not used on tests
+ mVisibleBgUserIds = null;
+
init();
}
@@ -477,6 +500,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler,
mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
+ mUmi = LocalServices.getService(UserManagerInternal.class);
+
+ if (UserManager.isVisibleBackgroundUsersEnabled()) {
+ mVisibleBgUserIds = new SparseBooleanArray();
+ mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v));
+ } else {
+ mVisibleBgUserIds = null;
+ }
+
init();
}
@@ -493,6 +525,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return mCurrentUserId;
}
+ @GuardedBy("mLock")
+ @Override
+ public SparseBooleanArray getVisibleUserIdsLocked() {
+ return mVisibleBgUserIds;
+ }
+
@Override
public boolean isAccessibilityButtonShown() {
return mIsAccessibilityButtonShown;
@@ -1362,6 +1400,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void registerUiTestAutomationService(IBinder owner,
IAccessibilityServiceClient serviceClient,
AccessibilityServiceInfo accessibilityServiceInfo,
+ int userId,
int flags) {
if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService",
@@ -1374,6 +1413,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
synchronized (mLock) {
+ changeCurrentUserForTestAutomationIfNeededLocked(userId);
mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
mSecurityPolicy, this, getTraceManager(), mWindowManagerService,
@@ -1390,7 +1430,47 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
synchronized (mLock) {
mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient);
+ restoreCurrentUserAfterTestAutomationIfNeededLocked();
+ }
+ }
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+ @GuardedBy("mLock")
+ private void changeCurrentUserForTestAutomationIfNeededLocked(@UserIdInt int userId) {
+ if (mVisibleBgUserIds == null) {
+ Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(%d): ignoring "
+ + "because device doesn't support visible background users", userId);
+ return;
+ }
+ if (!mVisibleBgUserIds.get(userId)) {
+ Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible "
+ + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds);
+ return;
+ }
+ if (mCurrentUserId == userId) {
+ Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is "
+ + "already %d", mCurrentUserId);
+ return;
+ }
+ Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes",
+ mCurrentUserId, userId);
+ mRealCurrentUserId = mCurrentUserId;
+ switchUser(userId);
+ }
+
+ // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation
+ @GuardedBy("mLock")
+ private void restoreCurrentUserAfterTestAutomationIfNeededLocked() {
+ if (mVisibleBgUserIds == null) {
+ Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring "
+ + "because device doesn't support visible background users");
+ return;
}
+ Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes",
+ mRealCurrentUserId, mCurrentUserId);
+ int currentUserId = mRealCurrentUserId;
+ mRealCurrentUserId = UserHandle.USER_CURRENT;
+ switchUser(currentUserId);
}
@Override
@@ -2291,8 +2371,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private void updateServicesLocked(AccessibilityUserState userState) {
Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
- boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
- .isUserUnlockingOrUnlocked(userState.mUserId);
+ boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId);
for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
@@ -2593,6 +2672,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
}
+ private void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) {
+ if (DEBUG) {
+ Slogf.d(LOG_TAG, "onUserVisibilityChanged(): %d => %b", userId, visible);
+ }
+ synchronized (mLock) {
+ if (visible) {
+ mVisibleBgUserIds.put(userId, visible);
+ } else {
+ mVisibleBgUserIds.delete(userId);
+ }
+ }
+ }
+
/**
* Called when any property of the user state has changed.
*
@@ -4025,7 +4117,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
pw.println();
pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+ if (mRealCurrentUserId != UserHandle.USER_CURRENT
+ && mCurrentUserId != mRealCurrentUserId) {
+ pw.append(" (set for UiAutomation purposes; \"real\" current user is ")
+ .append(String.valueOf(mRealCurrentUserId)).append(")");
+ }
pw.println();
+ if (mVisibleBgUserIds != null) {
+ pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString());
+ pw.println();
+ }
pw.append("hasWindowMagnificationConnection=").append(
String.valueOf(getWindowMagnificationMgr().isConnected()));
pw.println();
@@ -4052,6 +4153,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
pw.println();
mProxyManager.dump(fd, pw, args);
+ mA11yDisplayListener.dump(fd, pw, args);
}
}
@@ -4437,6 +4539,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
/* do nothing */
}
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Accessibility Display Listener:");
+ pw.println(" SystemUI uid: " + mSystemUiUid);
+ int size = mDisplaysList.size();
+ pw.printf(" %d valid display%s: ", size, (size == 1 ? "" : "s"));
+ for (int i = 0; i < size; i++) {
+ pw.print(mDisplaysList.get(i).getDisplayId());
+ if (i < size - 1) {
+ pw.print(", ");
+ }
+ }
+ pw.println();
+ }
+
private boolean isValidDisplay(@Nullable Display display) {
if (display == null || display.getType() == Display.TYPE_OVERLAY) {
return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index c37ea501bbc9..88656239e59b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -39,6 +39,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArraySet;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import android.view.accessibility.AccessibilityEvent;
import android.view.inputmethod.InputMethodInfo;
@@ -88,6 +89,12 @@ public class AccessibilitySecurityPolicy {
*/
int getCurrentUserIdLocked();
// TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy
+
+ // TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds
+ /**
+ * Returns the {@link android.os.UserManager#getVisibleUsers() visible users}.
+ */
+ @Nullable SparseBooleanArray getVisibleUserIdsLocked();
}
private final Context mContext;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index baed181ebd43..a8a536590004 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -51,6 +51,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
+import com.android.server.utils.Slogf;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
@@ -59,6 +60,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
/**
* This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and
@@ -67,6 +69,7 @@ import java.util.List;
public class AccessibilityWindowManager {
private static final String LOG_TAG = "AccessibilityWindowManager";
private static final boolean DEBUG = false;
+ private static final boolean VERBOSE = false;
private static int sNextWindowId;
@@ -209,6 +212,9 @@ public class AccessibilityWindowManager {
* Constructor for DisplayWindowsObserver.
*/
DisplayWindowsObserver(int displayId) {
+ if (DEBUG) {
+ Slogf.d(LOG_TAG, "Creating DisplayWindowsObserver for displayId %d", displayId);
+ }
mDisplayId = displayId;
}
@@ -430,12 +436,27 @@ public class AccessibilityWindowManager {
synchronized (mLock) {
updateWindowsByWindowAttributesLocked(windows);
if (DEBUG) {
- Slog.i(LOG_TAG, "Display Id = " + mDisplayId);
- Slog.i(LOG_TAG, "Windows changed: " + windows);
+ Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, "
+ + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId,
+ mAccessibilityUserManager.getCurrentUserIdLocked(),
+ mAccessibilityUserManager.getVisibleUserIdsLocked());
+ if (VERBOSE) {
+ Slogf.i(LOG_TAG, "%d windows changed: %s ", windows.size(), windows);
+ } else {
+ List<String> windowsInfo = windows.stream()
+ .map(w -> "{displayId=" + w.displayId + ", title=" + w.title + "}")
+ .collect(Collectors.toList());
+ Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo);
+ }
}
if (shouldUpdateWindowsLocked(forceSend, windows)) {
mTopFocusedDisplayId = topFocusedDisplayId;
mTopFocusedWindowToken = topFocusedWindowToken;
+ if (DEBUG) {
+ Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for "
+ + "display %d and token %s",
+ topFocusedDisplayId, topFocusedWindowToken);
+ }
cacheWindows(windows);
// Lets the policy update the focused and active windows.
updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(),
@@ -443,6 +464,11 @@ public class AccessibilityWindowManager {
// Someone may be waiting for the windows - advertise it.
mLock.notifyAll();
}
+ else if (DEBUG) {
+ Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for "
+ + "display %d and token %s",
+ topFocusedDisplayId, topFocusedWindowToken);
+ }
}
}
@@ -472,6 +498,12 @@ public class AccessibilityWindowManager {
}
final int windowCount = windows.size();
+ if (VERBOSE) {
+ Slogf.v(LOG_TAG,
+ "shouldUpdateWindowsLocked(): mDisplayId=%d, windowCount=%d, "
+ + "mCachedWindowInfos.size()=%d, windows.size()=%d", mDisplayId,
+ windowCount, mCachedWindowInfos.size(), windows.size());
+ }
// We computed the windows and if they changed notify the client.
if (mCachedWindowInfos.size() != windowCount) {
// Different size means something changed.
@@ -1274,7 +1306,7 @@ public class AccessibilityWindowManager {
*/
@Nullable
public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) {
- if (DEBUG) {
+ if (VERBOSE) {
Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
}
RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 8d039fc02026..2a964b8b701f 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2073,15 +2073,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@Override
public void onSaveRequestSuccess(@NonNull String servicePackageName,
@Nullable IntentSender intentSender) {
- // Log onSaveRequest result.
- mSaveEventLogger.maybeSetIsSaved(true);
- final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
- mSaveEventLogger.logAndEndEvent();
-
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
-
+ // Log onSaveRequest result.
+ mSaveEventLogger.maybeSetIsSaved(true);
+ final long saveRequestFinishTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+ mSaveEventLogger.logAndEndEvent();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: "
+ id + " destroyed");
@@ -2108,14 +2107,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
@NonNull String servicePackageName) {
boolean showMessage = !TextUtils.isEmpty(message);
- // Log onSaveRequest result.
- final long saveRequestFinishTimestamp = SystemClock.elapsedRealtime() - mLatencyBaseTime;
- mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
- mSaveEventLogger.logAndEndEvent();
-
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
-
+ // Log onSaveRequest result.
+ final long saveRequestFinishTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mSaveEventLogger.maybeSetLatencySaveFinishMillis(saveRequestFinishTimestamp);
+ mSaveEventLogger.logAndEndEvent();
if (mDestroyed) {
Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: "
+ id + " destroyed");
@@ -2228,8 +2226,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// AutoFillUiCallback
@Override
public void save() {
- mSaveEventLogger.maybeSetSaveButtonClicked(true);
synchronized (mLock) {
+ mSaveEventLogger.maybeSetSaveButtonClicked(true);
if (mDestroyed) {
Slog.w(TAG, "Call to Session#save() rejected - session: "
+ id + " destroyed");
@@ -2247,10 +2245,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
// AutoFillUiCallback
@Override
public void cancelSave() {
- mSaveEventLogger.maybeSetDialogDismissed(true);
synchronized (mLock) {
mSessionFlags.mShowingSaveUi = false;
-
+ mSaveEventLogger.maybeSetDialogDismissed(true);
if (mDestroyed) {
Slog.w(TAG, "Call to Session#cancelSave() rejected - session: "
+ id + " destroyed");
diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
index 1990fe277af9..98aebddddac9 100644
--- a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
+++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java
@@ -77,4 +77,19 @@ public class BackupAndRestoreFeatureFlags {
/* name= */ "full_backup_utils_route_buffer_size_bytes",
/* defaultValue= */ 32 * 1024); // 32 KB
}
+
+ /**
+ * Retrieves the value of the flag
+ * "unified_restore_continue_after_transport_failure_in_kv_restore".
+ * If true, Unified restore task will continue to next package if key-value restore of a
+ * package fails due to Transport-level failure. See b/128499560 for more context.
+ */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ public static boolean getUnifiedRestoreContinueAfterTransportFailureInKvRestore() {
+ return DeviceConfig.getBoolean(
+ NAMESPACE,
+ /* name= */
+ "unified_restore_continue_after_transport_failure_in_kv_restore",
+ /* defaultValue= */ true);
+ }
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 18e28de75782..1656b6f0ab9b 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -57,6 +57,7 @@ import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.backup.BackupAgentTimeoutParameters;
+import com.android.server.backup.BackupAndRestoreFeatureFlags;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.BackupUtils;
import com.android.server.backup.OperationStorage;
@@ -168,11 +169,13 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
private final BackupEligibilityRules mBackupEligibilityRules;
@VisibleForTesting
- PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) {
+ PerformUnifiedRestoreTask(
+ UserBackupManagerService backupManagerService,
+ TransportConnection transportConnection) {
mListener = null;
mAgentTimeoutParameters = null;
mOperationStorage = null;
- mTransportConnection = null;
+ mTransportConnection = transportConnection;
mTransportManager = null;
mEphemeralOpToken = 0;
mUserId = 0;
@@ -731,13 +734,18 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
ParcelFileDescriptor.MODE_TRUNCATE);
if (transport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) {
- // Transport-level failure, so we wind everything up and
- // terminate the restore operation.
+ // Transport-level failure. This failure could be specific to package currently in
+ // restore.
Slog.e(TAG, "Error getting restore data for " + packageName);
EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE);
stage.close();
downloadFile.delete();
- executeNextState(UnifiedRestoreState.FINAL);
+ UnifiedRestoreState nextState =
+ BackupAndRestoreFeatureFlags
+ .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()
+ ? UnifiedRestoreState.RUNNING_QUEUE
+ : UnifiedRestoreState.FINAL;
+ executeNextState(nextState);
return;
}
@@ -1358,6 +1366,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
executeNextState(UnifiedRestoreState.RUNNING_QUEUE);
}
+ @VisibleForTesting
void executeNextState(UnifiedRestoreState nextState) {
if (MORE_DEBUG) {
Slog.i(TAG, " => executing next step on "
@@ -1369,6 +1378,26 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
backupManagerService.getBackupHandler().sendMessage(msg);
}
+ @VisibleForTesting
+ UnifiedRestoreState getCurrentUnifiedRestoreStateForTesting() {
+ return mState;
+ }
+
+ @VisibleForTesting
+ void setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState state) {
+ mState = state;
+ }
+
+ @VisibleForTesting
+ void setStateDirForTesting(File stateDir) {
+ mStateDir = stateDir;
+ }
+
+ @VisibleForTesting
+ void initiateOneRestoreForTesting(PackageInfo app, long appVersionCode) {
+ initiateOneRestore(app, appVersionCode);
+ }
+
// restore observer support
void sendStartRestore(int numPackages) {
if (mObserver != null) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 542cc2f0a0a6..5b320a87d113 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.companion;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -1066,6 +1067,10 @@ public class CompanionDeviceManagerService extends SystemService {
// No role was granted to for this association, there is nothing else we need to here.
return true;
}
+ // Do not need to remove the system role since it was pre-granted by the system.
+ if (deviceProfile.equals(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)) {
+ return true;
+ }
// Check if the applications is associated with another devices with the profile. If so,
// it should remain the role holder.
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 291c05877c17..96446422dd85 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -151,15 +151,14 @@ public class VirtualDeviceManagerService extends SystemService {
}
void onCameraAccessBlocked(int appUid) {
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName();
- mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid,
- getContext().getString(
- com.android.internal.R.string.vdm_camera_access_denied,
- deviceName),
- Toast.LENGTH_LONG, Looper.myLooper());
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
+ virtualDevice.showToastWhereUidIsRunning(appUid,
+ getContext().getString(
+ com.android.internal.R.string.vdm_camera_access_denied,
+ virtualDevice.getDisplayName()),
+ Toast.LENGTH_LONG, Looper.myLooper());
}
}
@@ -265,6 +264,16 @@ public class VirtualDeviceManagerService extends SystemService {
cdm.removeOnAssociationsChangedListener(mCdmAssociationListener);
}
+ private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() {
+ synchronized (mVirtualDeviceManagerLock) {
+ ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size());
+ for (int i = 0; i < mVirtualDevices.size(); i++) {
+ virtualDevices.add(mVirtualDevices.valueAt(i));
+ }
+ return virtualDevices;
+ }
+ }
+
class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub {
private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback =
@@ -314,6 +323,17 @@ public class VirtualDeviceManagerService extends SystemService {
Objects.requireNonNull(activityListener);
Objects.requireNonNull(soundEffectListener);
+ final UserHandle userHandle = getCallingUserHandle();
+ final CameraAccessController cameraAccessController =
+ getCameraAccessController(userHandle);
+ final int deviceId = sNextUniqueIndex.getAndIncrement();
+ final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
+ runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
+ VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
+ associationInfo, VirtualDeviceManagerService.this, token, callingUid,
+ deviceId, cameraAccessController,
+ mPendingTrampolineCallback, activityListener,
+ soundEffectListener, runningAppsChangedCallback, params);
synchronized (mVirtualDeviceManagerLock) {
if (mVirtualDevices.size() == 0) {
final long callindId = Binder.clearCallingIdentity();
@@ -323,21 +343,9 @@ public class VirtualDeviceManagerService extends SystemService {
Binder.restoreCallingIdentity(callindId);
}
}
-
- final UserHandle userHandle = getCallingUserHandle();
- final CameraAccessController cameraAccessController =
- getCameraAccessController(userHandle);
- final int deviceId = sNextUniqueIndex.getAndIncrement();
- final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
- runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
- VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(),
- associationInfo, VirtualDeviceManagerService.this, token, callingUid,
- deviceId, cameraAccessController,
- mPendingTrampolineCallback, activityListener,
- soundEffectListener, runningAppsChangedCallback, params);
mVirtualDevices.put(deviceId, virtualDevice);
- return virtualDevice;
}
+ return virtualDevice;
}
@Override // Binder call
@@ -399,12 +407,11 @@ public class VirtualDeviceManagerService extends SystemService {
if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
return Context.DEVICE_ID_DEFAULT;
}
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
- if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) {
- return virtualDevice.getDeviceId();
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
+ if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) {
+ return virtualDevice.getDeviceId();
}
}
return Context.DEVICE_ID_DEFAULT;
@@ -496,10 +503,9 @@ public class VirtualDeviceManagerService extends SystemService {
return;
}
fout.println("Created virtual devices: ");
- synchronized (mVirtualDeviceManagerLock) {
- for (int i = 0; i < mVirtualDevices.size(); i++) {
- mVirtualDevices.valueAt(i).dump(fd, fout, args);
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ virtualDevicesSnapshot.get(i).dump(fd, fout, args);
}
}
}
@@ -516,33 +522,30 @@ public class VirtualDeviceManagerService extends SystemService {
@Override
public int getDeviceOwnerUid(int deviceId) {
+ VirtualDeviceImpl virtualDevice;
synchronized (mVirtualDeviceManagerLock) {
- VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
- return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID;
+ virtualDevice = mVirtualDevices.get(deviceId);
}
+ return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID;
}
@Override
public @Nullable VirtualSensor getVirtualSensor(int deviceId, int handle) {
+ VirtualDeviceImpl virtualDevice;
synchronized (mVirtualDeviceManagerLock) {
- VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
- if (virtualDevice != null) {
- return virtualDevice.getVirtualSensorByHandle(handle);
- }
+ virtualDevice = mVirtualDevices.get(deviceId);
}
- return null;
+ return virtualDevice != null ? virtualDevice.getVirtualSensorByHandle(handle) : null;
}
@Override
public @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid) {
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
ArraySet<Integer> result = new ArraySet<>();
- synchronized (mVirtualDeviceManagerLock) {
- int size = mVirtualDevices.size();
- for (int i = 0; i < size; i++) {
- VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
- if (device.isAppRunningOnVirtualDevice(uid)) {
- result.add(device.getDeviceId());
- }
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ VirtualDeviceImpl device = virtualDevicesSnapshot.get(i);
+ if (device.isAppRunningOnVirtualDevice(uid)) {
+ result.add(device.getDeviceId());
}
}
return result;
@@ -630,12 +633,10 @@ public class VirtualDeviceManagerService extends SystemService {
@Override
public boolean isAppRunningOnAnyVirtualDevice(int uid) {
- synchronized (mVirtualDeviceManagerLock) {
- int size = mVirtualDevices.size();
- for (int i = 0; i < size; i++) {
- if (mVirtualDevices.valueAt(i).isAppRunningOnVirtualDevice(uid)) {
- return true;
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ if (virtualDevicesSnapshot.get(i).isAppRunningOnVirtualDevice(uid)) {
+ return true;
}
}
return false;
@@ -643,12 +644,10 @@ public class VirtualDeviceManagerService extends SystemService {
@Override
public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) {
- synchronized (mVirtualDeviceManagerLock) {
- int size = mVirtualDevices.size();
- for (int i = 0; i < size; i++) {
- if (mVirtualDevices.valueAt(i).isDisplayOwnedByVirtualDevice(displayId)) {
- return true;
- }
+ ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
+ for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
+ if (virtualDevicesSnapshot.get(i).isDisplayOwnedByVirtualDevice(displayId)) {
+ return true;
}
}
return false;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index c8caab93d76c..cfdf3ac5915b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -174,7 +174,6 @@ java_library_static {
"android.hardware.configstore-V1.1-java",
"android.hardware.ir-V1-java",
"android.hardware.rebootescrow-V1-java",
- "android.hardware.soundtrigger-V2.3-java",
"android.hardware.power.stats-V2-java",
"android.hardware.power-V4-java",
"android.hidl.manager-V1.2-java",
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index c6f63dd73a25..12ee13183221 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -39,12 +39,14 @@ public abstract class BatteryStatsInternal {
public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1;
public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1;
public static final int CPU_WAKEUP_SUBSYSTEM_WIFI = 2;
+ public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3;
/** @hide */
@IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = {
CPU_WAKEUP_SUBSYSTEM_UNKNOWN,
CPU_WAKEUP_SUBSYSTEM_ALARM,
CPU_WAKEUP_SUBSYSTEM_WIFI,
+ CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
})
@Retention(RetentionPolicy.SOURCE)
@interface CpuWakeupSubsystem {
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 3ecf93328219..8fc30e43d1a0 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -80,7 +80,7 @@ import android.util.apk.ApkSignatureVerifier;
import android.util.apk.ApkSigningBlockUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Histogram;
+import com.android.modules.expresslog.Histogram;
import com.android.internal.os.IBinaryTransparencyService;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.pm.ApexManager;
diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java
index 3487613d313c..5156c541011f 100644
--- a/services/core/java/com/android/server/DockObserver.java
+++ b/services/core/java/com/android/server/DockObserver.java
@@ -70,7 +70,7 @@ final class DockObserver extends SystemService {
private boolean mUpdatesStopped;
- private final boolean mKeepDreamingWhenUndocking;
+ private final boolean mKeepDreamingWhenUnplugging;
private final boolean mAllowTheaterModeWakeFromDock;
private final List<ExtconStateConfig> mExtconStateConfigs;
@@ -167,8 +167,8 @@ final class DockObserver extends SystemService {
mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
mAllowTheaterModeWakeFromDock = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromDock);
- mKeepDreamingWhenUndocking = context.getResources().getBoolean(
- com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+ mKeepDreamingWhenUnplugging = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler);
mExtconStateConfigs = loadExtconStateConfigs(context);
@@ -237,7 +237,7 @@ final class DockObserver extends SystemService {
}
private boolean allowWakeFromDock() {
- if (mKeepDreamingWhenUndocking) {
+ if (mKeepDreamingWhenUnplugging) {
return false;
}
return (mAllowTheaterModeWakeFromDock
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 409f0541eed7..123cd3288343 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -27,6 +27,7 @@ per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS
per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS
per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS
per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
+per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS
per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 92889c05d9f4..d256aead97e8 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -426,7 +426,7 @@ public class PackageWatchdog {
}
int impact = registeredObserver.onHealthCheckFailed(
versionedPackage, failureReason, mitigationCount);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
currentObserverToNotify = registeredObserver;
currentObserverImpact = impact;
@@ -466,7 +466,7 @@ public class PackageWatchdog {
if (registeredObserver != null) {
int impact = registeredObserver.onHealthCheckFailed(
failingPackage, failureReason, 1);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
currentObserverToNotify = registeredObserver;
currentObserverImpact = impact;
@@ -494,7 +494,7 @@ public class PackageWatchdog {
PackageHealthObserver registeredObserver = observer.registeredObserver;
if (registeredObserver != null) {
int impact = registeredObserver.onBootLoop(mitigationCount);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
&& impact < currentObserverImpact) {
currentObserverToNotify = registeredObserver;
currentObserverImpact = impact;
@@ -576,19 +576,23 @@ public class PackageWatchdog {
/** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */
@Retention(SOURCE)
- @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE,
- PackageHealthObserverImpact.USER_IMPACT_LOW,
- PackageHealthObserverImpact.USER_IMPACT_MEDIUM,
- PackageHealthObserverImpact.USER_IMPACT_HIGH})
+ @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
public @interface PackageHealthObserverImpact {
/** No action to take. */
- int USER_IMPACT_NONE = 0;
+ int USER_IMPACT_LEVEL_0 = 0;
/* Action has low user impact, user of a device will barely notice. */
- int USER_IMPACT_LOW = 1;
- /* Action has medium user impact, user of a device will likely notice. */
- int USER_IMPACT_MEDIUM = 3;
+ int USER_IMPACT_LEVEL_10 = 10;
+ /* Actions having medium user impact, user of a device will likely notice. */
+ int USER_IMPACT_LEVEL_30 = 30;
+ int USER_IMPACT_LEVEL_50 = 50;
+ int USER_IMPACT_LEVEL_70 = 70;
/* Action has high user impact, a last resort, user of a device will be very frustrated. */
- int USER_IMPACT_HIGH = 5;
+ int USER_IMPACT_LEVEL_100 = 100;
}
/** Register instances of this interface to receive notifications on package failure. */
@@ -633,7 +637,7 @@ public class PackageWatchdog {
* boot loop (including this time).
*/
default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
- return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
/**
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 3de65f94decf..6e2e5f75b8f3 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -107,7 +107,7 @@ public class RescueParty {
static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
"namespace_to_package_mapping";
@VisibleForTesting
- static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10);
+ static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10;
private static final String NAME = "rescue-party-observer";
@@ -117,6 +117,8 @@ public class RescueParty {
"persist.device_config.configuration.disable_rescue_party";
private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
"persist.device_config.configuration.disable_rescue_party_factory_reset";
+ private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
+ "persist.device_config.configuration.rescue_party_throttle_duration_min";
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -489,13 +491,14 @@ public class RescueParty {
switch(rescueLevel) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return PackageHealthObserverImpact.USER_IMPACT_LOW;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
case LEVEL_WARM_REBOOT:
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
case LEVEL_FACTORY_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
default:
- return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
}
@@ -633,7 +636,7 @@ public class RescueParty {
return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
mayPerformReboot(failedPackage)));
} else {
- return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
}
@@ -677,7 +680,7 @@ public class RescueParty {
@Override
public int onBootLoop(int mitigationCount) {
if (isDisabled()) {
- return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
}
@@ -721,7 +724,9 @@ public class RescueParty {
private boolean shouldThrottleReboot() {
Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0);
long now = System.currentTimeMillis();
- return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS;
+ long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
+ DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
+ return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
}
private boolean isPersistentSystemApp(@NonNull String packageName) {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java
index cc398d930c7e..e6c1750c4a1d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/core/java/com/android/server/SoundTriggerInternal.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.soundtrigger;
+package com.android.server;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,15 +29,13 @@ import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.permission.Identity;
import android.os.IBinder;
-import com.android.server.voiceinteraction.VoiceInteractionManagerService;
-
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
/**
* Provides a local service for managing voice-related recoginition models. This is primarily used
- * by the {@link VoiceInteractionManagerService}.
+ * by the {@code VoiceInteractionManagerService}.
*/
public interface SoundTriggerInternal {
/**
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 78cbf2bd80d8..ee18ed566725 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -26,6 +26,17 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED;
import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK;
@@ -88,6 +99,7 @@ import static com.android.internal.messages.nano.SystemMessageProto.SystemMessag
import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER;
import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT;
+import static com.android.internal.util.FrameworkStatsLog.FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_NORMAL;
import static com.android.internal.util.FrameworkStatsLog.SERVICE_REQUEST_EVENT_REPORTED__PACKAGE_STOPPED_STATE__PACKAGE_STATE_STOPPED;
@@ -116,6 +128,7 @@ import android.annotation.UptimeMillisLong;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityManagerInternal.ServiceNotificationPolicy;
import android.app.ActivityThread;
import android.app.AppGlobals;
@@ -1006,6 +1019,24 @@ public final class ActiveServices {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, neededGrants, callingUid, callingProcessName, callingPackage));
+ // We want to allow scheduling user-initiated jobs when the app is running a
+ // foreground service that was started in the same conditions that allows for scheduling
+ // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
+ // an FGS that started when the app was in the TOP or a BAL-approved state.
+ final boolean isFgs = r.isForeground || r.fgRequired;
+ if (isFgs) {
+ // As of Android UDC, the conditions required for the while-in-use permissions
+ // are the same conditions that we want, so we piggyback on that logic.
+ // Use that as a shortcut if possible to avoid having to recheck all the conditions.
+ final boolean whileInUseAllowsUiJobScheduling =
+ ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs(
+ r.mAllowWhileInUsePermissionInFgsReason);
+ r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling
+ || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage));
+ } else {
+ r.updateAllowUiJobScheduling(false);
+ }
+
if (fgRequired) {
// We are now effectively running a foreground service.
synchronized (mAm.mProcessStats.mLock) {
@@ -1145,7 +1176,7 @@ public final class ActiveServices {
} finally {
/* Will be a no-op if nothing pending */
mAm.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ OOM_ADJ_REASON_START_SERVICE);
}
} else {
unbindServiceLocked(connection);
@@ -1235,8 +1266,7 @@ public final class ActiveServices {
/* ignore - local call */
} finally {
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(
- OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
} else { // Starting a service
try {
@@ -1310,7 +1340,7 @@ public final class ActiveServices {
false /* packageFrozen */,
true /* enqueueOomAdj */);
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
if (error != null) {
return new ComponentName("!!", error);
}
@@ -1495,7 +1525,7 @@ public final class ActiveServices {
stopServiceLocked(service, true);
}
if (size > 0) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE);
}
}
}
@@ -3240,6 +3270,12 @@ public final class ActiveServices {
return;
}
Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
+ final long now = SystemClock.uptimeMillis();
+ logFGSStateChangeLocked(sr,
+ FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT,
+ now > sr.mFgsEnterTime ? (int) (now - sr.mFgsEnterTime) : 0,
+ FGS_STOP_REASON_UNKNOWN,
+ FGS_TYPE_POLICY_CHECK_UNKNOWN);
try {
sr.app.getThread().scheduleTimeoutService(sr, sr.getShortFgsInfo().getStartId());
} catch (RemoteException e) {
@@ -3289,7 +3325,7 @@ public final class ActiveServices {
Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
- mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+ mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
}
}
@@ -3623,7 +3659,7 @@ public final class ActiveServices {
needOomAdj = true;
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
permissionsReviewRequired, packageFrozen, true) != null) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
return 0;
}
}
@@ -3648,7 +3684,7 @@ public final class ActiveServices {
mAm.enqueueOomAdjTargetLocked(s.app);
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE);
}
final int packageState = wasStopped
@@ -3780,7 +3816,8 @@ public final class ActiveServices {
}
}
- serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false);
+ serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
+ OOM_ADJ_REASON_EXECUTING_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -3871,7 +3908,7 @@ public final class ActiveServices {
}
}
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -3918,7 +3955,8 @@ public final class ActiveServices {
}
}
- serviceDoneExecutingLocked(r, inDestroying, false, false);
+ serviceDoneExecutingLocked(r, inDestroying, false, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -4353,11 +4391,11 @@ public final class ActiveServices {
/**
* Bump the given service record into executing state.
* @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link
- * OomAdjuster#OOM_ADJ_REASON_NONE}.
+ * ActivityManagerInternal#OOM_ADJ_REASON_NONE}.
* @return {@code true} if it performed oomAdjUpdate.
*/
private boolean bumpServiceExecutingLocked(
- ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING "
+ why + " of " + r + " in app " + r.app);
else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING "
@@ -4409,7 +4447,7 @@ public final class ActiveServices {
}
}
boolean oomAdjusted = false;
- if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null
+ if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null
&& r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) {
// Force an immediate oomAdjUpdate, so the client app could be in the correct process
// state before doing any service related transactions
@@ -4433,8 +4471,7 @@ public final class ActiveServices {
+ " rebind=" + rebind);
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
- bumpServiceExecutingLocked(r, execInFg, "bind",
- OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
+ bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE);
if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding="
+ i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter);
@@ -4450,13 +4487,15 @@ public final class ActiveServices {
// Keep the executeNesting count accurate.
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
throw e;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_UNBIND_SERVICE);
return false;
}
}
@@ -4834,7 +4873,7 @@ public final class ActiveServices {
// Ignore, it's been logged and nothing upstack cares.
} finally {
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
}
@@ -5186,13 +5225,14 @@ public final class ActiveServices {
final ProcessServiceRecord psr = app.mServices;
final boolean newService = psr.startService(r);
- bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE);
+ bumpServiceExecutingLocked(r, execInFg, "create",
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
mAm.updateLruProcessLocked(app, false, null);
updateServiceForegroundLocked(psr, /* oomAdj= */ false);
// Force an immediate oomAdjUpdate, so the client app could be in the correct process state
// before doing any service related transactions
mAm.enqueueOomAdjTargetLocked(app);
- mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE);
boolean created = false;
try {
@@ -5226,7 +5266,8 @@ public final class ActiveServices {
if (!created) {
// Keep the executeNesting count accurate.
final boolean inDestroying = mDestroyingServices.contains(r);
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, false);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, false,
+ OOM_ADJ_REASON_STOP_SERVICE);
// Cleanup.
if (newService) {
@@ -5312,7 +5353,8 @@ public final class ActiveServices {
mAm.grantImplicitAccess(r.userId, si.intent, si.callingId,
UserHandle.getAppId(r.appInfo.uid)
);
- bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE);
+ bumpServiceExecutingLocked(r, execInFg, "start",
+ OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */);
if (r.fgRequired && !r.fgWaiting) {
if (!r.isForeground) {
if (DEBUG_BACKGROUND_CHECK) {
@@ -5338,7 +5380,7 @@ public final class ActiveServices {
if (!oomAdjusted) {
mAm.enqueueOomAdjTargetLocked(r.app);
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args);
slice.setInlineCountLimit(4);
@@ -5364,10 +5406,11 @@ public final class ActiveServices {
// Keep nesting count correct
final boolean inDestroying = mDestroyingServices.contains(r);
for (int i = 0, size = args.size(); i < size; i++) {
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, true);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, true,
+ OOM_ADJ_REASON_STOP_SERVICE);
}
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (caughtException instanceof TransactionTooLargeException) {
throw (TransactionTooLargeException)caughtException;
}
@@ -5454,7 +5497,7 @@ public final class ActiveServices {
if (ibr.hasBound) {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind",
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ OOM_ADJ_REASON_UNBIND_SERVICE);
ibr.hasBound = false;
ibr.requested = false;
r.app.getThread().scheduleUnbindService(r,
@@ -5608,7 +5651,7 @@ public final class ActiveServices {
} else {
try {
oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy",
- oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE);
mDestroyingServices.add(r);
r.destroying = true;
r.app.getThread().scheduleStopService(r);
@@ -5630,7 +5673,7 @@ public final class ActiveServices {
if (!oomAdjusted) {
mAm.enqueueOomAdjTargetLocked(r.app);
if (!enqueueOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
}
}
if (r.bindings.size() > 0) {
@@ -5755,8 +5798,7 @@ public final class ActiveServices {
if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
&& b.intent.hasBound) {
try {
- bumpServiceExecutingLocked(s, false, "unbind",
- OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY)
&& s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) {
// If this service's process is not already in the cached list,
@@ -5879,7 +5921,8 @@ public final class ActiveServices {
}
}
final long origId = Binder.clearCallingIdentity();
- serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj,
+ OOM_ADJ_REASON_EXECUTING_SERVICE);
Binder.restoreCallingIdentity(origId);
} else {
Slog.w(TAG, "Done executing unknown service from pid "
@@ -5898,11 +5941,11 @@ public final class ActiveServices {
r.tracker.setStarted(false, memFactor, now);
}
}
- serviceDoneExecutingLocked(r, true, true, enqueueOomAdj);
+ serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END);
}
private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
- boolean finishing, boolean enqueueOomAdj) {
+ boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r
+ ": nesting=" + r.executeNesting
+ ", inDestroying=" + inDestroying + ", app=" + r.app);
@@ -5938,7 +5981,7 @@ public final class ActiveServices {
if (enqueueOomAdj) {
mAm.enqueueOomAdjTargetLocked(r.app);
} else {
- mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjLocked(r.app, oomAdjReason);
}
}
r.executeFg = false;
@@ -6008,7 +6051,7 @@ public final class ActiveServices {
bringDownServiceLocked(sr, true);
}
/* Will be a no-op if nothing pending */
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE);
}
} catch (RemoteException e) {
Slog.w(TAG, "Exception in new application when starting service "
@@ -6068,7 +6111,7 @@ public final class ActiveServices {
}
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END);
}
}
@@ -6139,7 +6182,7 @@ public final class ActiveServices {
bringDownServiceLocked(mTmpCollectionResults.get(i), true);
}
if (size > 0) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED);
}
if (fullStop && !mTmpCollectionResults.isEmpty()) {
// if we're tearing down the app's entire service state, account for possible
@@ -6266,7 +6309,7 @@ public final class ActiveServices {
}
}
if (needOomAdj) {
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK);
}
}
@@ -6437,7 +6480,7 @@ public final class ActiveServices {
}
}
- mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
+ mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE);
if (!allowRestart) {
psr.stopAllServices();
@@ -7337,26 +7380,12 @@ public final class ActiveServices {
} else {
allowWhileInUse = REASON_UNKNOWN;
}
- // We want to allow scheduling user-initiated jobs when the app is running a
- // foreground service that was started in the same conditions that allows for scheduling
- // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running
- // an FGS that started when the app was in the TOP or a BAL-approved state.
- // As of Android UDC, the conditions required for the while-in-use permissions
- // are the same conditions that we want, so we piggyback on that logic.
- // We use that as a shortcut if possible so we don't have to recheck all the conditions.
- final boolean isFgs = r.isForeground || r.fgRequired;
- if (isFgs) {
- r.updateAllowUiJobScheduling(ActivityManagerService
- .doesReasonCodeAllowSchedulingUserInitiatedJobs(allowWhileInUse)
- || mAm.canScheduleUserInitiatedJobs(
- callingUid, callingPid, callingPackage, true));
- } else {
- r.updateAllowUiJobScheduling(false);
- }
+ r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
}
void resetFgsRestrictionLocked(ServiceRecord r) {
r.mAllowWhileInUsePermissionInFgs = false;
+ r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED;
r.mAllowStartForeground = REASON_DENIED;
r.mInfoAllowStartForeground = null;
r.mInfoTempFgsAllowListReason = null;
@@ -7400,14 +7429,17 @@ public final class ActiveServices {
final int uidState = mAm.getUidStateLocked(callingUid);
if (ret == REASON_DENIED) {
- // Is the calling UID at PROCESS_STATE_TOP or above?
+ // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT,
+ // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP.
if (uidState <= PROCESS_STATE_TOP) {
ret = getReasonCodeFromProcState(uidState);
}
}
if (ret == REASON_DENIED) {
- // Does the calling UID have any visible activity?
+ // Allow FGS while-in-use if the caller has visible activity.
+ // Here we directly check ActivityTaskManagerService, instead of checking
+ // PendingStartActivityUids in ActivityManagerService, which gives the same result.
final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid);
if (isCallingUidVisible) {
ret = REASON_UID_VISIBLE;
@@ -7415,7 +7447,8 @@ public final class ActiveServices {
}
if (ret == REASON_DENIED) {
- // Is the allow activity background start flag on?
+ // Allow FGS while-in-use if the background activity start flag is on. Because
+ // activity start can lead to FGS start in TOP state and obtain while-in-use.
if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) {
ret = REASON_START_ACTIVITY_FLAG;
}
@@ -7424,6 +7457,7 @@ public final class ActiveServices {
if (ret == REASON_DENIED) {
boolean isCallerSystem = false;
final int callingAppId = UserHandle.getAppId(callingUid);
+ // Allow FGS while-in-use for a list of special UIDs.
switch (callingAppId) {
case ROOT_UID:
case SYSTEM_UID:
@@ -7442,6 +7476,10 @@ public final class ActiveServices {
}
if (ret == REASON_DENIED) {
+ // Allow FGS while-in-use if the WindowManager allows background activity start.
+ // This is mainly to get the 10 seconds grace period if any activity in the caller has
+ // either started or finished very recently. The binding flag
+ // BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS is also allowed by the check here.
final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> {
if (pr.uid == callingUid) {
if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) {
@@ -7456,6 +7494,12 @@ public final class ActiveServices {
}
if (ret == REASON_DENIED) {
+ // Allow FGS while-in-use if the caller UID is in ActivityManagerService's
+ // mFgsWhileInUseTempAllowList. This is a temp allowlist to allow FGS while-in-use. It
+ // is used when MediaSessionService's bluetooth button or play/resume/stop commands are
+ // issued. The typical temp allowlist duration is 10 seconds.
+ // This temp allowlist mechanism can also be called by other system_server internal
+ // components such as Telephone/VOIP if they want to start a FGS and get while-in-use.
if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) {
return REASON_TEMP_ALLOWED_WHILE_IN_USE;
}
@@ -7463,6 +7507,8 @@ public final class ActiveServices {
if (ret == REASON_DENIED) {
if (targetProcess != null) {
+ // Allow FGS while-in-use if the caller of the instrumentation has
+ // START_ACTIVITIES_FROM_BACKGROUND permission.
ActiveInstrumentation instr = targetProcess.getActiveInstrumentation();
if (instr != null && instr.mHasBackgroundActivityStartsPermission) {
ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION;
@@ -7471,6 +7517,9 @@ public final class ActiveServices {
}
if (ret == REASON_DENIED) {
+ // Allow FGS while-in-use if the caller has START_ACTIVITIES_FROM_BACKGROUND
+ // permission, because starting an activity can lead to starting FGS from the TOP state
+ // and obtain while-in-use.
if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid)
== PERMISSION_GRANTED) {
ret = REASON_BACKGROUND_ACTIVITY_PERMISSION;
@@ -7478,6 +7527,8 @@ public final class ActiveServices {
}
if (ret == REASON_DENIED) {
+ // Allow FGS while-in-use if the caller is in the while-in-use allowlist. Right now
+ // AttentionService and SystemCaptionsService packageName are in this allowlist.
if (verifyPackage(callingPackage, callingUid)) {
final boolean isAllowedPackage =
mAllowListWhileInUsePermissionInFgs.contains(callingPackage);
@@ -7492,7 +7543,7 @@ public final class ActiveServices {
}
if (ret == REASON_DENIED) {
- // Is the calling UID a device owner app?
+ // Allow FGS while-in-use if the caller is the device owner.
final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid);
if (isDeviceOwner) {
ret = REASON_DEVICE_OWNER;
@@ -7897,7 +7948,8 @@ public final class ActiveServices {
boolean allowWhileInUsePermissionInFgs;
@PowerExemptionManager.ReasonCode int fgsStartReasonCode;
if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER
- || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT) {
+ || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__EXIT
+ || state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
allowWhileInUsePermissionInFgs = r.mAllowWhileInUsePermissionInFgsAtEntering;
fgsStartReasonCode = r.mAllowStartForegroundAtEntering;
} else {
@@ -7931,9 +7983,9 @@ public final class ActiveServices {
r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mClientUid : INVALID_UID,
r.mFgsDelegation != null ? r.mFgsDelegation.mOptions.mDelegationService
: ForegroundServiceDelegationOptions.DELEGATION_SERVICE_DEFAULT,
- 0,
- null,
- null);
+ 0 /* api_sate */,
+ null /* api_type */,
+ null /* api_timestamp */);
int event = 0;
if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__ENTER) {
@@ -7942,7 +7994,9 @@ public final class ActiveServices {
event = EventLogTags.AM_FOREGROUND_SERVICE_STOP;
} else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__DENIED) {
event = EventLogTags.AM_FOREGROUND_SERVICE_DENIED;
- } else {
+ } else if (state == FOREGROUND_SERVICE_STATE_CHANGED__STATE__TIMED_OUT) {
+ event = EventLogTags.AM_FOREGROUND_SERVICE_TIMED_OUT;
+ }else {
// Unknown event.
return;
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index ae5dbe11495a..44e198b53761 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -213,7 +213,7 @@ final class ActivityManagerConstants extends ContentObserver {
private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false;
private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000;
- private static final boolean DEFAULT_USE_MODERN_TRIM = false;
+ private static final boolean DEFAULT_USE_MODERN_TRIM = true;
/**
* Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ef7d5ae43396..97d34b8dd718 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,14 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED;
import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT;
@@ -199,6 +207,7 @@ import android.app.ActivityManagerInternal.BindServiceEventListener;
import android.app.ActivityManagerInternal.BroadcastEventListener;
import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
import android.app.ActivityManagerInternal.MediaProjectionTokenEvent;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.ActivityThread;
import android.app.AnrController;
@@ -368,7 +377,6 @@ import android.util.FeatureFlagUtils;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
import android.util.Log;
-import android.util.LogWriter;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Slog;
@@ -1968,7 +1976,7 @@ public class ActivityManagerService extends IActivityManager.Stub
app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM);
addPidLocked(app);
updateLruProcessLocked(app, false, null);
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
}
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(
@@ -2502,7 +2510,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// bind background threads to little cores
// this is expected to fail inside of framework tests because apps can't touch cpusets directly
// make sure we've already adjusted system_server's internal view of itself first
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT);
try {
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
@@ -3387,7 +3395,7 @@ public class ActivityManagerService extends IActivityManager.Stub
handleAppDiedLocked(app, pid, false, true, fromBinderDied);
if (doOomAdj) {
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
}
if (doLowMem) {
mAppProfiler.doLowMemReportIfNeededLocked(app);
@@ -4843,7 +4851,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (!didSomething) {
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
}
@@ -5485,7 +5493,7 @@ public class ActivityManagerService extends IActivityManager.Stub
"setProcessLimit()");
synchronized (this) {
mConstants.setOverrideMaxCachedProcesses(max);
- trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END);
}
}
@@ -5513,7 +5521,7 @@ public class ActivityManagerService extends IActivityManager.Stub
pr.mState.setForcingToImportant(null);
clearProcessForegroundLocked(pr);
}
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -5560,7 +5568,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
if (changed) {
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -6181,8 +6189,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final ProcessServiceRecord psr = pr.mServices;
if (psr != null && psr.hasForegroundServices()) {
- for (int s = psr.numberOfExecutingServices() - 1; s >= 0; --s) {
- final ServiceRecord sr = psr.getExecutingServiceAt(s);
+ for (int s = psr.numberOfRunningServices() - 1; s >= 0; --s) {
+ final ServiceRecord sr = psr.getRunningServiceAt(s);
if (sr.isForeground && sr.mAllowUiJobScheduling) {
return true;
}
@@ -6197,12 +6205,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}.
*/
// TODO(262260570): log allow reason to an atom
- private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
- return canScheduleUserInitiatedJobs(uid, pid, pkgName, false);
- }
-
- boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName,
- boolean skipWhileInUseCheck) {
+ boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) {
synchronized (this) {
final ProcessRecord processRecord;
synchronized (mPidsSelfLocked) {
@@ -6232,7 +6235,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// As of Android UDC, the conditions required to grant a while-in-use permission
// covers the majority of those cases, and so we piggyback on that logic as the base.
// Missing cases are added after.
- if (!skipWhileInUseCheck && mServices.canAllowWhileInUsePermissionInFgsLocked(
+ if (mServices.canAllowWhileInUsePermissionInFgsLocked(
pid, uid, pkgName, processRecord, backgroundStartPrivileges)) {
return true;
}
@@ -6869,7 +6872,7 @@ public class ActivityManagerService extends IActivityManager.Stub
new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION,
customProcess != null ? customProcess : info.processName));
updateLruProcessLocked(app, false, null);
- updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+ updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN);
}
// Report usage as process is persistent and being started.
@@ -6986,7 +6989,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mOomAdjProfiler.onWakefulnessChanged(wakefulness);
mOomAdjuster.onWakefulnessChanged(wakefulness);
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY);
}
}
}
@@ -7748,7 +7751,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
if (changed) {
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
} finally {
@@ -8728,7 +8731,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes,
// debuggerd will terminate the process, but there's a backup where ActivityManager will
// also kill it. Avoid that.
- if (!recoverable) {
+ if (recoverable) {
+ mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo);
+ } else {
mAppErrors.crashApplication(r, crashInfo);
}
}
@@ -9508,7 +9513,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mAppProfiler.setMemFactorOverrideLocked(level);
// Kick off an oom adj update since we forced a mem factor update.
- updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(OOM_ADJ_REASON_SHELL);
}
}
@@ -13408,7 +13413,7 @@ public class ActivityManagerService extends IActivityManager.Stub
proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
// Try not to kill the process during backup
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
// If the process is already attached, schedule the creation of the backup agent now.
// If it is not yet live, this will be done when it attaches to the framework.
@@ -13532,7 +13537,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Not backing this app up any more; reset its OOM adjustment
final ProcessRecord proc = backupTarget.app;
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP);
proc.setInFullBackup(false);
proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP);
@@ -13713,7 +13718,7 @@ public class ActivityManagerService extends IActivityManager.Stub
if (!sdkSandboxManagerLocal.canRegisterBroadcastReceiver(
/*IntentFilter=*/ filter, flags, onlyProtectedBroadcasts)) {
throw new SecurityException("SDK sandbox not allowed to register receiver"
- + " with the given IntentFilter: " + filter.toString());
+ + " with the given IntentFilter: " + filter.toLongString());
}
}
@@ -13923,7 +13928,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// If we actually concluded any broadcasts, we might now be able
// to trim the recipients' apps from our working set
if (doTrim) {
- trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+ trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
return;
}
}
@@ -15185,7 +15190,7 @@ public class ActivityManagerService extends IActivityManager.Stub
queue.finishReceiverLocked(callerApp, resultCode,
resultData, resultExtras, resultAbort, true);
// updateOomAdjLocked() will be done here
- trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER);
+ trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER);
}
} finally {
@@ -16130,7 +16135,7 @@ public class ActivityManagerService extends IActivityManager.Stub
item.foregroundServiceTypes = fgServiceTypes;
}
if (oomAdj) {
- updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -16196,7 +16201,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* {@link #enqueueOomAdjTargetLocked}.
*/
@GuardedBy("this")
- void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason);
}
@@ -16215,7 +16220,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@GuardedBy("this")
- final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) {
mOomAdjuster.updateOomAdjLocked(oomAdjReason);
}
@@ -16227,8 +16232,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* @return whether updateOomAdjLocked(app) was successful.
*/
@GuardedBy("this")
- final boolean updateOomAdjLocked(
- ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) {
return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason);
}
@@ -16461,16 +16465,14 @@ public class ActivityManagerService extends IActivityManager.Stub
mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist);
}
- private void trimApplications(
- boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
synchronized (this) {
trimApplicationsLocked(forceFullOomAdj, oomAdjReason);
}
}
@GuardedBy("this")
- private void trimApplicationsLocked(
- boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) {
// First remove any unused application processes whose package
// has been removed.
boolean didSomething = false;
@@ -17442,7 +17444,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
pr.mState.setHasOverlayUi(hasOverlayUi);
//Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid);
- updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY);
}
}
@@ -17577,7 +17579,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void trimApplications() {
- ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY);
}
public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) {
@@ -17626,9 +17628,9 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void updateOomAdj() {
+ public void updateOomAdj(@OomAdjReason int oomAdjReason) {
synchronized (ActivityManagerService.this) {
- ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ ActivityManagerService.this.updateOomAdjLocked(oomAdjReason);
}
}
@@ -18288,8 +18290,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
// workaround can be removed. (b/213288355)
if (isNewPending) {
- mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid,
- OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY);
}
// We need to update the network rules for the app coming to the top state so that
// it can access network when the device or the app is in a restricted state
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index e080a80e1c37..17a0d62c27b3 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -247,6 +247,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runSendBroadcast(pw);
case "compact":
return runCompact(pw);
+ case "freeze":
+ return runFreeze(pw);
+ case "unfreeze":
+ return runUnfreeze(pw);
case "instrument":
getOutPrintWriter().println("Error: must be invoked through 'am instrument'.");
return -1;
@@ -1074,20 +1078,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
boolean isFullCompact = op.equals("full");
boolean isSomeCompact = op.equals("some");
if (isFullCompact || isSomeCompact) {
- String processName = getNextArgRequired();
- synchronized (mInternal.mProcLock) {
- // Default to current user
- int userId = mInterface.getCurrentUserId();
- String userOpt = getNextOption();
- if (userOpt != null && "--user".equals(userOpt)) {
- int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
- if (inputUserId != UserHandle.USER_CURRENT) {
- userId = inputUserId;
- }
- }
- final int uid =
- mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
- app = mInternal.getProcessRecordLocked(processName, uid);
+ app = getProcessFromShell();
+ if (app == null) {
+ getErrPrintWriter().println("Error: could not find process");
+ return -1;
}
pw.println("Process record found pid: " + app.mPid);
if (isFullCompact) {
@@ -1143,6 +1137,93 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ @NeverCompile
+ int runFreeze(PrintWriter pw) throws RemoteException {
+ String freezerOpt = getNextOption();
+ boolean isSticky = false;
+ if (freezerOpt != null) {
+ isSticky = freezerOpt.equals("--sticky");
+ }
+ ProcessRecord app = getProcessFromShell();
+ if (app == null) {
+ getErrPrintWriter().println("Error: could not find process");
+ return -1;
+ }
+ pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky);
+ synchronized (mInternal) {
+ synchronized (mInternal.mProcLock) {
+ app.mOptRecord.setFreezeSticky(isSticky);
+ mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true);
+ }
+ }
+ return 0;
+ }
+
+ @NeverCompile
+ int runUnfreeze(PrintWriter pw) throws RemoteException {
+ String freezerOpt = getNextOption();
+ boolean isSticky = false;
+ if (freezerOpt != null) {
+ isSticky = freezerOpt.equals("--sticky");
+ }
+ ProcessRecord app = getProcessFromShell();
+ if (app == null) {
+ getErrPrintWriter().println("Error: could not find process");
+ return -1;
+ }
+ pw.println("Unfreezing pid: " + app.mPid);
+ synchronized (mInternal) {
+ synchronized (mInternal.mProcLock) {
+ synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) {
+ app.mOptRecord.setFreezeSticky(isSticky);
+ mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0,
+ false);
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Parses from the shell the process name and user id if provided and provides the corresponding
+ * {@link ProcessRecord)} If no user is provided, it will fallback to current user.
+ * Example usage: {@code <processname> --user current} or {@code <processname>}
+ * @return process record of process, null if none found.
+ * @throws RemoteException
+ */
+ @NeverCompile
+ ProcessRecord getProcessFromShell() throws RemoteException {
+ ProcessRecord app;
+ String processName = getNextArgRequired();
+ synchronized (mInternal.mProcLock) {
+ // Default to current user
+ int userId = getUserIdFromShellOrFallback();
+ final int uid =
+ mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId);
+ app = mInternal.getProcessRecordLocked(processName, uid);
+ }
+ return app;
+ }
+
+ /**
+ * @return User id from command line provided in the form of
+ * {@code --user <userid|current|all>} and if the argument is not found it will fallback
+ * to current user.
+ * @throws RemoteException
+ */
+ @NeverCompile
+ int getUserIdFromShellOrFallback() throws RemoteException {
+ int userId = mInterface.getCurrentUserId();
+ String userOpt = getNextOption();
+ if (userOpt != null && "--user".equals(userOpt)) {
+ int inputUserId = UserHandle.parseUserArg(getNextArgRequired());
+ if (inputUserId != UserHandle.USER_CURRENT) {
+ userId = inputUserId;
+ }
+ }
+ return userId;
+ }
+
int runDumpHeap(PrintWriter pw) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
boolean managed = true;
@@ -4061,6 +4142,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Perform a native compaction for process with <pid>.");
pw.println(" some: execute file compaction.");
pw.println(" full: execute anon + file compaction.");
+ pw.println(" freeze [--sticky] <processname> [--user <USER_ID>]");
+ pw.println(" Freeze a process.");
+ pw.println(" --sticky: persists the frozen state for the process lifetime or");
+ pw.println(" until an unfreeze is triggered via shell");
+ pw.println(" unfreeze [--sticky] <processname> [--user <USER_ID>]");
+ pw.println(" Unfreeze a process.");
+ pw.println(" --sticky: persists the unfrozen state for the process lifetime or");
+ pw.println(" until a freeze is triggered via shell");
pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]");
pw.println(" [--user <USER_ID> | current]");
pw.println(" [--no-hidden-api-checks [--no-test-api-access]]");
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c343ec24412a..061bcd740f6b 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -555,6 +555,15 @@ class AppErrors {
}
}
+ void sendRecoverableCrashToAppExitInfo(
+ ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
+ if (r == null || crashInfo == null
+ || !"Native crash".equals(crashInfo.exceptionClassName)) return;
+ synchronized (mService) {
+ mService.mProcessList.noteAppRecoverableCrash(r);
+ }
+ }
+
/**
* Bring up the "unexpected error" dialog box for a crashing app.
* Deal with edge cases (intercepts from instrumented applications,
diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java
index 44436369fb31..4c0dd115a3cc 100644
--- a/services/core/java/com/android/server/am/AppExitInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java
@@ -308,6 +308,16 @@ public final class AppExitInfoTracker {
mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget();
}
+ void scheduleNoteAppRecoverableCrash(final ProcessRecord app) {
+ if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return;
+
+ ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis());
+ raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE);
+ raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN);
+ raw.setDescription("recoverable_crash");
+ mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget();
+ }
+
void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason,
final @SubReason int subReason, final String msg) {
if (!mAppExitInfoLoaded.get()) {
@@ -421,8 +431,24 @@ public final class AppExitInfoTracker {
scheduleLogToStatsdLocked(info, true);
}
+ /**
+ * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't
+ * being killed but the crash should still be added to AppExitInfo. Also, because we're not
+ * crashing, don't log out to statsd.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) {
+ addExitInfoLocked(raw, /* recoverable */ true);
+ }
+
@GuardedBy("mLock")
private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) {
+ return addExitInfoLocked(raw, /* recoverable */ false);
+ }
+
+ @GuardedBy("mLock")
+ private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) {
if (!mAppExitInfoLoaded.get()) {
Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage");
return null;
@@ -438,7 +464,7 @@ public final class AppExitInfoTracker {
}
}
for (int i = 0; i < packages.length; i++) {
- addExitInfoInnerLocked(packages[i], uid, info);
+ addExitInfoInnerLocked(packages[i], uid, info, recoverable);
}
schedulePersistProcessExitInfo(false);
@@ -845,7 +871,8 @@ public final class AppExitInfoTracker {
}
@GuardedBy("mLock")
- private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) {
+ private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info,
+ boolean recoverable) {
AppExitInfoContainer container = mData.get(packageName, uid);
if (container == null) {
container = new AppExitInfoContainer(mAppExitInfoHistoryListSize);
@@ -859,7 +886,11 @@ public final class AppExitInfoTracker {
}
mData.put(packageName, uid, container);
}
- container.addExitInfoLocked(info);
+ if (recoverable) {
+ container.addRecoverableCrashLocked(info);
+ } else {
+ container.addExitInfoLocked(info);
+ }
}
@GuardedBy("mLock")
@@ -1284,38 +1315,40 @@ public final class AppExitInfoTracker {
* A container class of {@link android.app.ApplicationExitInfo}
*/
final class AppExitInfoContainer {
- private SparseArray<ApplicationExitInfo> mInfos; // index is pid
+ private SparseArray<ApplicationExitInfo> mInfos; // index is a pid
+ private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid
private int mMaxCapacity;
private int mUid; // Application uid, not isolated uid.
AppExitInfoContainer(final int maxCapacity) {
mInfos = new SparseArray<ApplicationExitInfo>();
+ mRecoverableCrashes = new SparseArray<ApplicationExitInfo>();
mMaxCapacity = maxCapacity;
}
@GuardedBy("mLock")
- void getExitInfoLocked(final int filterPid, final int maxNum,
- ArrayList<ApplicationExitInfo> results) {
+ void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid,
+ final int maxNum, ArrayList<ApplicationExitInfo> results) {
if (filterPid > 0) {
- ApplicationExitInfo r = mInfos.get(filterPid);
+ ApplicationExitInfo r = map.get(filterPid);
if (r != null) {
results.add(r);
}
} else {
- final int numRep = mInfos.size();
+ final int numRep = map.size();
if (maxNum <= 0 || numRep <= maxNum) {
// Return all records.
for (int i = 0; i < numRep; i++) {
- results.add(mInfos.valueAt(i));
+ results.add(map.valueAt(i));
}
Collections.sort(results,
(a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
} else {
if (maxNum == 1) {
// Most of the caller might be only interested with the most recent one
- ApplicationExitInfo r = mInfos.valueAt(0);
+ ApplicationExitInfo r = map.valueAt(0);
for (int i = 1; i < numRep; i++) {
- ApplicationExitInfo t = mInfos.valueAt(i);
+ ApplicationExitInfo t = map.valueAt(i);
if (r.getTimestamp() < t.getTimestamp()) {
r = t;
}
@@ -1326,7 +1359,7 @@ public final class AppExitInfoTracker {
ArrayList<ApplicationExitInfo> list = mTmpInfoList2;
list.clear();
for (int i = 0; i < numRep; i++) {
- list.add(mInfos.valueAt(i));
+ list.add(map.valueAt(i));
}
Collections.sort(list,
(a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
@@ -1340,24 +1373,30 @@ public final class AppExitInfoTracker {
}
@GuardedBy("mLock")
- void addExitInfoLocked(ApplicationExitInfo info) {
+ void getExitInfoLocked(final int filterPid, final int maxNum,
+ ArrayList<ApplicationExitInfo> results) {
+ getInfosLocked(mInfos, filterPid, maxNum, results);
+ }
+
+ @GuardedBy("mLock")
+ void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) {
int size;
- if ((size = mInfos.size()) >= mMaxCapacity) {
+ if ((size = map.size()) >= mMaxCapacity) {
int oldestIndex = -1;
long oldestTimeStamp = Long.MAX_VALUE;
for (int i = 0; i < size; i++) {
- ApplicationExitInfo r = mInfos.valueAt(i);
+ ApplicationExitInfo r = map.valueAt(i);
if (r.getTimestamp() < oldestTimeStamp) {
oldestTimeStamp = r.getTimestamp();
oldestIndex = i;
}
}
if (oldestIndex >= 0) {
- final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile();
+ final File traceFile = map.valueAt(oldestIndex).getTraceFile();
if (traceFile != null) {
traceFile.delete();
}
- mInfos.removeAt(oldestIndex);
+ map.removeAt(oldestIndex);
}
}
// Claim the state information if there is any
@@ -1367,7 +1406,17 @@ public final class AppExitInfoTracker {
mActiveAppStateSummary, uid, pid));
info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid));
info.setAppTraceRetriever(mAppTraceRetriever);
- mInfos.append(pid, info);
+ map.append(pid, info);
+ }
+
+ @GuardedBy("mLock")
+ void addExitInfoLocked(ApplicationExitInfo info) {
+ addInfoLocked(mInfos, info);
+ }
+
+ @GuardedBy("mLock")
+ void addRecoverableCrashLocked(ApplicationExitInfo info) {
+ addInfoLocked(mRecoverableCrashes, info);
}
@GuardedBy("mLock")
@@ -1382,9 +1431,9 @@ public final class AppExitInfoTracker {
}
@GuardedBy("mLock")
- void destroyLocked() {
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- ApplicationExitInfo ai = mInfos.valueAt(i);
+ void destroyLocked(SparseArray<ApplicationExitInfo> map) {
+ for (int i = map.size() - 1; i >= 0; i--) {
+ ApplicationExitInfo ai = map.valueAt(i);
final File traceFile = ai.getTraceFile();
if (traceFile != null) {
traceFile.delete();
@@ -1395,24 +1444,37 @@ public final class AppExitInfoTracker {
}
@GuardedBy("mLock")
+ void destroyLocked() {
+ destroyLocked(mInfos);
+ destroyLocked(mRecoverableCrashes);
+ }
+
+ @GuardedBy("mLock")
void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) {
- if (callback != null) {
- for (int i = mInfos.size() - 1; i >= 0; i--) {
- switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
- case FOREACH_ACTION_REMOVE_ITEM:
- final File traceFile = mInfos.valueAt(i).getTraceFile();
- if (traceFile != null) {
- traceFile.delete();
- }
- mInfos.removeAt(i);
- break;
- case FOREACH_ACTION_STOP_ITERATION:
- i = 0;
- break;
- case FOREACH_ACTION_NONE:
- default:
- break;
- }
+ if (callback == null) return;
+ for (int i = mInfos.size() - 1; i >= 0; i--) {
+ switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) {
+ case FOREACH_ACTION_STOP_ITERATION: return;
+ case FOREACH_ACTION_REMOVE_ITEM:
+ final File traceFile = mInfos.valueAt(i).getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ mInfos.removeAt(i);
+ break;
+ }
+ }
+ for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+ switch (callback.apply(
+ mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) {
+ case FOREACH_ACTION_STOP_ITERATION: return;
+ case FOREACH_ACTION_REMOVE_ITEM:
+ final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile();
+ if (traceFile != null) {
+ traceFile.delete();
+ }
+ mRecoverableCrashes.removeAt(i);
+ break;
}
}
}
@@ -1423,6 +1485,9 @@ public final class AppExitInfoTracker {
for (int i = mInfos.size() - 1; i >= 0; i--) {
list.add(mInfos.valueAt(i));
}
+ for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+ list.add(mRecoverableCrashes.valueAt(i));
+ }
Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp()));
int size = list.size();
for (int i = 0; i < size; i++) {
@@ -1434,10 +1499,13 @@ public final class AppExitInfoTracker {
void writeToProto(ProtoOutputStream proto, long fieldId) {
long token = proto.start(fieldId);
proto.write(AppsExitInfoProto.Package.User.UID, mUid);
- int size = mInfos.size();
- for (int i = 0; i < size; i++) {
+ for (int i = 0; i < mInfos.size(); i++) {
mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
}
+ for (int i = 0; i < mRecoverableCrashes.size(); i++) {
+ mRecoverableCrashes.valueAt(i).writeToProto(
+ proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+ }
proto.end(token);
}
@@ -1448,14 +1516,23 @@ public final class AppExitInfoTracker {
next != ProtoInputStream.NO_MORE_FIELDS;
next = proto.nextField()) {
switch (next) {
- case (int) AppsExitInfoProto.Package.User.UID:
+ case (int) AppsExitInfoProto.Package.User.UID: {
mUid = proto.readInt(AppsExitInfoProto.Package.User.UID);
break;
- case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO:
+ }
+ case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: {
ApplicationExitInfo info = new ApplicationExitInfo();
info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO);
mInfos.put(info.getPid(), info);
break;
+ }
+ case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: {
+ ApplicationExitInfo info = new ApplicationExitInfo();
+ info.readFromProto(
+ proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH);
+ mRecoverableCrashes.put(info.getPid(), info);
+ break;
+ }
}
}
proto.end(token);
@@ -1472,6 +1549,11 @@ public final class AppExitInfoTracker {
list.add(mInfos.valueAt(i));
}
}
+ for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) {
+ if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) {
+ list.add(mRecoverableCrashes.valueAt(i));
+ }
+ }
return list;
}
}
@@ -1610,6 +1692,7 @@ public final class AppExitInfoTracker {
static final int MSG_PROC_DIED = 4103;
static final int MSG_APP_KILL = 4104;
static final int MSG_STATSD_LOG = 4105;
+ static final int MSG_APP_RECOVERABLE_CRASH = 4106;
KillHandler(Looper looper) {
super(looper, null, true);
@@ -1648,6 +1731,14 @@ public final class AppExitInfoTracker {
}
}
break;
+ case MSG_APP_RECOVERABLE_CRASH: {
+ ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj;
+ synchronized (mLock) {
+ handleNoteAppRecoverableCrashLocked(raw);
+ }
+ recycleRawRecord(raw);
+ }
+ break;
default:
super.handleMessage(msg);
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index ed297d0867a1..0744f751354d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -108,8 +108,8 @@ import com.android.server.power.stats.BatteryExternalStatsWorker;
import com.android.server.power.stats.BatteryStatsImpl;
import com.android.server.power.stats.BatteryUsageStatsProvider;
import com.android.server.power.stats.BatteryUsageStatsStore;
-import com.android.server.power.stats.CpuWakeupStats;
import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes;
+import com.android.server.power.stats.wakeups.CpuWakeupStats;
import java.io.File;
import java.io.FileDescriptor;
@@ -515,13 +515,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub
@Override
public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) {
Objects.requireNonNull(uids);
- mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids);
+ mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids));
}
-
@Override
public void noteWakingSoundTrigger(long elapsedMillis, int uid) {
- // TODO(b/267717665): Pipe to noteCpuWakingActivity once SoundTrigger starts using this.
- Slog.w(TAG, "Sound trigger event dispatched to uid " + uid);
+ noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, elapsedMillis, uid);
}
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index dbb351b23c85..0767218ec065 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -18,9 +18,9 @@ package com.android.server.am;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.server.am.BroadcastRecord.deliveryStateToString;
-import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
+import android.annotation.CheckResult;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -188,6 +188,12 @@ class BroadcastProcessQueue {
private @Reason int mRunnableAtReason = REASON_EMPTY;
private boolean mRunnableAtInvalidated;
+ /**
+ * Last state applied by {@link #updateDeferredStates}, used to quickly
+ * determine if a state transition is occurring.
+ */
+ private boolean mLastDeferredStates;
+
private boolean mUidCached;
private boolean mProcessInstrumented;
private boolean mProcessPersistent;
@@ -236,7 +242,15 @@ class BroadcastProcessQueue {
*/
@Nullable
public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
- int recordIndex, boolean wouldBeSkipped) {
+ int recordIndex, boolean wouldBeSkipped,
+ @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
+ // When updateDeferredStates() has already applied a deferred state to
+ // all pending items, apply to this new broadcast too
+ if (mLastDeferredStates && record.deferUntilActive
+ && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
+
if (record.isReplacePending()) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex,
wouldBeSkipped);
@@ -341,7 +355,12 @@ class BroadcastProcessQueue {
* Predicates that choose to remove a broadcast <em>must</em> finish
* delivery of the matched broadcast, to ensure that situations like ordered
* broadcasts are handled consistently.
+ *
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
*/
+ @CheckResult
public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
@NonNull BroadcastConsumer consumer, boolean andRemove) {
boolean didSomething = false;
@@ -354,6 +373,7 @@ class BroadcastProcessQueue {
return didSomething;
}
+ @CheckResult
private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
@NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer,
boolean andRemove) {
@@ -370,6 +390,10 @@ class BroadcastProcessQueue {
args.recycle();
it.remove();
onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped);
+ } else {
+ // Even if we're leaving broadcast in queue, it may have
+ // been mutated in such a way to change our runnable time
+ invalidateRunnableAt();
}
didSomething = true;
}
@@ -381,32 +405,44 @@ class BroadcastProcessQueue {
/**
* Update the actively running "warm" process for this process.
+ *
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
*/
- public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
+ @CheckResult
+ public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) {
this.app = app;
- if (app != null) {
- setUidCached(uidCached);
- setProcessInstrumented(app.getActiveInstrumentation() != null);
- setProcessPersistent(app.isPersistent());
- } else {
- setUidCached(uidCached);
- setProcessInstrumented(false);
- setProcessPersistent(false);
- }
// Since we may have just changed our PID, invalidate cached strings
mCachedToString = null;
mCachedToShortString = null;
+
+ boolean didSomething = false;
+ if (app != null) {
+ didSomething |= setUidCached(uidCached);
+ didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null);
+ didSomething |= setProcessPersistent(app.isPersistent());
+ } else {
+ didSomething |= setUidCached(uidCached);
+ didSomething |= setProcessInstrumented(false);
+ didSomething |= setProcessPersistent(false);
+ }
+ return didSomething;
}
/**
* Update if this process is in the "cached" state, typically signaling that
* broadcast dispatch should be paused or delayed.
*/
- private void setUidCached(boolean uidCached) {
+ @CheckResult
+ private boolean setUidCached(boolean uidCached) {
if (mUidCached != uidCached) {
mUidCached = uidCached;
invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
}
}
@@ -415,10 +451,14 @@ class BroadcastProcessQueue {
* signaling that broadcast dispatch should bypass all pauses or delays, to
* avoid holding up test suites.
*/
- private void setProcessInstrumented(boolean instrumented) {
+ @CheckResult
+ private boolean setProcessInstrumented(boolean instrumented) {
if (mProcessInstrumented != instrumented) {
mProcessInstrumented = instrumented;
invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
}
}
@@ -426,10 +466,14 @@ class BroadcastProcessQueue {
* Update if this process is in the "persistent" state, which signals broadcast dispatch should
* bypass all pauses or delays to prevent the system from becoming out of sync with itself.
*/
- private void setProcessPersistent(boolean persistent) {
+ @CheckResult
+ private boolean setProcessPersistent(boolean persistent) {
if (mProcessPersistent != persistent) {
mProcessPersistent = persistent;
invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
}
}
@@ -441,17 +485,17 @@ class BroadcastProcessQueue {
}
public int getPreferredSchedulingGroupLocked() {
- if (mCountForeground > mCountForegroundDeferred) {
+ if (!isActive()) {
+ return ProcessList.SCHED_GROUP_UNDEFINED;
+ } else if (mCountForeground > mCountForegroundDeferred) {
// We have a foreground broadcast somewhere down the queue, so
// boost priority until we drain them all
return ProcessList.SCHED_GROUP_DEFAULT;
} else if ((mActive != null) && mActive.isForeground()) {
// We have a foreground broadcast right now, so boost priority
return ProcessList.SCHED_GROUP_DEFAULT;
- } else if (!isIdle()) {
- return ProcessList.SCHED_GROUP_BACKGROUND;
} else {
- return ProcessList.SCHED_GROUP_UNDEFINED;
+ return ProcessList.SCHED_GROUP_BACKGROUND;
}
}
@@ -649,8 +693,20 @@ class BroadcastProcessQueue {
return mActive != null;
}
- void forceDelayBroadcastDelivery(long delayedDurationMs) {
- mForcedDelayedDurationMs = delayedDurationMs;
+ /**
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
+ */
+ @CheckResult
+ boolean forceDelayBroadcastDelivery(long delayedDurationMs) {
+ if (mForcedDelayedDurationMs != delayedDurationMs) {
+ mForcedDelayedDurationMs = delayedDurationMs;
+ invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -709,7 +765,7 @@ class BroadcastProcessQueue {
|| consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
final boolean isLPQueueEligible = shouldConsiderLPQueue
&& nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
- && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+ && !nextLPRecord.isBlocked(nextLPRecordIndex);
return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
}
@@ -722,10 +778,21 @@ class BroadcastProcessQueue {
* broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
* waiting. This is typically used in case there are callers waiting for "barrier" to be
* reached.
+ *
+ * @return if this operation may have changed internal state, indicating
+ * that the caller is responsible for invoking
+ * {@link BroadcastQueueModernImpl#updateRunnableList}
*/
+ @CheckResult
@VisibleForTesting
- void setPrioritizeEarliest(boolean prioritizeEarliest) {
- mPrioritizeEarliest = prioritizeEarliest;
+ boolean setPrioritizeEarliest(boolean prioritizeEarliest) {
+ if (mPrioritizeEarliest != prioritizeEarliest) {
+ mPrioritizeEarliest = prioritizeEarliest;
+ invalidateRunnableAt();
+ return true;
+ } else {
+ return false;
+ }
}
/**
@@ -912,39 +979,20 @@ class BroadcastProcessQueue {
}
}
- private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) {
- final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
-
- int existingDeferredCount = 0;
- if (r.deferUntilActive) {
- for (int i = 0; i < index; i++) {
- if (r.deferredUntilActive[i]) existingDeferredCount++;
- }
- }
-
- // We might be blocked waiting for other receivers to finish,
- // typically for an ordered broadcast or priority traunches
- if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount
- && !isDeliveryStateTerminal(r.getDeliveryState(index))) {
- return true;
- }
- return false;
- }
-
/**
- * Update {@link #getRunnableAt()} if it's currently invalidated.
+ * Update {@link #getRunnableAt()}, when needed.
*/
- private void updateRunnableAt() {
- final SomeArgs next = peekNextBroadcast();
+ void updateRunnableAt() {
+ if (!mRunnableAtInvalidated) return;
mRunnableAtInvalidated = false;
+
+ final SomeArgs next = peekNextBroadcast();
if (next != null) {
final BroadcastRecord r = (BroadcastRecord) next.arg1;
final int index = next.argi1;
final long runnableAt = r.enqueueTime;
- // If we're specifically queued behind other ordered dispatch activity,
- // we aren't runnable yet
- if (blockedOnOrderedDispatch(r, index)) {
+ if (r.isBlocked(index)) {
mRunnableAt = Long.MAX_VALUE;
mRunnableAtReason = REASON_BLOCKED;
return;
@@ -1047,10 +1095,44 @@ class BroadcastProcessQueue {
}
/**
+ * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our
+ * pending broadcasts, when needed.
+ */
+ void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
+ @NonNull BroadcastConsumer clearConsumer) {
+ // When all we have pending is deferred broadcasts, and we're cached,
+ // then we want everything to be marked deferred
+ final boolean wantDeferredStates = (mCountDeferred > 0)
+ && (mCountDeferred == mCountEnqueued) && mUidCached;
+
+ if (mLastDeferredStates != wantDeferredStates) {
+ mLastDeferredStates = wantDeferredStates;
+ if (wantDeferredStates) {
+ forEachMatchingBroadcast((r, i) -> {
+ return r.deferUntilActive
+ && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
+ }, applyConsumer, false);
+ } else {
+ forEachMatchingBroadcast((r, i) -> {
+ return r.deferUntilActive
+ && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+ }, clearConsumer, false);
+ }
+ }
+ }
+
+ /**
* Check overall health, confirming things are in a reasonable state and
* that we're not wedged.
*/
public void assertHealthLocked() {
+ // If we're not actively running, we should be sorted into the runnable
+ // list, and if we're invalidated then someone likely forgot to invoke
+ // updateRunnableList() to re-sort us into place
+ if (!isActive()) {
+ checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated");
+ }
+
assertHealthLocked(mPending);
assertHealthLocked(mPendingUrgent);
assertHealthLocked(mPendingOffload);
@@ -1153,19 +1235,30 @@ class BroadcastProcessQueue {
return mCachedToShortString;
}
+ public String describeStateLocked() {
+ return describeStateLocked(SystemClock.uptimeMillis());
+ }
+
+ public String describeStateLocked(@UptimeMillisLong long now) {
+ final StringBuilder sb = new StringBuilder();
+ if (isRunnable()) {
+ sb.append("runnable at ");
+ TimeUtils.formatDuration(getRunnableAt(), now, sb);
+ } else {
+ sb.append("not runnable");
+ }
+ sb.append(" because ");
+ sb.append(reasonToString(mRunnableAtReason));
+ return sb.toString();
+ }
+
@NeverCompile
public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) {
if ((mActive == null) && isEmpty()) return;
pw.print(toShortString());
- if (isRunnable()) {
- pw.print(" runnable at ");
- TimeUtils.formatDuration(getRunnableAt(), now, pw);
- } else {
- pw.print(" not runnable");
- }
- pw.print(" because ");
- pw.print(reasonToString(mRunnableAtReason));
+ pw.print(" ");
+ pw.print(describeStateLocked(now));
pw.println();
pw.increaseIndent();
@@ -1262,12 +1355,12 @@ class BroadcastProcessQueue {
pw.print(info.activityInfo.name);
}
pw.println();
- final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
- if (blockedUntilTerminalCount != -1) {
+ final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex];
+ if (blockedUntilBeyondCount != -1) {
pw.print(" blocked until ");
- pw.print(blockedUntilTerminalCount);
+ pw.print(blockedUntilBeyondCount);
pw.print(", currently at ");
- pw.print(record.terminalCount);
+ pw.print(record.beyondCount);
pw.print(" of ");
pw.println(record.receivers.size());
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index bd36c3ff6f98..5a4d315767ca 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
import static android.text.TextUtils.formatSimple;
@@ -37,7 +38,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_L
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a4bdf61e628f..8735f8a37b8b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE;
@@ -38,7 +39,6 @@ import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
import static com.android.server.am.BroadcastRecord.getReceiverUid;
import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -327,6 +327,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return;
}
+ // To place ourselves correctly in the runnable list, we may need to
+ // update internals that may have been invalidated; we wait until now at
+ // the last possible moment to avoid duplicated work
+ queue.updateDeferredStates(mBroadcastConsumerDeferApply, mBroadcastConsumerDeferClear);
+ queue.updateRunnableAt();
+
final boolean wantQueue = queue.isRunnable();
final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null)
|| (queue.runnableAtNext != null);
@@ -352,8 +358,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If app isn't running, and there's nothing in the queue, clean up
if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
- } else {
- updateQueueDeferred(queue);
}
}
@@ -619,14 +623,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
enqueuedBroadcast = true;
final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast(
- r, i, wouldBeSkipped);
+ r, i, wouldBeSkipped, mBroadcastConsumerDeferApply);
if (replacedBroadcast != null) {
replacedBroadcasts.add(replacedBroadcast);
}
- if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) {
- setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED,
- "deferred at enqueue time");
- }
updateRunnableList(queue);
enqueueUpdateRunningList();
}
@@ -1008,6 +1008,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
final BroadcastRecord r = queue.getActive();
+ final int index = queue.getActiveIndex();
if (r.ordered) {
r.resultCode = resultCode;
r.resultData = resultData;
@@ -1015,18 +1016,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
if (!r.isNoAbort()) {
r.resultAbort = resultAbort;
}
+ }
- // When the caller aborted an ordered broadcast, we mark all
- // remaining receivers as skipped
- if (r.resultAbort) {
- for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
- setDeliveryState(null, null, r, i, r.receivers.get(i),
- BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
- }
+ // To ensure that "beyond" high-water marks are updated in a monotonic
+ // way, we finish this receiver before possibly skipping any remaining
+ // aborted receivers
+ final boolean res = finishReceiverActiveLocked(queue,
+ BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+
+ // When the caller aborted an ordered broadcast, we mark all
+ // remaining receivers as skipped
+ if (r.resultAbort) {
+ for (int i = index + 1; i < r.receivers.size(); i++) {
+ setDeliveryState(null, null, r, i, r.receivers.get(i),
+ BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
}
}
- return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
+ return res;
}
/**
@@ -1108,21 +1115,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
@NonNull Object receiver, @DeliveryState int newDeliveryState,
@NonNull String reason) {
final int cookie = traceBegin("setDeliveryState");
+
+ // Remember the old state and apply the new state
final int oldDeliveryState = getDeliveryState(r, index);
- boolean checkFinished = false;
-
- // Only apply state when we haven't already reached a terminal state;
- // this is how we ignore racing timeout messages
- if (!isDeliveryStateTerminal(oldDeliveryState)) {
- r.setDeliveryState(index, newDeliveryState, reason);
- if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
- r.deferredCount--;
- } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) {
- // If we're deferring a broadcast, maybe that's enough to unblock the final callback
- r.deferredCount++;
- checkFinished = true;
- }
- }
+ final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason);
// Emit any relevant tracing results when we're changing the delivery
// state as part of running from a queue
@@ -1147,15 +1143,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
+ deliveryStateToString(newDeliveryState) + " because " + reason);
}
- r.terminalCount++;
notifyFinishReceiver(queue, app, r, index, receiver);
- checkFinished = true;
}
- // When entire ordered broadcast finished, deliver final result
- if (checkFinished) {
- final boolean recordFinished =
- ((r.terminalCount + r.deferredCount) == r.receivers.size());
- if (recordFinished) {
+
+ // When we've reached a new high-water mark, we might be in a position
+ // to unblock other receivers or the final resultTo
+ if (beyondCountChanged) {
+ if (r.beyondCount == r.receivers.size()) {
scheduleResultTo(r);
}
@@ -1255,14 +1249,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultExtras = null;
};
- private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> {
+ private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
- "mBroadcastConsumerDefer");
+ "mBroadcastConsumerDeferApply");
};
- private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> {
+ private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
- "mBroadcastConsumerUndoDefer");
+ "mBroadcastConsumerDeferClear");
};
/**
@@ -1278,7 +1272,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final long now = SystemClock.uptimeMillis();
if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) {
mLastTestFailureTime = now;
- pw.println("Test " + label + " failed due to " + leaf.toShortString());
+ pw.println("Test " + label + " failed due to " + leaf.toShortString() + " "
+ + leaf.describeStateLocked());
pw.flush();
}
return false;
@@ -1315,34 +1310,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return didSomething;
}
- private void forEachMatchingQueue(
+ private boolean forEachMatchingQueue(
@NonNull Predicate<BroadcastProcessQueue> queuePredicate,
@NonNull Consumer<BroadcastProcessQueue> queueConsumer) {
+ boolean didSomething = false;
for (int i = mProcessQueues.size() - 1; i >= 0; i--) {
BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
while (leaf != null) {
if (queuePredicate.test(leaf)) {
queueConsumer.accept(leaf);
updateRunnableList(leaf);
+ didSomething = true;
}
leaf = leaf.processNameNext;
}
}
- }
-
- private void updateQueueDeferred(
- @NonNull BroadcastProcessQueue leaf) {
- if (leaf.isDeferredUntilActive()) {
- leaf.forEachMatchingBroadcast((r, i) -> {
- return r.deferUntilActive && (r.getDeliveryState(i)
- == BroadcastRecord.DELIVERY_PENDING);
- }, mBroadcastConsumerDefer, false);
- } else if (leaf.hasDeferredBroadcasts()) {
- leaf.forEachMatchingBroadcast((r, i) -> {
- return r.deferUntilActive && (r.getDeliveryState(i)
- == BroadcastRecord.DELIVERY_DEFERRED);
- }, mBroadcastConsumerUndoDefer, false);
+ if (didSomething) {
+ enqueueUpdateRunningList();
}
+ return didSomething;
}
@Override
@@ -1365,8 +1351,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Update internal state by refreshing values previously
// read from any known running process
setQueueProcess(leaf, leaf.app);
- updateQueueDeferred(leaf);
- updateRunnableList(leaf);
leaf = leaf.processNameNext;
}
enqueueUpdateRunningList();
@@ -1535,19 +1519,31 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
}
+ @SuppressWarnings("CheckResult")
private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
if (!queue.isProcessWarm()) {
- setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid));
+ // This is a bit awkward; we're in the middle of traversing the
+ // runnable queue, so we can't reorder that list if the runnable
+ // time changes here. However, if this process was just found to be
+ // warm via this operation, we're going to immediately promote it to
+ // be running, and any side effect of this operation will then apply
+ // after it's finished and is returned to the runnable list.
+ queue.setProcessAndUidCached(
+ mService.getProcessRecordLocked(queue.processName, queue.uid),
+ mUidCached.get(queue.uid, false));
}
}
/**
* Update the {@link ProcessRecord} associated with the given
- * {@link BroadcastProcessQueue}.
+ * {@link BroadcastProcessQueue}. Also updates any runnable status that
+ * might have changed as a side-effect.
*/
private void setQueueProcess(@NonNull BroadcastProcessQueue queue,
@Nullable ProcessRecord app) {
- queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false));
+ if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) {
+ updateRunnableList(queue);
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index c368290386a0..64fe39314f0e 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -24,6 +24,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY;
+import android.annotation.CheckResult;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.IntDef;
@@ -101,8 +102,7 @@ final class BroadcastRecord extends Binder {
final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo
final @DeliveryState int[] delivery; // delivery state of each receiver
final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver
- final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred
- final int[] blockedUntilTerminalCount; // blocked until count of each receiver
+ final int[] blockedUntilBeyondCount; // blocked until count of each receiver
@Nullable ProcessRecord resultToApp; // who receives final result if non-null
@Nullable IIntentReceiver resultTo; // who receives final result if non-null
boolean deferred;
@@ -134,6 +134,7 @@ final class BroadcastRecord extends Binder {
int manifestSkipCount; // number of manifest receivers skipped.
int terminalCount; // number of receivers in terminal state.
int deferredCount; // number of receivers in deferred state.
+ int beyondCount; // high-water number of receivers we've moved beyond.
@Nullable BroadcastQueue queue; // the outbound queue handling this broadcast
// Determines the privileges the app's process has in regard to background starts.
@@ -219,6 +220,23 @@ final class BroadcastRecord extends Binder {
}
}
+ /**
+ * Return if the given delivery state is "beyond", which means that we've
+ * moved beyond this receiver, and future receivers are now unblocked.
+ */
+ static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) {
+ switch (deliveryState) {
+ case DELIVERY_DELIVERED:
+ case DELIVERY_SKIPPED:
+ case DELIVERY_TIMEOUT:
+ case DELIVERY_FAILURE:
+ case DELIVERY_DEFERRED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
ProcessRecord curApp; // hosting application of current receiver.
ComponentName curComponent; // the receiver class that is currently running.
ActivityInfo curReceiver; // the manifest receiver that is currently running.
@@ -356,7 +374,7 @@ final class BroadcastRecord extends Binder {
TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw);
pw.print(' ');
}
- pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") ");
+ pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") ");
pw.print("#"); pw.print(i); pw.print(": ");
if (o instanceof BroadcastFilter) {
pw.println(o);
@@ -411,8 +429,7 @@ final class BroadcastRecord extends Binder {
urgent = calculateUrgent(_intent, _options);
deferUntilActive = calculateDeferUntilActive(_callingUid,
_options, _resultTo, _serialized, urgent);
- deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0];
- blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
+ blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized);
scheduledTime = new long[delivery.length];
terminalTime = new long[delivery.length];
resultToApp = _resultToApp;
@@ -423,7 +440,7 @@ final class BroadcastRecord extends Binder {
ordered = _serialized;
sticky = _sticky;
initialSticky = _initialSticky;
- prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
+ prioritized = isPrioritized(blockedUntilBeyondCount, _serialized);
userId = _userId;
nextReceiver = 0;
state = IDLE;
@@ -467,8 +484,7 @@ final class BroadcastRecord extends Binder {
delivery = from.delivery;
deliveryReasons = from.deliveryReasons;
deferUntilActive = from.deferUntilActive;
- deferredUntilActive = from.deferredUntilActive;
- blockedUntilTerminalCount = from.blockedUntilTerminalCount;
+ blockedUntilBeyondCount = from.blockedUntilBeyondCount;
scheduledTime = from.scheduledTime;
terminalTime = from.terminalTime;
resultToApp = from.resultToApp;
@@ -627,32 +643,72 @@ final class BroadcastRecord extends Binder {
/**
* Update the delivery state of the given {@link #receivers} index.
* Automatically updates any time measurements related to state changes.
+ *
+ * @return if {@link #beyondCount} changed due to this state transition,
+ * indicating that other events may be unblocked.
*/
- void setDeliveryState(int index, @DeliveryState int deliveryState,
+ @CheckResult
+ boolean setDeliveryState(int index, @DeliveryState int newDeliveryState,
@NonNull String reason) {
- delivery[index] = deliveryState;
- deliveryReasons[index] = reason;
- if (deferUntilActive) deferredUntilActive[index] = false;
- switch (deliveryState) {
- case DELIVERY_DELIVERED:
- case DELIVERY_SKIPPED:
- case DELIVERY_TIMEOUT:
- case DELIVERY_FAILURE:
- terminalTime[index] = SystemClock.uptimeMillis();
+ final int oldDeliveryState = delivery[index];
+ if (isDeliveryStateTerminal(oldDeliveryState)
+ || newDeliveryState == oldDeliveryState) {
+ // We've already arrived in terminal or requested state, so leave
+ // any statistics and reasons intact from the first transition
+ return false;
+ }
+
+ switch (oldDeliveryState) {
+ case DELIVERY_DEFERRED:
+ deferredCount--;
break;
+ }
+ switch (newDeliveryState) {
case DELIVERY_SCHEDULED:
scheduledTime[index] = SystemClock.uptimeMillis();
break;
case DELIVERY_DEFERRED:
- if (deferUntilActive) deferredUntilActive[index] = true;
+ deferredCount++;
+ break;
+ case DELIVERY_DELIVERED:
+ case DELIVERY_SKIPPED:
+ case DELIVERY_TIMEOUT:
+ case DELIVERY_FAILURE:
+ terminalTime[index] = SystemClock.uptimeMillis();
+ terminalCount++;
break;
}
+
+ delivery[index] = newDeliveryState;
+ deliveryReasons[index] = reason;
+
+ // If this state change might bring us to a new high-water mark, bring
+ // ourselves as high as we possibly can
+ final int oldBeyondCount = beyondCount;
+ if (index >= beyondCount) {
+ for (int i = beyondCount; i < delivery.length; i++) {
+ if (isDeliveryStateBeyond(getDeliveryState(i))) {
+ beyondCount = i + 1;
+ } else {
+ break;
+ }
+ }
+ }
+ return (beyondCount != oldBeyondCount);
}
@DeliveryState int getDeliveryState(int index) {
return delivery[index];
}
+ /**
+ * @return if the given {@link #receivers} index should be considered
+ * blocked based on the current status of the overall broadcast.
+ */
+ boolean isBlocked(int index) {
+ return (beyondCount < blockedUntilBeyondCount[index]);
+ }
+
boolean wasDeliveryAttempted(int index) {
final int deliveryState = getDeliveryState(index);
switch (deliveryState) {
@@ -757,36 +813,36 @@ final class BroadcastRecord extends Binder {
* has prioritized tranches of receivers.
*/
@VisibleForTesting
- static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+ static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount,
boolean ordered) {
- return !ordered && (blockedUntilTerminalCount.length > 0)
- && (blockedUntilTerminalCount[0] != -1);
+ return !ordered && (blockedUntilBeyondCount.length > 0)
+ && (blockedUntilBeyondCount[0] != -1);
}
/**
- * Calculate the {@link #terminalCount} that each receiver should be
+ * Calculate the {@link #beyondCount} that each receiver should be
* considered blocked until.
* <p>
* For example, in an ordered broadcast, receiver {@code N} is blocked until
- * receiver {@code N-1} reaches a terminal state. Similarly, in a
- * prioritized broadcast, receiver {@code N} is blocked until all receivers
- * of a higher priority reach a terminal state.
+ * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in
+ * a prioritized broadcast, receiver {@code N} is blocked until all
+ * receivers of a higher priority reach a terminal or deferred state.
* <p>
- * When there are no terminal count constraints, the blocked value for each
+ * When there are no beyond count constraints, the blocked value for each
* receiver is {@code -1}.
*/
@VisibleForTesting
- static @NonNull int[] calculateBlockedUntilTerminalCount(
+ static @NonNull int[] calculateBlockedUntilBeyondCount(
@NonNull List<Object> receivers, boolean ordered) {
final int N = receivers.size();
- final int[] blockedUntilTerminalCount = new int[N];
+ final int[] blockedUntilBeyondCount = new int[N];
int lastPriority = 0;
int lastPriorityIndex = 0;
for (int i = 0; i < N; i++) {
if (ordered) {
// When sending an ordered broadcast, we need to block this
// receiver until all previous receivers have terminated
- blockedUntilTerminalCount[i] = i;
+ blockedUntilBeyondCount[i] = i;
} else {
// When sending a prioritized broadcast, we only need to wait
// for the previous tranche of receivers to be terminated
@@ -794,18 +850,18 @@ final class BroadcastRecord extends Binder {
if ((i == 0) || (thisPriority != lastPriority)) {
lastPriority = thisPriority;
lastPriorityIndex = i;
- blockedUntilTerminalCount[i] = i;
+ blockedUntilBeyondCount[i] = i;
} else {
- blockedUntilTerminalCount[i] = lastPriorityIndex;
+ blockedUntilBeyondCount[i] = lastPriorityIndex;
}
}
}
// If the entire list is in the same priority tranche, mark as -1 to
// indicate that none of them need to wait
- if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
- Arrays.fill(blockedUntilTerminalCount, -1);
+ if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) {
+ Arrays.fill(blockedUntilBeyondCount, -1);
}
- return blockedUntilTerminalCount;
+ return blockedUntilBeyondCount;
}
static int getReceiverUid(@NonNull Object receiver) {
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 78edbba2e569..1426cfd65286 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -18,6 +18,28 @@ package com.android.server.am;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN;
import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION;
@@ -26,6 +48,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import android.annotation.IntDef;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.ApplicationExitInfo;
import android.app.IApplicationThread;
@@ -139,6 +162,26 @@ public final class CachedAppOptimizer {
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS;
static final int UNFREEZE_REASON_FEATURE_FLAGS =
FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS;
+ static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT;
+ static final int UNFREEZE_REASON_SYSTEM_INIT =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT;
+ static final int UNFREEZE_REASON_BACKUP =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP;
+ static final int UNFREEZE_REASON_SHELL =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL;
+ static final int UNFREEZE_REASON_REMOVE_TASK =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK;
+ static final int UNFREEZE_REASON_UID_IDLE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE;
+ static final int UNFREEZE_REASON_STOP_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE;
+ static final int UNFREEZE_REASON_EXECUTING_SERVICE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE;
+ static final int UNFREEZE_REASON_RESTRICTION_CHANGE =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE;
+ static final int UNFREEZE_REASON_COMPONENT_DISABLED =
+ FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED;
@IntDef(prefix = {"UNFREEZE_REASON_"}, value = {
UNFREEZE_REASON_NONE,
@@ -160,6 +203,16 @@ public final class CachedAppOptimizer {
UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE,
UNFREEZE_REASON_BINDER_TXNS,
UNFREEZE_REASON_FEATURE_FLAGS,
+ UNFREEZE_REASON_SHORT_FGS_TIMEOUT,
+ UNFREEZE_REASON_SYSTEM_INIT,
+ UNFREEZE_REASON_BACKUP,
+ UNFREEZE_REASON_SHELL,
+ UNFREEZE_REASON_REMOVE_TASK,
+ UNFREEZE_REASON_UID_IDLE,
+ UNFREEZE_REASON_STOP_SERVICE,
+ UNFREEZE_REASON_EXECUTING_SERVICE,
+ UNFREEZE_REASON_RESTRICTION_CHANGE,
+ UNFREEZE_REASON_COMPONENT_DISABLED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface UnfreezeReason {}
@@ -178,6 +231,7 @@ public final class CachedAppOptimizer {
private static final String ATRACE_FREEZER_TRACK = "Freezer";
private static final int FREEZE_BINDER_TIMEOUT_MS = 100;
+ private static final int FREEZE_DEADLOCK_TIMEOUT_MS = 1000;
@VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false;
@@ -244,6 +298,7 @@ public final class CachedAppOptimizer {
static final int REPORT_UNFREEZE_MSG = 4;
static final int COMPACT_NATIVE_MSG = 5;
static final int UID_FROZEN_STATE_CHANGED_MSG = 6;
+ static final int DEADLOCK_WATCHDOG_MSG = 7;
// When free swap falls below this percentage threshold any full (file + anon)
// compactions will be downgraded to file only compactions to reduce pressure
@@ -287,7 +342,7 @@ public final class CachedAppOptimizer {
private final ActivityManagerGlobalLock mProcLock;
- private final Object mFreezerLock = new Object();
+ public final Object mFreezerLock = new Object();
private final OnPropertiesChangedListener mOnFlagsChangedListener =
new OnPropertiesChangedListener() {
@@ -708,8 +763,9 @@ public final class CachedAppOptimizer {
pw.println(" Apps frozen: " + size);
for (int i = 0; i < size; i++) {
ProcessRecord app = mFrozenProcesses.valueAt(i);
- pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime()
- + ": " + app.getPid() + " " + app.processName);
+ pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid()
+ + " " + app.processName
+ + (app.mOptRecord.isFreezeSticky() ? " (sticky)" : ""));
}
if (!mPendingCompactionProcesses.isEmpty()) {
@@ -1228,12 +1284,26 @@ public final class CachedAppOptimizer {
@GuardedBy({"mAm", "mProcLock"})
void freezeAppAsyncLSP(ProcessRecord app) {
+ freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false);
+ }
+
+ @GuardedBy({"mAm", "mProcLock"})
+ void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) {
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
if (opt.isPendingFreeze()) {
// Skip redundant DO_FREEZE message
return;
}
+ if (opt.isFreezeSticky() && !force) {
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM,
+ "Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " "
+ + app.processName);
+ }
+ return;
+ }
+
if (mAm.mConstants.USE_MODERN_TRIM
&& app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) {
final IApplicationThread thread = app.getThread();
@@ -1246,9 +1316,8 @@ public final class CachedAppOptimizer {
}
}
mFreezeHandler.sendMessageDelayed(
- mFreezeHandler.obtainMessage(
- SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
- mFreezerDebounceTimeout);
+ mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
+ delayMillis);
opt.setPendingFreeze(true);
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName);
@@ -1256,9 +1325,19 @@ public final class CachedAppOptimizer {
}
@GuardedBy({"mAm", "mProcLock", "mFreezerLock"})
- void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason) {
+ void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) {
final int pid = app.getPid();
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
+ boolean sticky = opt.isFreezeSticky();
+ if (sticky && !force) {
+ // Sticky freezes will not change their state unless forced out of it.
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM,
+ "Skip unfreezing because frozen state is sticky pid=" + pid + " "
+ + app.processName);
+ }
+ return;
+ }
if (opt.isPendingFreeze()) {
// Remove pending DO_FREEZE message
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
@@ -1271,8 +1350,7 @@ public final class CachedAppOptimizer {
UidRecord uidRec = app.getUidRecord();
if (uidRec != null && uidRec.isFrozen()) {
uidRec.setFrozen(false);
- mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
- reportOneUidFrozenStateChanged(app.uid, false);
+ postUidFrozenMessage(uidRec.getUid(), false);
}
opt.setFreezerOverride(false);
@@ -1327,7 +1405,7 @@ public final class CachedAppOptimizer {
}
try {
- traceAppFreeze(app.processName, pid, false);
+ traceAppFreeze(app.processName, pid, reason);
Process.setProcessFrozen(pid, app.uid, false);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -1339,7 +1417,7 @@ public final class CachedAppOptimizer {
}
if (!opt.isFrozen()) {
- Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName);
+ Slog.d(TAG_AM, "sync unfroze " + pid + " " + app.processName + " for " + reason);
mFreezeHandler.sendMessage(
mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
@@ -1352,7 +1430,7 @@ public final class CachedAppOptimizer {
@GuardedBy({"mAm", "mProcLock"})
void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) {
synchronized (mFreezerLock) {
- unfreezeAppInternalLSP(app, reason);
+ unfreezeAppInternalLSP(app, reason, false);
}
}
@@ -1363,13 +1441,13 @@ public final class CachedAppOptimizer {
* The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app.
* @param pid pid of the process to be unfrozen
*/
- void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeProcess(int pid, @OomAdjReason int reason) {
synchronized (mFreezerLock) {
ProcessRecord app = mFrozenProcesses.get(pid);
if (app == null) {
return;
}
- Slog.d(TAG_AM, "quick sync unfreeze " + pid);
+ Slog.d(TAG_AM, "quick sync unfreeze " + pid + " for " + reason);
try {
freezeBinder(pid, false, FREEZE_BINDER_TIMEOUT_MS);
} catch (RuntimeException e) {
@@ -1378,7 +1456,7 @@ public final class CachedAppOptimizer {
}
try {
- traceAppFreeze(app.processName, pid, false);
+ traceAppFreeze(app.processName, pid, reason);
Process.setProcessFrozen(pid, app.uid, false);
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to quick unfreeze " + pid);
@@ -1386,9 +1464,15 @@ public final class CachedAppOptimizer {
}
}
- private static void traceAppFreeze(String processName, int pid, boolean freeze) {
+ /**
+ * Trace app freeze status
+ * @param processName The name of the target process
+ * @param pid The pid of the target process
+ * @param reason UNFREEZE_REASON_XXX (>=0) for unfreezing and -1 for freezing
+ */
+ private static void traceAppFreeze(String processName, int pid, int reason) {
Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_FREEZER_TRACK,
- (freeze ? "Freeze " : "Unfreeze ") + processName + ":" + pid);
+ (reason < 0 ? "Freeze " : "Unfreeze ") + processName + ":" + pid + " " + reason);
}
/**
@@ -1407,8 +1491,7 @@ public final class CachedAppOptimizer {
UidRecord uidRec = app.getUidRecord();
if (uidRec != null && uidRec.isFrozen()) {
uidRec.setFrozen(false);
- mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app);
- reportOneUidFrozenStateChanged(app.uid, false);
+ postUidFrozenMessage(uidRec.getUid(), false);
}
mFrozenProcesses.delete(app.getPid());
@@ -1538,12 +1621,12 @@ public final class CachedAppOptimizer {
public long mOrigAnonRss;
public int mProcState;
public int mOomAdj;
- public @OomAdjuster.OomAdjReason int mOomAdjReason;
+ public @OomAdjReason int mOomAdjReason;
SingleCompactionStats(long[] rss, CompactSource source, String processName,
long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss,
long cpuTimeMillis, int procState, int oomAdj,
- @OomAdjuster.OomAdjReason int oomAdjReason, int uid) {
+ @OomAdjReason int oomAdjReason, int uid) {
mRssAfterCompaction = rss;
mSourceType = source;
mProcessName = processName;
@@ -1937,6 +2020,15 @@ public final class CachedAppOptimizer {
mAm.reportUidFrozenStateChanged(uids, frozenStates);
}
+ private void postUidFrozenMessage(int uid, boolean frozen) {
+ final Integer uidObj = Integer.valueOf(uid);
+ mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj);
+
+ final int op = frozen ? 1 : 0;
+ mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op,
+ 0, uidObj));
+ }
+
private final class FreezeHandler extends Handler implements
ProcLocksReader.ProcLocksReaderCallback {
private FreezeHandler() {
@@ -1947,29 +2039,15 @@ public final class CachedAppOptimizer {
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_FROZEN_PROCESS_MSG:
- {
ProcessRecord proc = (ProcessRecord) msg.obj;
- int pid = proc.getPid();
- final String name = proc.processName;
synchronized (mAm) {
freezeProcess(proc);
}
- try {
- // post-check to prevent deadlock
- mProcLocksReader.handleBlockingFileLocks(this);
- } catch (Exception e) {
- Slog.e(TAG_AM, "Unable to check file locks for "
- + name + "(" + pid + "): " + e);
- synchronized (mAm) {
- synchronized (mProcLock) {
- unfreezeAppLSP(proc, UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE);
- }
- }
- }
if (proc.mOptRecord.isFrozen()) {
onProcessFrozen(proc);
+ removeMessages(DEADLOCK_WATCHDOG_MSG);
+ sendEmptyMessageDelayed(DEADLOCK_WATCHDOG_MSG, FREEZE_DEADLOCK_TIMEOUT_MS);
}
- }
break;
case REPORT_UNFREEZE_MSG:
int pid = msg.arg1;
@@ -1981,8 +2059,20 @@ public final class CachedAppOptimizer {
reportUnfreeze(pid, frozenDuration, processName, reason);
break;
case UID_FROZEN_STATE_CHANGED_MSG:
- ProcessRecord proc = (ProcessRecord) msg.obj;
- reportOneUidFrozenStateChanged(proc.uid, true);
+ final boolean frozen = (msg.arg1 == 1);
+ final int uid = (int) msg.obj;
+ reportOneUidFrozenStateChanged(uid, frozen);
+ break;
+ case DEADLOCK_WATCHDOG_MSG:
+ try {
+ // post-check to prevent deadlock
+ if (DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "Freezer deadlock watchdog");
+ }
+ mProcLocksReader.handleBlockingFileLocks(this);
+ } catch (IOException e) {
+ Slog.w(TAG_AM, "Unable to check file locks");
+ }
break;
default:
return;
@@ -2014,15 +2104,6 @@ public final class CachedAppOptimizer {
synchronized (mProcLock) {
pid = proc.getPid();
- if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ
- || opt.shouldNotFreeze()) {
- if (DEBUG_FREEZER) {
- Slog.d(TAG_AM, "Skipping freeze for process " + pid
- + " " + name + " curAdj = " + proc.mState.getCurAdj()
- + ", shouldNotFreeze = " + opt.shouldNotFreeze());
- }
- return;
- }
if (mFreezerOverride) {
opt.setFreezerOverride(true);
@@ -2065,7 +2146,7 @@ public final class CachedAppOptimizer {
long unfreezeTime = opt.getFreezeUnfreezeTime();
try {
- traceAppFreeze(proc.processName, pid, true);
+ traceAppFreeze(proc.processName, pid, -1);
Process.setProcessFrozen(pid, proc.uid, true);
opt.setFreezeUnfreezeTime(SystemClock.uptimeMillis());
@@ -2082,8 +2163,8 @@ public final class CachedAppOptimizer {
final UidRecord uidRec = proc.getUidRecord();
if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) {
uidRec.setFrozen(true);
- mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(
- UID_FROZEN_STATE_CHANGED_MSG, proc));
+
+ postUidFrozenMessage(uidRec.getUid(), true);
}
}
@@ -2129,7 +2210,7 @@ public final class CachedAppOptimizer {
private void reportUnfreeze(int pid, int frozenDuration, String processName,
@UnfreezeReason int reason) {
- EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName);
+ EventLog.writeEvent(EventLogTags.AM_UNFREEZE, pid, processName, reason);
// See above for why we're not taking mPhenotypeFlagLock here
if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
@@ -2203,32 +2284,52 @@ public final class CachedAppOptimizer {
}
}
- static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) {
+ static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) {
switch (oomAdjReason) {
- case OomAdjuster.OOM_ADJ_REASON_ACTIVITY:
+ case OOM_ADJ_REASON_ACTIVITY:
return UNFREEZE_REASON_ACTIVITY;
- case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER:
+ case OOM_ADJ_REASON_FINISH_RECEIVER:
return UNFREEZE_REASON_FINISH_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER:
+ case OOM_ADJ_REASON_START_RECEIVER:
return UNFREEZE_REASON_START_RECEIVER;
- case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE:
+ case OOM_ADJ_REASON_BIND_SERVICE:
return UNFREEZE_REASON_BIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE:
+ case OOM_ADJ_REASON_UNBIND_SERVICE:
return UNFREEZE_REASON_UNBIND_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_START_SERVICE:
+ case OOM_ADJ_REASON_START_SERVICE:
return UNFREEZE_REASON_START_SERVICE;
- case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER:
+ case OOM_ADJ_REASON_GET_PROVIDER:
return UNFREEZE_REASON_GET_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER:
+ case OOM_ADJ_REASON_REMOVE_PROVIDER:
return UNFREEZE_REASON_REMOVE_PROVIDER;
- case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY:
+ case OOM_ADJ_REASON_UI_VISIBILITY:
return UNFREEZE_REASON_UI_VISIBILITY;
- case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST:
+ case OOM_ADJ_REASON_ALLOWLIST:
return UNFREEZE_REASON_ALLOWLIST;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN:
+ case OOM_ADJ_REASON_PROCESS_BEGIN:
return UNFREEZE_REASON_PROCESS_BEGIN;
- case OomAdjuster.OOM_ADJ_REASON_PROCESS_END:
+ case OOM_ADJ_REASON_PROCESS_END:
return UNFREEZE_REASON_PROCESS_END;
+ case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+ return UNFREEZE_REASON_SHORT_FGS_TIMEOUT;
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return UNFREEZE_REASON_SYSTEM_INIT;
+ case OOM_ADJ_REASON_BACKUP:
+ return UNFREEZE_REASON_BACKUP;
+ case OOM_ADJ_REASON_SHELL:
+ return UNFREEZE_REASON_SHELL;
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return UNFREEZE_REASON_REMOVE_TASK;
+ case OOM_ADJ_REASON_UID_IDLE:
+ return UNFREEZE_REASON_UID_IDLE;
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return UNFREEZE_REASON_STOP_SERVICE;
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return UNFREEZE_REASON_EXECUTING_SERVICE;
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return UNFREEZE_REASON_RESTRICTION_CHANGE;
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return UNFREEZE_REASON_COMPONENT_DISABLED;
default:
return UNFREEZE_REASON_NONE;
}
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index a1fcd424f8c1..d8cb094caa65 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -16,6 +16,8 @@
package com.android.server.am;
import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile;
import static android.os.Process.PROC_CHAR;
import static android.os.Process.PROC_OUT_LONG;
@@ -292,7 +294,7 @@ public class ContentProviderHelper {
checkTime(startTime, "getContentProviderImpl: before updateOomAdj");
final int verifiedAdj = cpr.proc.mState.getVerifiedAdj();
boolean success = mService.updateOomAdjLocked(cpr.proc,
- OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+ OOM_ADJ_REASON_GET_PROVIDER);
// XXX things have changed so updateOomAdjLocked doesn't actually tell us
// if the process has been successfully adjusted. So to reduce races with
// it, we will check whether the process still exists. Note that this doesn't
@@ -757,7 +759,7 @@ public class ContentProviderHelper {
// update the app's oom adj value and each provider's usage stats
if (providersPublished) {
- mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER);
+ mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER);
for (int i = 0, size = providers.size(); i < size; i++) {
ContentProviderHolder src = providers.get(i);
if (src == null || src.info == null || src.provider == null) {
@@ -835,8 +837,7 @@ public class ContentProviderHelper {
ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
if (localCpr.hasExternalProcessHandles()) {
if (localCpr.removeExternalProcessHandleLocked(token)) {
- mService.updateOomAdjLocked(localCpr.proc,
- OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+ mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
} else {
Slog.e(TAG, "Attempt to remove content provider " + localCpr
+ " with no external reference for token: " + token + ".");
@@ -1506,8 +1507,7 @@ public class ContentProviderHelper {
mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid,
cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
if (updateOomAdj) {
- mService.updateOomAdjLocked(conn.provider.proc,
- OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER);
+ mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER);
}
}
}
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index 9ff2cd0649d4..727d4df96c47 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -22,7 +22,7 @@ import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
/** Rate limiter for adding errors into dropbox. */
public class DropboxRateLimiter {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index 81b242155bac..9e9db6aff699 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -121,10 +121,11 @@ option java_package com.android.server.am
# Similarly, tags below are used by UserManagerService
30091 um_user_visibility_changed (userId|1|5),(visible|1)
-# Foreground service start/stop events.
+# Foreground service start/stop/denied/timed_out events.
30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
30101 am_foreground_service_denied (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
30102 am_foreground_service_stop (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
+30103 am_foreground_service_timed_out (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3),(fgsType|1)
# Intent Sender redirect for UserHandle.USER_CURRENT
30110 am_intent_sender_redirect_user (userId|1|5)
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index a98571b68067..365dcd9bd785 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -41,6 +41,29 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI;
import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE;
import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION;
@@ -101,9 +124,9 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ;
import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityManagerInternal.OomAdjReason;
import android.app.ActivityThread;
import android.app.AppProtoEnums;
import android.app.ApplicationExitInfo;
@@ -141,8 +164,6 @@ import com.android.server.wm.ActivityServiceConnectionsHolder;
import com.android.server.wm.WindowProcessController;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@@ -154,32 +175,6 @@ import java.util.List;
public class OomAdjuster {
static final String TAG = "OomAdjuster";
- static final int OOM_ADJ_REASON_NONE = 0;
- static final int OOM_ADJ_REASON_ACTIVITY = 1;
- static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2;
- static final int OOM_ADJ_REASON_START_RECEIVER = 3;
- static final int OOM_ADJ_REASON_BIND_SERVICE = 4;
- static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5;
- static final int OOM_ADJ_REASON_START_SERVICE = 6;
- static final int OOM_ADJ_REASON_GET_PROVIDER = 7;
- static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8;
- static final int OOM_ADJ_REASON_UI_VISIBILITY = 9;
- static final int OOM_ADJ_REASON_ALLOWLIST = 10;
- static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
- static final int OOM_ADJ_REASON_PROCESS_END = 12;
- static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
-
- @IntDef(prefix = {"OOM_ADJ_REASON_"},
- value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
- OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE,
- OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
- OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
- OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
- OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
- OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface OomAdjReason {}
-
public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) {
switch (oomReason) {
case OOM_ADJ_REASON_NONE:
@@ -210,6 +205,24 @@ public class OomAdjuster {
return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT;
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT;
+ case OOM_ADJ_REASON_BACKUP:
+ return AppProtoEnums.OOM_ADJ_REASON_BACKUP;
+ case OOM_ADJ_REASON_SHELL:
+ return AppProtoEnums.OOM_ADJ_REASON_SHELL;
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK;
+ case OOM_ADJ_REASON_UID_IDLE:
+ return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE;
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE;
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE;
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE;
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED;
default:
return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
}
@@ -246,6 +259,24 @@ public class OomAdjuster {
return OOM_ADJ_REASON_METHOD + "_processEnd";
case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
return OOM_ADJ_REASON_METHOD + "_shortFgs";
+ case OOM_ADJ_REASON_SYSTEM_INIT:
+ return OOM_ADJ_REASON_METHOD + "_systemInit";
+ case OOM_ADJ_REASON_BACKUP:
+ return OOM_ADJ_REASON_METHOD + "_backup";
+ case OOM_ADJ_REASON_SHELL:
+ return OOM_ADJ_REASON_METHOD + "_shell";
+ case OOM_ADJ_REASON_REMOVE_TASK:
+ return OOM_ADJ_REASON_METHOD + "_removeTask";
+ case OOM_ADJ_REASON_UID_IDLE:
+ return OOM_ADJ_REASON_METHOD + "_uidIdle";
+ case OOM_ADJ_REASON_STOP_SERVICE:
+ return OOM_ADJ_REASON_METHOD + "_stopService";
+ case OOM_ADJ_REASON_EXECUTING_SERVICE:
+ return OOM_ADJ_REASON_METHOD + "_executingService";
+ case OOM_ADJ_REASON_RESTRICTION_CHANGE:
+ return OOM_ADJ_REASON_METHOD + "_restrictionChange";
+ case OOM_ADJ_REASON_COMPONENT_DISABLED:
+ return OOM_ADJ_REASON_METHOD + "_componentDisabled";
default:
return "_unknown";
}
@@ -874,8 +905,7 @@ public class OomAdjuster {
}
@GuardedBy("mService")
- private void performUpdateOomAdjPendingTargetsLocked(
- @OomAdjuster.OomAdjReason int oomAdjReason) {
+ private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) {
final ProcessRecord topApp = mService.getTopApp();
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason));
@@ -3453,7 +3483,7 @@ public class OomAdjuster {
}
@GuardedBy("mService")
- void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) {
+ void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) {
if (!mCachedAppOptimizer.useFreezer()) {
return;
}
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 24cc5337b86f..e8c8f6dd5462 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.app.ActivityManagerInternal.OomAdjReason;
+
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -51,7 +53,7 @@ final class ProcessCachedOptimizerRecord {
/**
* Last oom adjust change reason for this app.
*/
- @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason;
+ @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason;
/**
* The most recent compaction action performed for this app.
@@ -73,6 +75,15 @@ final class ProcessCachedOptimizerRecord {
private boolean mFrozen;
/**
+ * If set to true it will make the (un)freeze decision sticky which means that the freezer
+ * decision will remain the same unless a freeze is forced via {@link #mForceFreezeOps}.
+ * This property is usually set to true when external user wants to maintain a (un)frozen state
+ * after being applied.
+ */
+ @GuardedBy("mProcLock")
+ private boolean mFreezeSticky;
+
+ /**
* Set to false after the process has been frozen.
* Set to true after we have collected PSS for the frozen process.
*/
@@ -139,12 +150,12 @@ final class ProcessCachedOptimizerRecord {
}
@GuardedBy("mProcLock")
- void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) {
+ void setLastOomAdjChangeReason(@OomAdjReason int reason) {
mLastOomAdjChangeReason = reason;
}
@GuardedBy("mProcLock")
- @OomAdjuster.OomAdjReason
+ @OomAdjReason
int getLastOomAdjChangeReason() {
return mLastOomAdjChangeReason;
}
@@ -193,6 +204,15 @@ final class ProcessCachedOptimizerRecord {
void setFrozen(boolean frozen) {
mFrozen = frozen;
}
+ @GuardedBy("mProcLock")
+ void setFreezeSticky(boolean sticky) {
+ mFreezeSticky = sticky;
+ }
+
+ @GuardedBy("mProcLock")
+ boolean isFreezeSticky() {
+ return mFreezeSticky;
+ }
boolean skipPSSCollectionBecauseFrozen() {
boolean collected = mHasCollectedFrozenPSS;
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 70a696c72a9d..ca41f429a0de 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -51,7 +51,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.expresslog.Counter;
+import com.android.modules.expresslog.Counter;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TimeoutRecord;
import com.android.internal.os.anr.AnrLatencyTracker;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b1322ef510d5..312f98ad6e09 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -19,6 +19,8 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode;
@@ -2875,7 +2877,7 @@ public final class ProcessList {
reasonCode, subReason, reason, !doFreeze /* async */);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
- mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END);
+ mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
if (doFreeze) {
freezePackageCgroup(packageUID, false);
}
@@ -5140,7 +5142,7 @@ public final class ProcessList {
}
});
/* Will be a no-op if nothing pending */
- mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE);
}
}
@@ -5203,6 +5205,17 @@ public final class ProcessList {
}
/**
+ * Called by ActivityManagerService when a recoverable native crash occurs.
+ */
+ @GuardedBy("mService")
+ void noteAppRecoverableCrash(final ProcessRecord app) {
+ if (DEBUG_PROCESSES) {
+ Slog.i(TAG, "note: " + app + " has a recoverable native crash");
+ }
+ mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app);
+ }
+
+ /**
* Called by ActivityManagerService when it decides to kill an application process.
*/
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index afae623cd217..ffb40ee959a4 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -577,7 +579,9 @@ class ProcessRecord implements WindowProcessListener {
processName = _processName;
sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage;
if (isSdkSandbox) {
- sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid;
+ final ApplicationInfo clientInfo = getClientInfoForSdkSandbox();
+ sdkSandboxClientAppVolumeUuid = clientInfo != null
+ ? clientInfo.volumeUuid : null;
} else {
sdkSandboxClientAppVolumeUuid = null;
}
@@ -1450,7 +1454,7 @@ class ProcessRecord implements WindowProcessListener {
}
mService.updateLruProcessLocked(this, activityChange, null /* client */);
if (updateOomAdj) {
- mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY);
+ mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY);
}
}
}
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index 71d5d39525b4..ab71acd5f21d 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY;
@@ -613,7 +614,7 @@ final class ProcessStateRecord {
void forceProcessStateUpTo(int newState) {
if (mRepProcState > newState) {
synchronized (mProcLock) {
- mRepProcState = newState;
+ setReportedProcState(newState);
setCurProcState(newState);
setCurRawProcState(newState);
}
@@ -766,7 +767,7 @@ final class ProcessStateRecord {
Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation
+ " for pid=" + mApp.getPid());
}
- mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY);
+ mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY);
}
@GuardedBy({"mService", "mProcLock"})
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index edf0dbd65ef2..a875860f016f 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -174,6 +174,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
// allow while-in-use permissions in foreground service or not.
// while-in-use permissions in FGS started from background might be restricted.
boolean mAllowWhileInUsePermissionInFgs;
+ @PowerExemptionManager.ReasonCode
+ int mAllowWhileInUsePermissionInFgsReason;
// A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state.
boolean mAllowWhileInUsePermissionInFgsAtEntering;
/** Allow scheduling user-initiated jobs from the background. */
@@ -609,6 +611,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs=");
pw.println(mAllowWhileInUsePermissionInFgs);
+ pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason=");
+ pw.println(mAllowWhileInUsePermissionInFgsReason);
pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling);
pw.print(prefix); pw.print("recentCallingPackage=");
pw.println(mRecentCallingPackage);
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 60a7f9371837..8c227f5488d3 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -83,6 +83,7 @@ public class SettingsToPropertiesMapper {
DeviceConfig.NAMESPACE_CAMERA_NATIVE,
DeviceConfig.NAMESPACE_CONFIGURATION,
DeviceConfig.NAMESPACE_CONNECTIVITY,
+ DeviceConfig.NAMESPACE_EDGETPU_NATIVE,
DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
DeviceConfig.NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS,
DeviceConfig.NAMESPACE_LMKD_NATIVE,
@@ -99,7 +100,6 @@ public class SettingsToPropertiesMapper {
DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT,
DeviceConfig.NAMESPACE_SWCODEC_NATIVE,
- DeviceConfig.NAMESPACE_TETHERING,
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE,
DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT,
DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE,
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 81ba4b813de1..a110169ac8c2 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -34,6 +34,7 @@ import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
import static android.app.AppOpsManager.OP_FLAG_SELF;
import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
@@ -42,6 +43,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO;
import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
import static android.app.AppOpsManager.OP_VIBRATE;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED;
import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED;
@@ -3027,17 +3029,29 @@ public class AppOpsService extends IAppOpsService.Stub {
packageName);
}
- // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution
- // purposes and not as a check, also make sure that the caller is allowed to access
- // the data gated by OP_RECORD_AUDIO.
+ // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and
+ // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check,
+ // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO.
//
// TODO: Revert this change before Android 12.
- if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) {
- int result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ int result = MODE_DEFAULT;
+ if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO
+ || code == OP_RECORD_AUDIO_SANDBOXED) {
+ result = checkOperation(OP_RECORD_AUDIO, uid, packageName);
+ // Check result
if (result != AppOpsManager.MODE_ALLOWED) {
return new SyncNotedAppOp(result, code, attributionTag, packageName);
}
}
+ // As a special case for OP_CAMERA_SANDBOXED.
+ if (code == OP_CAMERA_SANDBOXED) {
+ result = checkOperation(OP_CAMERA, uid, packageName);
+ // Check result
+ if (result != AppOpsManager.MODE_ALLOWED) {
+ return new SyncNotedAppOp(result, code, attributionTag, packageName);
+ }
+ }
+
return startOperationUnchecked(clientId, code, uid, packageName, attributionTag,
Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault,
shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags,
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ac55f28a8ab0..d1cbbfcba2e2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7053,13 +7053,16 @@ public class AudioService extends IAudioService.Stub
return deviceSet.iterator().next();
} else {
// Multiple device selection is either:
+ // - dock + one other device: give priority to dock in this case.
// - speaker + one other device: give priority to speaker in this case.
// - one A2DP device + another device: happens with duplicated output. In this case
// retain the device on the A2DP output as the other must not correspond to an active
// selection if not the speaker.
// - HDMI-CEC system audio mode only output: give priority to available item in order.
- if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
+ if (deviceSet.contains(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET)) {
+ return AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET;
+ } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) {
return AudioSystem.DEVICE_OUT_SPEAKER;
} else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER_SAFE)) {
// Note: DEVICE_OUT_SPEAKER_SAFE not present in getDeviceSetForStreamDirect
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index 7c8e6df4acdc..5127d26e6e73 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -19,6 +19,8 @@ package com.android.server.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR;
+import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE;
@@ -519,6 +521,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
try {
mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message);
+ final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR
+ ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo;
+ mClientReceiver.onAcquired(aAcquiredInfo, message);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 937e3f8f8668..bac44809883f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -22,14 +22,20 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricConstants;
+import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
/**
* Wraps IBiometricAuthenticator implementation and stores information about the authenticator,
@@ -67,6 +73,7 @@ public abstract class BiometricSensor {
public final int id;
public final @Authenticators.Types int oemStrength; // strength as configured by the OEM
public final int modality;
+ @NonNull public final List<ComponentInfoInternal> componentInfo;
public final IBiometricAuthenticator impl;
private @Authenticators.Types int mUpdatedStrength; // updated by BiometricStrengthController
@@ -86,15 +93,16 @@ public abstract class BiometricSensor {
*/
abstract boolean confirmationSupported();
- BiometricSensor(@NonNull Context context, int id, int modality,
- @Authenticators.Types int strength, IBiometricAuthenticator impl) {
+ BiometricSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props,
+ IBiometricAuthenticator impl) {
this.mContext = context;
- this.id = id;
+ this.id = props.sensorId;
this.modality = modality;
- this.oemStrength = strength;
+ this.oemStrength = Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+ this.componentInfo = Collections.unmodifiableList(props.componentInfo);
this.impl = impl;
- mUpdatedStrength = strength;
+ mUpdatedStrength = oemStrength;
goToStateUnknown();
}
@@ -178,8 +186,25 @@ public abstract class BiometricSensor {
return "ID(" + id + ")"
+ ", oemStrength: " + oemStrength
+ ", updatedStrength: " + mUpdatedStrength
- + ", modality " + modality
+ + ", modality: " + modality
+ ", state: " + mSensorState
+ ", cookie: " + mCookie;
}
+
+ protected void dump(@NonNull IndentingPrintWriter pw) {
+ pw.println(TextUtils.formatSimple("ID: %d", id));
+ pw.increaseIndent();
+ pw.println(TextUtils.formatSimple("oemStrength: %d", oemStrength));
+ pw.println(TextUtils.formatSimple("updatedStrength: %d", mUpdatedStrength));
+ pw.println(TextUtils.formatSimple("modality: %d", modality));
+ pw.println("componentInfo:");
+ for (ComponentInfoInternal info : componentInfo) {
+ pw.increaseIndent();
+ info.dump(pw);
+ pw.decreaseIndent();
+ }
+ pw.println(TextUtils.formatSimple("state: %d", mSensorState));
+ pw.println(TextUtils.formatSimple("cookie: %d", mCookie));
+ pw.decreaseIndent();
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index ffa5d2055e92..f44d14bfa12c 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -62,6 +62,7 @@ import android.provider.Settings;
import android.security.KeyStore;
import android.text.TextUtils;
import android.util.ArraySet;
+import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -638,13 +639,16 @@ public class BiometricService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
@Override
- public synchronized void registerAuthenticator(int id, int modality,
- @Authenticators.Types int strength,
+ public synchronized void registerAuthenticator(int modality,
+ @NonNull SensorPropertiesInternal props,
@NonNull IBiometricAuthenticator authenticator) {
super.registerAuthenticator_enforcePermission();
- Slog.d(TAG, "Registering ID: " + id
+ @Authenticators.Types final int strength =
+ Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
+
+ Slog.d(TAG, "Registering ID: " + props.sensorId
+ " Modality: " + modality
+ " Strength: " + strength);
@@ -665,12 +669,12 @@ public class BiometricService extends SystemService {
}
for (BiometricSensor sensor : mSensors) {
- if (sensor.id == id) {
+ if (sensor.id == props.sensorId) {
throw new IllegalStateException("Cannot register duplicate authenticator");
}
}
- mSensors.add(new BiometricSensor(getContext(), id, modality, strength, authenticator) {
+ mSensors.add(new BiometricSensor(getContext(), modality, props, authenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
return mSettingObserver.getConfirmationAlwaysRequired(modality, userId);
@@ -1360,13 +1364,17 @@ public class BiometricService extends SystemService {
return null;
}
- private void dumpInternal(PrintWriter pw) {
+ private void dumpInternal(PrintWriter printWriter) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
+
pw.println("Legacy Settings: " + mSettingObserver.mUseLegacyFaceOnlySettings);
pw.println();
pw.println("Sensors:");
for (BiometricSensor sensor : mSensors) {
- pw.println(" " + sensor);
+ pw.increaseIndent();
+ sensor.dump(pw);
+ pw.decreaseIndent();
}
pw.println();
pw.println("CurrentSession: " + mAuthSession);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
index 0f0a81d24473..d43045b4450f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceServiceRegistry.java
@@ -20,7 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
@@ -28,7 +27,6 @@ import android.hardware.face.IFaceService;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BiometricServiceRegistry;
import java.util.List;
@@ -53,10 +51,8 @@ public class FaceServiceRegistry extends BiometricServiceRegistry<ServiceProvide
@Override
protected void registerService(@NonNull IBiometricService service,
@NonNull FaceSensorPropertiesInternal props) {
- @BiometricManager.Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
try {
- service.registerAuthenticator(props.sensorId, TYPE_FACE, strength,
+ service.registerAuthenticator(TYPE_FACE, props,
new FaceAuthenticator(mService, props.sensorId));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
index 7ae31b2a114d..50d375c56f4a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java
@@ -212,6 +212,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
// 1) Authenticated == true
// 2) Error occurred
// 3) Authenticated == false
+ // 4) onLockout
+ // 5) onLockoutTimed
mCallback.onClientFinished(this, true /* success */);
}
@@ -304,11 +306,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
PerformanceTracker.getInstanceForSensorId(getSensorId())
.incrementTimedLockoutForUser(getTargetUserId());
- try {
- getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ onError(error, 0 /* vendorCode */);
}
@Override
@@ -323,10 +321,6 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut
PerformanceTracker.getInstanceForSensorId(getSensorId())
.incrementPermanentLockoutForUser(getTargetUserId());
- try {
- getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- }
+ onError(error, 0 /* vendorCode */);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 1a53fec82d98..c5037b7012f2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -465,7 +465,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
BaseClientMonitor clientMonitor,
boolean success) {
mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId),
- sensorId, requestId, success);
+ sensorId, requestId, client.wasAuthSuccessful());
}
});
});
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 128ef0b2a802..6c26e2b0ce99 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -413,6 +413,11 @@ public class FingerprintService extends SystemService {
Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e);
}
}
+
+ @Override
+ public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) {
+ onAuthenticationAcquired(acquireInfo);
+ }
};
return biometricPrompt.authenticateForOperation(
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
index 33810b764f23..6d210eac542b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistry.java
@@ -20,7 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
@@ -28,7 +27,6 @@ import android.hardware.fingerprint.IFingerprintService;
import android.os.RemoteException;
import android.util.Slog;
-import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.BiometricServiceRegistry;
import java.util.List;
@@ -53,10 +51,8 @@ public class FingerprintServiceRegistry extends BiometricServiceRegistry<Service
@Override
protected void registerService(@NonNull IBiometricService service,
@NonNull FingerprintSensorPropertiesInternal props) {
- @BiometricManager.Authenticators.Types final int strength =
- Utils.propertyStrengthToAuthenticatorStrength(props.sensorStrength);
try {
- service.registerAuthenticator(props.sensorId, TYPE_FINGERPRINT, strength,
+ service.registerAuthenticator(TYPE_FINGERPRINT, props,
new FingerprintAuthenticator(mService, props.sensorId));
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception when registering sensorId: " + props.sensorId);
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
index 35ea36c5d56f..f27761fd644c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisService.java
@@ -16,12 +16,10 @@
package com.android.server.biometrics.sensors.iris;
-import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_IRIS;
import android.annotation.NonNull;
import android.content.Context;
-import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.IBiometricService;
import android.hardware.biometrics.SensorPropertiesInternal;
import android.hardware.iris.IIrisService;
@@ -33,7 +31,6 @@ import android.util.Slog;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
-import com.android.server.biometrics.Utils;
import java.util.List;
@@ -75,17 +72,12 @@ public class IrisService extends SystemService {
ServiceManager.getService(Context.BIOMETRIC_SERVICE));
for (SensorPropertiesInternal hidlSensor : hidlSensors) {
- final int sensorId = hidlSensor.sensorId;
- final @BiometricManager.Authenticators.Types int strength =
- Utils.propertyStrengthToAuthenticatorStrength(
- hidlSensor.sensorStrength);
- final IrisAuthenticator authenticator = new IrisAuthenticator(mServiceWrapper,
- sensorId);
try {
- biometricService.registerAuthenticator(sensorId, TYPE_IRIS, strength,
- authenticator);
+ biometricService.registerAuthenticator(TYPE_IRIS, hidlSensor,
+ new IrisAuthenticator(mServiceWrapper, hidlSensor.sensorId));
} catch (RemoteException e) {
- Slog.e(TAG, "Remote exception when registering sensorId: " + sensorId);
+ Slog.e(TAG, "Remote exception when registering sensorId: "
+ + hidlSensor.sensorId);
}
}
});
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index b25206d3b621..7e48f68dcefc 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3391,6 +3391,7 @@ public class Vpn {
* consistency of the Ikev2VpnRunner fields.
*/
public void onDefaultNetworkChanged(@NonNull Network network) {
+ mEventChanges.log("[UnderlyingNW] Default network changed to " + network);
Log.d(TAG, "onDefaultNetworkChanged: " + network);
// If there is a new default network brought up, cancel the retry task to prevent
@@ -3628,6 +3629,7 @@ public class Vpn {
mNetworkCapabilities = new NetworkCapabilities.Builder(mNetworkCapabilities)
.setTransportInfo(info)
.build();
+ mEventChanges.log("[VPNRunner] Update agent caps " + mNetworkCapabilities);
doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
}
}
@@ -3664,6 +3666,7 @@ public class Vpn {
private void startIkeSession(@NonNull Network underlyingNetwork) {
Log.d(TAG, "Start new IKE session on network " + underlyingNetwork);
+ mEventChanges.log("[IKE] Start IKE session over " + underlyingNetwork);
try {
// Clear mInterface to prevent Ikev2VpnRunner being cleared when
@@ -3778,6 +3781,7 @@ public class Vpn {
}
public void onValidationStatus(int status) {
+ mEventChanges.log("[Validation] validation status " + status);
if (status == NetworkAgent.VALIDATION_STATUS_VALID) {
// No data stall now. Reset it.
mExecutor.execute(() -> {
@@ -3818,6 +3822,7 @@ public class Vpn {
* consistency of the Ikev2VpnRunner fields.
*/
public void onDefaultNetworkLost(@NonNull Network network) {
+ mEventChanges.log("[UnderlyingNW] Network lost " + network);
// If the default network is torn down, there is no need to call
// startOrMigrateIkeSession() since it will always check if there is an active network
// can be used or not.
@@ -3936,6 +3941,8 @@ public class Vpn {
* consistency of the Ikev2VpnRunner fields.
*/
public void onSessionLost(int token, @Nullable Exception exception) {
+ mEventChanges.log("[IKE] Session lost on network " + mActiveNetwork
+ + (null == exception ? "" : " reason " + exception.getMessage()));
Log.d(TAG, "onSessionLost() called for token " + token);
if (!isActiveToken(token)) {
@@ -4092,6 +4099,7 @@ public class Vpn {
* consistency of the Ikev2VpnRunner fields.
*/
private void disconnectVpnRunner() {
+ mEventChanges.log("[VPNRunner] Disconnect runner, underlying network" + mActiveNetwork);
mActiveNetwork = null;
mUnderlyingNetworkCapabilities = null;
mUnderlyingLinkProperties = null;
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 7802b9d24de9..0e26d4661017 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -124,7 +124,7 @@ public final class DreamManagerService extends SystemService {
private final boolean mDreamsEnabledByDefaultConfig;
private final boolean mDreamsActivatedOnChargeByDefault;
private final boolean mDreamsActivatedOnDockByDefault;
- private final boolean mKeepDreamingWhenUndockedDefault;
+ private final boolean mKeepDreamingWhenUnpluggingDefault;
private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener>
mDreamManagerStateListeners = new CopyOnWriteArrayList<>();
@@ -236,8 +236,8 @@ public final class DreamManagerService extends SystemService {
mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
mSettingsObserver = new SettingsObserver(mHandler);
- mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_keepDreamingWhenUndocking);
+ mKeepDreamingWhenUnpluggingDefault = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_keepDreamingWhenUnplugging);
}
@Override
@@ -311,7 +311,7 @@ public final class DreamManagerService extends SystemService {
pw.println("mIsDocked=" + mIsDocked);
pw.println("mIsCharging=" + mIsCharging);
pw.println("mWhenToDream=" + mWhenToDream);
- pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault);
+ pw.println("mKeepDreamingWhenUnpluggingDefault=" + mKeepDreamingWhenUnpluggingDefault);
pw.println("getDozeComponent()=" + getDozeComponent());
pw.println();
@@ -340,11 +340,11 @@ public final class DreamManagerService extends SystemService {
}
}
- private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+ private void reportKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) {
mHandler.post(() -> {
for (DreamManagerInternal.DreamManagerStateListener listener
: mDreamManagerStateListeners) {
- listener.onKeepDreamingWhenUndockedChanged(keepDreaming);
+ listener.onKeepDreamingWhenUnpluggingChanged(keepDreaming);
}
});
}
@@ -600,8 +600,7 @@ public final class DreamManagerService extends SystemService {
}
mSystemDreamComponent = componentName;
- reportKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
-
+ reportKeepDreamingWhenUnpluggingChanged(shouldKeepDreamingWhenUnplugging());
// Switch dream if currently dreaming and not dozing.
if (isDreamingInternal() && !isDozingInternal()) {
startDreamInternal(false /*doze*/, (mSystemDreamComponent == null ? "clear" : "set")
@@ -610,8 +609,8 @@ public final class DreamManagerService extends SystemService {
}
}
- private boolean shouldKeepDreamingWhenUndocked() {
- return mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null;
+ private boolean shouldKeepDreamingWhenUnplugging() {
+ return mKeepDreamingWhenUnpluggingDefault && mSystemDreamComponent == null;
}
private ComponentName getDefaultDreamComponentForUser(int userId) {
@@ -1057,7 +1056,7 @@ public final class DreamManagerService extends SystemService {
public void registerDreamManagerStateListener(DreamManagerStateListener listener) {
mDreamManagerStateListeners.add(listener);
// Initialize the listener's state.
- listener.onKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked());
+ listener.onKeepDreamingWhenUnpluggingChanged(shouldKeepDreamingWhenUnplugging());
}
@Override
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
index 2ac283370886..c039a836843c 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java
@@ -334,8 +334,8 @@ public class FontManagerShellCommand extends ShellCommand {
}
private int installCert(ShellCommand shell) throws SystemFontException {
- if (!(Build.IS_USERDEBUG || Build.IS_ENG)) {
- throw new SecurityException("Only userdebug/eng device can add debug certificate");
+ if (!Build.IS_DEBUGGABLE) {
+ throw new SecurityException("Only debuggable device can add debug certificate");
}
if (Binder.getCallingUid() != Process.ROOT_UID) {
throw new SecurityException("Only root can add debug certificate");
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index c05a03ee1d2d..c76ca2beda96 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -286,7 +286,6 @@ final class AdditionalSubtypeUtils {
final InputMethodSubtype.InputMethodSubtypeBuilder
builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(label)
- .setSubtypeNameOverride(untranslatableName)
.setPhysicalKeyboardHint(
pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
pkLayoutType == null ? "" : pkLayoutType)
@@ -302,6 +301,9 @@ final class AdditionalSubtypeUtils {
if (subtypeId != InputMethodSubtype.SUBTYPE_ID_NONE) {
builder.setSubtypeId(subtypeId);
}
+ if (untranslatableName != null) {
+ builder.setSubtypeNameOverride(untranslatableName);
+ }
tempSubtypesArray.add(builder.build());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index c212e8e3c82c..44ae454e7ef2 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -19,7 +19,6 @@ package com.android.server.inputmethod;
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
-import android.annotation.Nullable;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -66,8 +65,8 @@ final class InputMethodMenuController {
private boolean mShowImeWithHardKeyboard;
@GuardedBy("ImfLock.class")
- @Nullable
- private InputMethodDialogWindowContext mDialogWindowContext;
+ private final InputMethodDialogWindowContext mDialogWindowContext =
+ new InputMethodDialogWindowContext();
InputMethodMenuController(InputMethodManagerService service) {
mService = service;
@@ -125,13 +124,11 @@ final class InputMethodMenuController {
}
}
- if (mDialogWindowContext == null) {
- mDialogWindowContext = new InputMethodDialogWindowContext();
- }
final Context dialogWindowContext = mDialogWindowContext.get(displayId);
mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
+ // TODO(b/277061090): refactor UI components should not be created while holding a lock.
final Context dialogContext = mDialogBuilder.getContext();
final TypedArray a = dialogContext.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
@@ -199,10 +196,11 @@ final class InputMethodMenuController {
attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
+ // TODO(b/277062834) decouple/remove dependency on IMMS
mService.updateSystemUiLocked();
mService.sendOnNavButtonFlagsChangedLocked();
- mSwitchingDialog.show();
}
+ mSwitchingDialog.show();
}
private boolean isScreenLocked() {
@@ -276,6 +274,7 @@ final class InputMethodMenuController {
private final int mTextViewResourceId;
private final List<ImeSubtypeListItem> mItemsList;
public int mCheckedItem;
+
private ImeSubtypeListAdapter(Context context, int textViewResourceId,
List<ImeSubtypeListItem> itemsList, int checkedItem) {
super(context, textViewResourceId, itemsList);
diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS
index 00cd700541c0..6e5eb5631112 100644
--- a/services/core/java/com/android/server/inputmethod/OWNERS
+++ b/services/core/java/com/android/server/inputmethod/OWNERS
@@ -1,8 +1,8 @@
set noparent
-ogunwale@google.com
+roosa@google.com
yukawa@google.com
tarandeep@google.com
-lumark@google.com
-roosa@google.com
-wilsonwu@google.com
+
+ogunwale@google.com #{LAST_RESORT_SUGGESTION}
+jjaggi@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 43e346a5bfa3..2d4066144a7f 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -323,7 +323,7 @@ public class LocaleManagerService extends SystemService {
*/
void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
LocaleList locales) {
- String installingPackageName = getInstallingPackageName(appPackageName);
+ String installingPackageName = getInstallingPackageName(appPackageName, userId);
if (installingPackageName != null) {
Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED,
appPackageName, locales);
@@ -464,7 +464,7 @@ public class LocaleManagerService extends SystemService {
* Checks if the calling app is the installer of the app whose locale changed.
*/
private boolean isCallerInstaller(String appPackageName, int userId) {
- String installingPackageName = getInstallingPackageName(appPackageName);
+ String installingPackageName = getInstallingPackageName(appPackageName, userId);
if (installingPackageName != null) {
// Get the uid of installer-on-record to compare with the calling uid.
int installerUid = getPackageUid(installingPackageName, userId);
@@ -513,10 +513,11 @@ public class LocaleManagerService extends SystemService {
}
@Nullable
- String getInstallingPackageName(String packageName) {
+ String getInstallingPackageName(String packageName, int userId) {
try {
- return mContext.getPackageManager()
- .getInstallSourceInfo(packageName).getInstallingPackageName();
+ return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */
+ 0).getPackageManager().getInstallSourceInfo(
+ packageName).getInstallingPackageName();
} catch (PackageManager.NameNotFoundException e) {
Slog.w(TAG, "Package not found " + packageName);
}
diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
index 215c653f1be7..373d3553e0eb 100644
--- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
+++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java
@@ -152,9 +152,10 @@ public class SystemAppUpdateTracker {
void onPackageUpdateFinished(String packageName, int uid) {
try {
if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
+ int userId = UserHandle.getUserId(uid);
// If a system app is updated, verify that it has an installer-on-record.
String installingPackageName = mLocaleManagerService.getInstallingPackageName(
- packageName);
+ packageName, userId);
if (installingPackageName == null) {
// We want to broadcast the locales info to the installer.
// If this app does not have an installer then do nothing.
@@ -162,7 +163,6 @@ public class SystemAppUpdateTracker {
}
try {
- int userId = UserHandle.getUserId(uid);
// Fetch the app-specific locales.
// If non-empty then send the info to the installer.
LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 653b71828c5b..5f783742bd26 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -519,17 +519,24 @@ public class ContextHubService extends IContextHubService.Stub {
BroadcastReceiver btReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())
- || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(
- intent.getAction())) {
+ if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
sendBtSettingUpdate(/* forceUpdate= */ false);
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED);
mContext.registerReceiver(btReceiver, filter);
+
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE),
+ /* notifyForDescendants= */ false,
+ new ContentObserver(/* handler= */ null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ sendBtSettingUpdate(/* forceUpdate= */ false);
+ }
+ }, UserHandle.USER_ALL);
}
/**
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0da94ff6aa0f..ee4a6fea8ad4 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1516,11 +1516,6 @@ public class LockSettingsService extends ILockSettings.Stub {
&& !getSeparateProfileChallengeEnabledInternal(userId);
}
- private boolean isProfileWithSeparatedLock(int userId) {
- return isCredentialSharableWithParent(userId)
- && getSeparateProfileChallengeEnabledInternal(userId);
- }
-
/**
* Send credentials for user {@code userId} to {@link RecoverableKeyStoreManager} during an
* unlock operation.
@@ -2784,9 +2779,19 @@ public class LockSettingsService extends ILockSettings.Stub {
activateEscrowTokens(sp, userId);
- if (isProfileWithSeparatedLock(userId)) {
- setDeviceUnlockedForUser(userId);
+ if (isCredentialSharableWithParent(userId)) {
+ if (getSeparateProfileChallengeEnabledInternal(userId)) {
+ setDeviceUnlockedForUser(userId);
+ } else {
+ // Here only clear StrongAuthFlags for a profile that has a unified challenge.
+ // StrongAuth for a profile with a separate challenge is handled differently and
+ // is cleared after the user successfully confirms the separate challenge to enter
+ // the profile. StrongAuth for the full user (e.g. userId 0) is also handled
+ // separately by Keyguard.
+ mStrongAuth.reportUnlock(userId);
+ }
}
+
mStrongAuth.reportSuccessfulStrongAuthUnlock(userId);
onSyntheticPasswordUnlocked(userId, sp);
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 f0e8ede5987d..94d5aabe24e5 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -357,12 +357,16 @@ public final class MediaProjectionManagerService extends SystemService
} catch (NameNotFoundException e) {
throw new IllegalArgumentException("No package matching :" + packageName);
}
-
- projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
- ai.isPrivilegedApp());
- if (isPermanentGrant) {
- mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
- projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ projection = new MediaProjection(type, uid, packageName, ai.targetSdkVersion,
+ ai.isPrivilegedApp());
+ if (isPermanentGrant) {
+ mAppOps.setMode(AppOpsManager.OP_PROJECT_MEDIA,
+ projection.uid, projection.packageName, AppOpsManager.MODE_ALLOWED);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
}
return projection;
}
@@ -418,16 +422,9 @@ public final class MediaProjectionManagerService extends SystemService
if (packageName == null || packageName.isEmpty()) {
throw new IllegalArgumentException("package name must not be empty");
}
- MediaProjection projection;
final UserHandle callingUser = Binder.getCallingUserHandle();
- final long callingToken = Binder.clearCallingIdentity();
- try {
- projection = createProjectionInternal(uid, packageName, type, isPermanentGrant,
- callingUser, false);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
- return projection;
+ return createProjectionInternal(uid, packageName, type, isPermanentGrant,
+ callingUser, false);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 273afcc9f769..dff02bf711cd 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -15,44 +15,53 @@
*/
package com.android.server.notification;
+import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_GROUP_SUMMARY;
+import static android.app.Notification.FLAG_LOCAL_ONLY;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import android.annotation.NonNull;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
/**
* NotificationManagerService helper for auto-grouping notifications.
*/
public class GroupHelper {
private static final String TAG = "GroupHelper";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
protected static final String AUTOGROUP_KEY = "ranker_group";
+ // Flags that all autogroup summaries have
+ protected static final int BASE_FLAGS =
+ FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY;
+ // Flag that autogroup summaries inherits if all children have the flag
+ private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL;
+ // Flags that autogroup summaries inherits if any child has them
+ private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR;
+
private final Callback mCallback;
private final int mAutoGroupAtCount;
- // count the number of ongoing notifications per group
- // userId|packageName -> (set of ongoing notifications that aren't in an app group)
- final ArrayMap<String, ArraySet<String>>
- mOngoingGroupCount = new ArrayMap<>();
-
- // Map of user : <Map of package : notification keys>. Only contains notifications that are not
- // grouped by the app (aka no group or sort key).
- Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>();
+ // Only contains notifications that are not explicitly grouped by the app (aka no group or
+ // sort key).
+ // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags)
+ @GuardedBy("mUngroupedNotifications")
+ private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications
+ = new ArrayMap<>();
public GroupHelper(int autoGroupAtCount, Callback callback) {
mAutoGroupAtCount = autoGroupAtCount;
- mCallback = callback;
+ mCallback = callback;
}
private String generatePackageKey(int userId, String pkg) {
@@ -60,69 +69,30 @@ public class GroupHelper {
}
@VisibleForTesting
- protected int getOngoingGroupCount(int userId, String pkg) {
- String key = generatePackageKey(userId, pkg);
- return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size();
- }
-
- private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) {
- if (sbn.getNotification().isGroupSummary()) {
- return;
- }
- String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
- ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0));
- if (add) {
- notifications.add(sbn.getKey());
- mOngoingGroupCount.put(key, notifications);
- } else {
- notifications.remove(sbn.getKey());
- // we don't need to put it back if it is default
+ @GuardedBy("mUngroupedNotifications")
+ protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) {
+ boolean allChildrenHasFlag = children.size() > 0;
+ int anyChildFlagSet = 0;
+ for (int i = 0; i < children.size(); i++) {
+ if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) {
+ allChildrenHasFlag = false;
+ }
+ if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) {
+ anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS);
+ }
}
-
- boolean needsOngoingFlag = notifications.size() > 0;
- mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag);
+ return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet;
}
- public void onNotificationUpdated(StatusBarNotification childSbn) {
- updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup());
+ private boolean hasAnyFlag(int flags, int mask) {
+ return (flags & mask) != 0;
}
public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) {
try {
- updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup());
-
- List<String> notificationsToGroup = new ArrayList<>();
if (!sbn.isAppGroup()) {
- // Not grouped by the app, add to the list of notifications for the app;
- // send grouping update if app exceeds the autogrouping limit.
- synchronized (mUngroupedNotifications) {
- Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
- = mUngroupedNotifications.get(sbn.getUserId());
- if (ungroupedNotificationsByUser == null) {
- ungroupedNotificationsByUser = new HashMap<>();
- }
- mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser);
- LinkedHashSet<String> notificationsForPackage
- = ungroupedNotificationsByUser.get(sbn.getPackageName());
- if (notificationsForPackage == null) {
- notificationsForPackage = new LinkedHashSet<>();
- }
-
- notificationsForPackage.add(sbn.getKey());
- ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage);
-
- if (notificationsForPackage.size() >= mAutoGroupAtCount
- || autogroupSummaryExists) {
- notificationsToGroup.addAll(notificationsForPackage);
- }
- }
- if (notificationsToGroup.size() > 0) {
- adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(),
- notificationsToGroup.get(0), true);
- adjustNotificationBundling(notificationsToGroup, true);
- }
+ maybeGroup(sbn, autogroupSummaryExists);
} else {
- // Grouped, but not by us. Send updates to un-autogroup, if we grouped it.
maybeUngroup(sbn, false, sbn.getUserId());
}
@@ -133,7 +103,6 @@ public class GroupHelper {
public void onNotificationRemoved(StatusBarNotification sbn) {
try {
- updateOngoingGroupCount(sbn, false);
maybeUngroup(sbn, true, sbn.getUserId());
} catch (Exception e) {
Slog.e(TAG, "Error processing canceled notification", e);
@@ -141,70 +110,114 @@ public class GroupHelper {
}
/**
- * Un-autogroups notifications that are now grouped by the app.
+ * A non-app grouped notification has been added or updated
+ * Evaluate if:
+ * (a) an existing autogroup summary needs updated flags
+ * (b) a new autogroup summary needs to be added with correct flags
+ * (c) other non-app grouped children need to be moved to the autogroup
+ *
+ * And stores the list of upgrouped notifications & their flags
+ */
+ private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) {
+ int flags = 0;
+ List<String> notificationsToGroup = new ArrayList<>();
+ synchronized (mUngroupedNotifications) {
+ String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+ final ArrayMap<String, Integer> children =
+ mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+
+ children.put(sbn.getKey(), sbn.getNotification().flags);
+ mUngroupedNotifications.put(key, children);
+
+ if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) {
+ flags = getAutogroupSummaryFlags(children);
+ notificationsToGroup.addAll(children.keySet());
+ }
+ }
+ if (notificationsToGroup.size() > 0) {
+ if (autogroupSummaryExists) {
+ mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags);
+ } else {
+ mCallback.addAutoGroupSummary(
+ sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags);
+ }
+ for (String key : notificationsToGroup) {
+ mCallback.addAutoGroup(key);
+ }
+ }
+ }
+
+ /**
+ * A notification was added that's app grouped, or a notification was removed.
+ * Evaluate whether:
+ * (a) an existing autogroup summary needs updated flags
+ * (b) if we need to remove our autogroup overlay for this notification
+ * (c) we need to remove the autogroup summary
+ *
+ * And updates the internal state of un-app-grouped notifications and their flags
*/
private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) {
- List<String> notificationsToUnAutogroup = new ArrayList<>();
boolean removeSummary = false;
+ int summaryFlags = 0;
+ boolean updateSummaryFlags = false;
+ boolean removeAutogroupOverlay = false;
synchronized (mUngroupedNotifications) {
- Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser
- = mUngroupedNotifications.get(sbn.getUserId());
- if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) {
- return;
- }
- LinkedHashSet<String> notificationsForPackage
- = ungroupedNotificationsByUser.get(sbn.getPackageName());
- if (notificationsForPackage == null || notificationsForPackage.size() == 0) {
+ String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName());
+ final ArrayMap<String, Integer> children =
+ mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+ if (children.size() == 0) {
return;
}
- if (notificationsForPackage.remove(sbn.getKey())) {
- if (!notificationGone) {
- // Add the current notification to the ungrouping list if it still exists.
- notificationsToUnAutogroup.add(sbn.getKey());
+
+ // if this notif was autogrouped and now isn't
+ if (children.containsKey(sbn.getKey())) {
+ // if this notification was contributing flags that aren't covered by other
+ // children to the summary, reevaluate flags for the summary
+ int flags = children.remove(sbn.getKey());
+ // this
+ if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) {
+ updateSummaryFlags = true;
+ summaryFlags = getAutogroupSummaryFlags(children);
+ }
+ // if this notification still exists and has an autogroup overlay, but is now
+ // grouped by the app, clear the overlay
+ if (!notificationGone && sbn.getOverrideGroupKey() != null) {
+ removeAutogroupOverlay = true;
+ }
+
+ // If there are no more children left to autogroup, remove the summary
+ if (children.size() == 0) {
+ removeSummary = true;
}
- }
- // If the status change of this notification has brought the number of loose
- // notifications to zero, remove the summary and un-autogroup.
- if (notificationsForPackage.size() == 0) {
- ungroupedNotificationsByUser.remove(sbn.getPackageName());
- removeSummary = true;
}
}
if (removeSummary) {
- adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false);
- }
- if (notificationsToUnAutogroup.size() > 0) {
- adjustNotificationBundling(notificationsToUnAutogroup, false);
- }
- }
-
- private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey,
- boolean summaryNeeded) {
- if (summaryNeeded) {
- mCallback.addAutoGroupSummary(userId, packageName, triggeringKey,
- getOngoingGroupCount(userId, packageName) > 0);
+ mCallback.removeAutoGroupSummary(userId, sbn.getPackageName());
} else {
- mCallback.removeAutoGroupSummary(userId, packageName);
+ if (updateSummaryFlags) {
+ mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags);
+ }
+ }
+ if (removeAutogroupOverlay) {
+ mCallback.removeAutoGroup(sbn.getKey());
}
}
- private void adjustNotificationBundling(List<String> keys, boolean group) {
- for (String key : keys) {
- if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group);
- if (group) {
- mCallback.addAutoGroup(key);
- } else {
- mCallback.removeAutoGroup(key);
- }
+ @VisibleForTesting
+ int getNotGroupedByAppCount(int userId, String pkg) {
+ synchronized (mUngroupedNotifications) {
+ String key = generatePackageKey(userId, pkg);
+ final ArrayMap<String, Integer> children =
+ mUngroupedNotifications.getOrDefault(key, new ArrayMap<>());
+ return children.size();
}
}
protected interface Callback {
void addAutoGroup(String key);
void removeAutoGroup(String key);
- void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
- boolean needsOngoingFlag);
+ void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags);
void removeAutoGroupSummary(int user, String pkg);
- void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag);
+ void updateAutogroupSummary(int userId, String pkg, int flags);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6d27fe058423..ebcbfed93ac7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -319,6 +319,7 @@ import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.policy.PermissionPolicyInternal;
+import com.android.server.powerstats.StatsPullAtomCallbackImpl;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.utils.Slogf;
@@ -902,11 +903,11 @@ public class NotificationManagerService extends SystemService {
* has the same flag. It will delete the flag otherwise
* @param userId user id of the autogroup summary
* @param pkg package of the autogroup summary
- * @param needsOngoingFlag true if the group has at least one ongoing notification
+ * @param flags the new flags for this summary
* @param isAppForeground true if the app is currently in the foreground.
*/
@GuardedBy("mNotificationLock")
- protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag,
+ protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags,
boolean isAppForeground) {
ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId);
if (summaries == null) {
@@ -921,13 +922,8 @@ public class NotificationManagerService extends SystemService {
return;
}
int oldFlags = summary.getSbn().getNotification().flags;
- if (needsOngoingFlag) {
- summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT;
- } else {
- summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT;
- }
-
- if (summary.getSbn().getNotification().flags != oldFlags) {
+ if (oldFlags != flags) {
+ summary.getSbn().getNotification().flags = flags;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
SystemClock.elapsedRealtime()));
}
@@ -2684,9 +2680,14 @@ public class NotificationManagerService extends SystemService {
@Override
public void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
- boolean needsOngoingFlag) {
- NotificationManagerService.this.addAutoGroupSummary(
- userId, pkg, triggeringKey, needsOngoingFlag);
+ int flags) {
+ NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags);
+ if (r != null) {
+ final boolean isAppForeground =
+ mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
+ mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
+ SystemClock.elapsedRealtime()));
+ }
}
@Override
@@ -2697,11 +2698,11 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) {
+ public void updateAutogroupSummary(int userId, String pkg, int flags) {
boolean isAppForeground = pkg != null
&& mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
- updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground);
+ updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground);
}
}
});
@@ -3334,7 +3335,7 @@ public class NotificationManagerService extends SystemService {
}
checkCallerIsSameApp(pkg);
- final boolean isSystemToast = isCallerSystemOrPhone()
+ final boolean isSystemToast = isCallerIsSystemOrSystemUi()
|| PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg);
boolean isAppRenderedToast = (callback != null);
if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast,
@@ -3815,6 +3816,28 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public boolean canUseFullScreenIntent(@NonNull AttributionSource attributionSource) {
+ final String packageName = attributionSource.getPackageName();
+ final int uid = attributionSource.getUid();
+ final int userId = UserHandle.getUserId(uid);
+ checkCallerIsSameApp(packageName, uid, userId);
+
+ final ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = mPackageManagerClient.getApplicationInfoAsUser(
+ packageName, PackageManager.MATCH_DIRECT_BOOT_AUTO, userId);
+ } catch (NameNotFoundException e) {
+ Slog.e(TAG, "Failed to getApplicationInfo() in canUseFullScreenIntent()", e);
+ return false;
+ }
+ final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags
+ .SHOW_STICKY_HUN_FOR_DENIED_FSI);
+ return checkUseFullScreenIntentPermission(attributionSource, applicationInfo,
+ showStickyHunIfDenied /* isAppOpPermission */, false /* forDataDelivery */);
+ }
+
+ @Override
public void updateNotificationChannelGroupForPackage(String pkg, int uid,
NotificationChannelGroup group) throws RemoteException {
enforceSystemOrSystemUI("Caller not system or systemui");
@@ -5939,19 +5962,6 @@ public class NotificationManagerService extends SystemService {
r.addAdjustment(adjustment);
}
- @VisibleForTesting
- void addAutoGroupSummary(int userId, String pkg, String triggeringKey,
- boolean needsOngoingFlag) {
- NotificationRecord r = createAutoGroupSummary(
- userId, pkg, triggeringKey, needsOngoingFlag);
- if (r != null) {
- final boolean isAppForeground =
- mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
- mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
- SystemClock.elapsedRealtime()));
- }
- }
-
// Clears the 'fake' auto-group summary.
@VisibleForTesting
@GuardedBy("mNotificationLock")
@@ -5975,7 +5985,7 @@ public class NotificationManagerService extends SystemService {
// Creates a 'fake' summary for a package that has exceeded the solo-notification limit.
NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey,
- boolean needsOngoingFlag) {
+ int flagsToSet) {
NotificationRecord summaryRecord = null;
boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
synchronized (mNotificationLock) {
@@ -5985,7 +5995,6 @@ public class NotificationManagerService extends SystemService {
// adjustment will post a summary if needed.
return null;
}
- NotificationChannel channel = notificationRecord.getChannel();
final StatusBarNotification adjustedSbn = notificationRecord.getSbn();
userId = adjustedSbn.getUser().getIdentifier();
int uid = adjustedSbn.getUid();
@@ -6008,11 +6017,8 @@ public class NotificationManagerService extends SystemService {
.setGroupSummary(true)
.setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN)
.setGroup(GroupHelper.AUTOGROUP_KEY)
- .setFlag(FLAG_AUTOGROUP_SUMMARY, true)
- .setFlag(Notification.FLAG_GROUP_SUMMARY, true)
- .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag)
+ .setFlag(flagsToSet, true)
.setColor(adjustedSbn.getNotification().color)
- .setLocalOnly(true)
.build();
summaryNotification.extras.putAll(extras);
Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg);
@@ -6350,6 +6356,7 @@ public class NotificationManagerService extends SystemService {
* The private API only accessible to the system process.
*/
private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
+
@Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String
channelId) {
@@ -6826,36 +6833,28 @@ public class NotificationManagerService extends SystemService {
notification.flags &= ~FLAG_FSI_REQUESTED_BUT_DENIED;
- if (notification.fullScreenIntent != null && ai.targetSdkVersion >= Build.VERSION_CODES.Q) {
+ if (notification.fullScreenIntent != null) {
final boolean forceDemoteFsiToStickyHun = mFlagResolver.isEnabled(
SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE);
-
- final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
- SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI);
-
if (forceDemoteFsiToStickyHun) {
makeStickyHun(notification, pkg, userId);
-
- } else if (showStickyHunIfDenied) {
- final AttributionSource source = new AttributionSource.Builder(notificationUid)
- .setPackageName(pkg)
- .build();
-
- final int permissionResult = mPermissionManager.checkPermissionForDataDelivery(
- Manifest.permission.USE_FULL_SCREEN_INTENT, source, /* message= */ null);
-
- if (permissionResult != PermissionManager.PERMISSION_GRANTED) {
- makeStickyHun(notification, pkg, userId);
- }
-
} else {
- int fullscreenIntentPermission = getContext().checkPermission(
- android.Manifest.permission.USE_FULL_SCREEN_INTENT, -1, notificationUid);
-
- if (fullscreenIntentPermission != PERMISSION_GRANTED) {
- notification.fullScreenIntent = null;
- Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
- + "USE_FULL_SCREEN_INTENT permission");
+ final AttributionSource attributionSource =
+ new AttributionSource.Builder(notificationUid).setPackageName(pkg).build();
+ final boolean showStickyHunIfDenied = mFlagResolver.isEnabled(
+ SystemUiSystemPropertiesFlags.NotificationFlags
+ .SHOW_STICKY_HUN_FOR_DENIED_FSI);
+ final boolean canUseFullScreenIntent = checkUseFullScreenIntentPermission(
+ attributionSource, ai, showStickyHunIfDenied /* isAppOpPermission */,
+ true /* forDataDelivery */);
+ if (!canUseFullScreenIntent) {
+ if (showStickyHunIfDenied) {
+ makeStickyHun(notification, pkg, userId);
+ } else {
+ notification.fullScreenIntent = null;
+ Slog.w(TAG, "Package " + pkg + ": Use of fullScreenIntent requires the"
+ + "USE_FULL_SCREEN_INTENT permission");
+ }
}
}
}
@@ -6951,6 +6950,30 @@ public class NotificationManagerService extends SystemService {
ai.packageName) == AppOpsManager.MODE_ALLOWED;
}
+ private boolean checkUseFullScreenIntentPermission(@NonNull AttributionSource attributionSource,
+ @NonNull ApplicationInfo applicationInfo, boolean isAppOpPermission,
+ boolean forDataDelivery) {
+ if (applicationInfo.targetSdkVersion < Build.VERSION_CODES.Q) {
+ return true;
+ }
+ if (isAppOpPermission) {
+ final int permissionResult;
+ if (forDataDelivery) {
+ permissionResult = mPermissionManager.checkPermissionForDataDelivery(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource, /* message= */ null);
+ } else {
+ permissionResult = mPermissionManager.checkPermissionForPreflight(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource);
+ }
+ return permissionResult == PermissionManager.PERMISSION_GRANTED;
+ } else {
+ final int permissionResult = getContext().checkPermission(
+ permission.USE_FULL_SCREEN_INTENT, attributionSource.getPid(),
+ attributionSource.getUid());
+ return permissionResult == PERMISSION_GRANTED;
+ }
+ }
+
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
if (removeRemoteView(pkg, tag, id, notification.contentView)) {
notification.contentView = null;
@@ -7789,18 +7812,17 @@ public class NotificationManagerService extends SystemService {
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
mListeners.notifyPostedLocked(r, old);
- if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup()))
- && !isCritical(r)) {
- mHandler.post(() -> {
- synchronized (mNotificationLock) {
- mGroupHelper.onNotificationPosted(
- n, hasAutoGroupSummaryLocked(n));
- }
- });
- } else if (oldSbn != null) {
- final NotificationRecord finalRecord = r;
- mHandler.post(() ->
- mGroupHelper.onNotificationUpdated(finalRecord.getSbn()));
+ if (oldSbn == null
+ || !Objects.equals(oldSbn.getGroup(), n.getGroup())
+ || oldSbn.getNotification().flags != n.getNotification().flags) {
+ if (!isCritical(r)) {
+ mHandler.post(() -> {
+ synchronized (mNotificationLock) {
+ mGroupHelper.onNotificationPosted(
+ n, hasAutoGroupSummaryLocked(n));
+ }
+ });
+ }
}
} else {
Slog.e(TAG, "Not posting notification without small icon: " + notification);
diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java
index 35b94e7ccd63..88d23ce3f9a1 100644
--- a/services/core/java/com/android/server/notification/ZenLog.java
+++ b/services/core/java/com/android/server/notification/ZenLog.java
@@ -27,6 +27,7 @@ import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
import android.util.Log;
import android.util.Slog;
@@ -146,13 +147,13 @@ public class ZenLog {
public static void traceConfig(String reason, ZenModeConfig oldConfig,
ZenModeConfig newConfig) {
- ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
- if (diff.isEmpty()) {
+ ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
+ if (diff == null || !diff.hasDiff()) {
append(TYPE_CONFIG, reason + " no changes");
} else {
append(TYPE_CONFIG, reason
+ ",\n" + (newConfig != null ? newConfig.toString() : null)
- + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
+ + ",\n" + diff);
}
}
diff --git a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
index cae7079c75e0..82b585d7ad01 100644
--- a/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
+++ b/services/core/java/com/android/server/pm/DynamicCodeLoggingService.java
@@ -65,7 +65,7 @@ public class DynamicCodeLoggingService extends JobService {
private static final String AVC_PREFIX = "type=" + AUDIT_AVC + " ";
private static final Pattern EXECUTE_NATIVE_AUDIT_PATTERN =
- Pattern.compile(".*\\bavc: granted \\{ execute(?:_no_trans|) \\} .*"
+ Pattern.compile(".*\\bavc: +granted +\\{ execute(?:_no_trans|) \\} .*"
+ "\\bpath=(?:\"([^\" ]*)\"|([0-9A-F]+)) .*"
+ "\\bscontext=u:r:untrusted_app(?:_25|_27)?:.*"
+ "\\btcontext=u:object_r:app_data_file:.*"
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index c29e4d78f929..52fdbda04fcd 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -606,6 +606,7 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
final Computer snapshot = snapshot();
// Return null for InstantApps.
if (snapshot.getInstantAppPackageName(Binder.getCallingUid()) != null) {
+ Log.w(PackageManagerService.TAG, "Returning null PackageInstaller for InstantApps");
return null;
}
return mInstallerService;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 5f424edb15c4..69ef3f780172 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -994,7 +994,7 @@ final class InstallPackageHelper {
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
requests, Collections.unmodifiableMap(mPm.mPackages),
versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
+ mPm.mSettings, mContext);
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.setError("Reconciliation failed...", e);
@@ -3588,6 +3588,11 @@ final class InstallPackageHelper {
// remove the package from the system and re-scan it without any
// special privileges
mRemovePackageHelper.removePackage(pkg, true);
+ PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
+ if (ps != null) {
+ ps.getPkgState().setUpdatedSystemApp(false);
+ }
+
try {
final File codePath = new File(pkg.getPath());
synchronized (mPm.mInstallLock) {
@@ -3925,7 +3930,7 @@ final class InstallPackageHelper {
mPm.mPackages, Collections.singletonMap(pkgName,
mPm.getSettingsVersionForPackage(parsedPackage)),
mSharedLibraries, mPm.mSettings.getKeySetManagerService(),
- mPm.mSettings);
+ mPm.mSettings, mContext);
if ((scanFlags & SCAN_AS_APEX) == 0) {
appIdCreated = optimisticallyRegisterAppId(installRequest);
} else {
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index 5312ae6ca84c..e3c97e933ad1 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
@@ -23,19 +24,24 @@ import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRI
import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.SigningDetails;
import android.os.SystemProperties;
+import android.permission.PermissionManager;
import android.util.ArrayMap;
import android.util.Log;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.component.ParsedUsesPermission;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.utils.WatchedLongSparseArray;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -54,7 +60,7 @@ final class ReconcilePackageUtils {
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
SharedLibrariesImpl sharedLibraries,
- KeySetManagerService ksms, Settings settings)
+ KeySetManagerService ksms, Settings settings, Context context)
throws ReconcileFailure {
final List<ReconciledPackage> result = new ArrayList<>(installRequests.size());
@@ -143,11 +149,11 @@ final class ReconcilePackageUtils {
} else {
if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
- "Package " + parsedPackage.getPackageName()
+ "Package " + installPackageName
+ " upgrade keys do not match the previously installed"
+ " version");
} else {
- String msg = "System package " + parsedPackage.getPackageName()
+ String msg = "System package " + installPackageName
+ " signature changed; retaining data.";
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
}
@@ -168,11 +174,42 @@ final class ReconcilePackageUtils {
removeAppKeySetData = true;
}
- // if this is is a sharedUser, check to see if the new package is signed by a
- // newer
- // signing certificate than the existing one, and if so, copy over the new
+ // if this is a sharedUser, check to see if the new package is signed by a
+ // newer signing certificate than the existing one, and if so, copy over the new
// details
if (sharedUserSetting != null) {
+ if (!parsedPackage.isTestOnly() && sharedUserSetting.isPrivileged()
+ && !signatureCheckPs.isSystem()) {
+ final List<ParsedUsesPermission> usesPermissions =
+ parsedPackage.getUsesPermissions();
+ final List<String> usesPrivilegedPermissions = new ArrayList<>();
+ final PermissionManager permissionManager = context.getSystemService(
+ PermissionManager.class);
+ // Check if the app requests any privileged permissions because that
+ // violates the privapp-permissions allowlist check during boot.
+ if (permissionManager != null) {
+ for (int i = 0; i < usesPermissions.size(); i++) {
+ final String permissionName = usesPermissions.get(i).getName();
+ final PermissionInfo permissionInfo =
+ permissionManager.getPermissionInfo(permissionName, 0);
+ if (permissionInfo != null
+ && (permissionInfo.getProtectionFlags()
+ & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) {
+ usesPrivilegedPermissions.add(permissionName);
+ }
+ }
+ }
+
+ if (!usesPrivilegedPermissions.isEmpty()) {
+ throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK,
+ "Non-system package: " + installPackageName
+ + " shares signature and sharedUserId with"
+ + " a privileged package but requests"
+ + " privileged permissions that are not"
+ + " allowed: " + Arrays.toString(
+ usesPrivilegedPermissions.toArray()));
+ }
+ }
// Attempt to merge the existing lineage for the shared SigningDetails with
// the lineage of the new package; if the shared SigningDetails are not
// returned this indicates the new package added new signers to the lineage
@@ -189,7 +226,7 @@ final class ReconcilePackageUtils {
for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) {
if (androidPackage.getPackageName() != null
&& !androidPackage.getPackageName().equals(
- parsedPackage.getPackageName())) {
+ installPackageName)) {
mergedDetails = mergedDetails.mergeLineageWith(
androidPackage.getSigningDetails(),
MERGE_RESTRICTED_CAPABILITY);
@@ -219,7 +256,7 @@ final class ReconcilePackageUtils {
if (sharedUserSetting != null) {
if (sharedUserSetting.signaturesChanged != null
&& !PackageManagerServiceUtils.canJoinSharedUserId(
- parsedPackage.getPackageName(), parsedPackage.getSigningDetails(),
+ installPackageName, parsedPackage.getSigningDetails(),
sharedUserSetting,
PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) {
if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) {
@@ -240,7 +277,7 @@ final class ReconcilePackageUtils {
// whichever package happened to be scanned later.
throw new IllegalStateException(
"Signature mismatch on system package "
- + parsedPackage.getPackageName()
+ + installPackageName
+ " for shared user "
+ sharedUserSetting);
}
@@ -252,7 +289,7 @@ final class ReconcilePackageUtils {
sharedUserSetting.signaturesChanged = Boolean.TRUE;
}
// File a report about this.
- String msg = "System package " + parsedPackage.getPackageName()
+ String msg = "System package " + installPackageName
+ " signature changed; retaining data.";
PackageManagerService.reportSettingsProblem(Log.WARN, msg);
} catch (IllegalArgumentException e) {
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ad77ef7ca975..9127a93a46ee 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -187,6 +187,9 @@ public final class SuspendPackageHelper {
if (changed) {
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, packageState.getAppId()));
+ } else {
+ Slog.w(TAG, "No change is needed for package: " + packageName
+ + ". Skipping suspending/un-suspending.");
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ab9d1cfc2eec..5f8efe29459d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2592,7 +2592,7 @@ public class UserManagerService extends IUserManager.Stub {
}
}
if (scheduleWriteUser) {
- scheduleWriteUser(userData);
+ scheduleWriteUser(userId);
}
}
@@ -2641,6 +2641,7 @@ public class UserManagerService extends IUserManager.Stub {
private void setUserRestrictionInner(int userId, @NonNull String key, boolean value) {
if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ Slog.e(LOG_TAG, "Setting invalid restriction " + key);
return;
}
synchronized (mRestrictionsLock) {
@@ -2902,7 +2903,7 @@ public class UserManagerService extends IUserManager.Stub {
!= newBaseRestrictions);
if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) {
- scheduleWriteUser(getUserDataNoChecks(userId));
+ scheduleWriteUser(userId);
}
}
@@ -2978,7 +2979,7 @@ public class UserManagerService extends IUserManager.Stub {
@GuardedBy("mRestrictionsLock")
private void applyUserRestrictionsLR(@UserIdInt int userId) {
updateUserRestrictionsInternalLR(null, userId);
- scheduleWriteUser(getUserDataNoChecks(userId));
+ scheduleWriteUser(userId);
}
@GuardedBy("mRestrictionsLock")
@@ -4129,14 +4130,14 @@ public class UserManagerService extends IUserManager.Stub {
}
}
- private void scheduleWriteUser(UserData userData) {
+ private void scheduleWriteUser(@UserIdInt int userId) {
if (DBG) {
debug("scheduleWriteUser");
}
// No need to wrap it within a lock -- worst case, we'll just post the same message
// twice.
- if (!mHandler.hasMessages(WRITE_USER_MSG, userData)) {
- Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userData);
+ if (!mHandler.hasMessages(WRITE_USER_MSG, userId)) {
+ Message msg = mHandler.obtainMessage(WRITE_USER_MSG, userId);
mHandler.sendMessageDelayed(msg, WRITE_USER_DELAY);
}
}
@@ -4152,7 +4153,7 @@ public class UserManagerService extends IUserManager.Stub {
// Something went wrong, schedule full rewrite.
UserData userData = getUserDataNoChecks(userId);
if (userData != null) {
- scheduleWriteUser(userData);
+ scheduleWriteUser(userId);
}
});
}
@@ -6363,7 +6364,7 @@ public class UserManagerService extends IUserManager.Stub {
userData.info.lastLoggedInTime = now;
}
userData.info.lastLoggedInFingerprint = PackagePartitions.FINGERPRINT;
- scheduleWriteUser(userData);
+ scheduleWriteUser(userId);
}
/**
@@ -6533,7 +6534,7 @@ public class UserManagerService extends IUserManager.Stub {
private void setLastEnteredForegroundTimeToNow(@NonNull UserData userData) {
userData.mLastEnteredForegroundTimeMillis = System.currentTimeMillis();
- scheduleWriteUser(userData);
+ scheduleWriteUser(userData.info.id);
}
@Override
@@ -6832,7 +6833,7 @@ public class UserManagerService extends IUserManager.Stub {
case WRITE_USER_MSG:
removeMessages(WRITE_USER_MSG, msg.obj);
synchronized (mPackagesLock) {
- int userId = ((UserData) msg.obj).info.id;
+ int userId = (int) msg.obj;
UserData userData = getUserDataNoChecks(userId);
if (userData != null) {
writeUserLP(userData);
diff --git a/services/core/java/com/android/server/pm/VerifyingSession.java b/services/core/java/com/android/server/pm/VerifyingSession.java
index c9ebeaee88ce..5015985dd747 100644
--- a/services/core/java/com/android/server/pm/VerifyingSession.java
+++ b/services/core/java/com/android/server/pm/VerifyingSession.java
@@ -361,7 +361,8 @@ final class VerifyingSession {
}
final int verifierUserId = verifierUser.getIdentifier();
- List<String> requiredVerifierPackages = Arrays.asList(mPm.mRequiredVerifierPackages);
+ List<String> requiredVerifierPackages = new ArrayList<>(
+ Arrays.asList(mPm.mRequiredVerifierPackages));
boolean requiredVerifierPackagesOverridden = false;
// Allow verifier override for ADB installations which could already be unverified using
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index e5e32f0a9690..4d2b119d7800 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -206,8 +206,6 @@ final class DefaultPermissionGrantPolicy {
static {
SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
- SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
}
private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index c5f939a2a66e..297ad73e054b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1332,9 +1332,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// Bg location is one-off runtime modifier permission and has no app op
if (sPlatformPermissions.containsKey(permission)
&& !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission)
- && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)
- && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND
- .equals(permission)) {
+ && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) {
Slog.wtf(LOG_TAG, "Platform runtime permission " + permission
+ " with no app op defined!");
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index cc2c9adfcba4..3492b2660c4a 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -23,6 +23,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT;
+import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED;
import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED;
import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION;
@@ -3655,6 +3656,26 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
for (String permission : pkg.getRequestedPermissions()) {
Integer permissionState = permissionStates.get(permission);
+
+ if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT)
+ && permissionState == null) {
+ final PackageStateInternal ps;
+ final long token = Binder.clearCallingIdentity();
+ try {
+ ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ final String[] useFullScreenIntentPackageNames =
+ mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_useFullScreenIntentPackages);
+ final boolean canUseFullScreenIntent = (ps != null && ps.isSystem())
+ || ArrayUtils.contains(useFullScreenIntentPackageNames,
+ pkg.getPackageName());
+ permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED
+ : PERMISSION_STATE_DENIED;
+ }
+
if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) {
continue;
}
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index 401eac6e2d19..7a5664f8135e 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -316,6 +316,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
private int resolveDatasourceOp(int code, int uid, @NonNull String packageName,
@Nullable String attributionTag) {
code = resolveRecordAudioOp(code, uid);
+ code = resolveSandboxedServiceOp(code, uid);
if (attributionTag == null) {
return code;
}
@@ -439,6 +440,28 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
return code;
}
+ private int resolveSandboxedServiceOp(int code, int uid) {
+ if (!Process.isIsolated(uid) // simple check which fails-fast for the common case
+ || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) {
+ return code;
+ }
+ final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity =
+ mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity();
+ if (hotwordDetectionServiceIdentity != null
+ && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) {
+ // Upgrade the op such that no indicators is shown for camera or audio service. This
+ // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA.
+ switch (code) {
+ case AppOpsManager.OP_RECORD_AUDIO:
+ return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED;
+ case AppOpsManager.OP_CAMERA:
+ return AppOpsManager.OP_CAMERA_SANDBOXED;
+ }
+ }
+ return code;
+ }
+
+
private int resolveUid(int code, int uid) {
// The HotwordDetectionService is an isolated service, which ordinarily cannot hold
// permissions. So we allow it to assume the owning package identity for certain
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8165958bd4ef..fc6b4e9dcb20 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -222,6 +222,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener;
import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
+import com.android.server.wallpaper.WallpaperManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.DisplayPolicy;
import com.android.server.wm.DisplayRotation;
@@ -412,6 +413,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
SensorPrivacyManager mSensorPrivacyManager;
DisplayManager mDisplayManager;
DisplayManagerInternal mDisplayManagerInternal;
+
+ private WallpaperManagerInternal mWallpaperManagerInternal;
+
boolean mPreloadedRecentApps;
final Object mServiceAcquireLock = new Object();
Vibrator mVibrator; // Vibrator for giving feedback of orientation changes
@@ -5016,11 +5020,34 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return bootCompleted ? mKeyguardDrawnTimeout : 5000;
}
+ @Nullable
+ private WallpaperManagerInternal getWallpaperManagerInternal() {
+ if (mWallpaperManagerInternal == null) {
+ mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class);
+ }
+ return mWallpaperManagerInternal;
+ }
+
+ private void reportScreenTurningOnToWallpaper(int displayId) {
+ WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+ if (wallpaperManagerInternal != null) {
+ wallpaperManagerInternal.onScreenTurningOn(displayId);
+ }
+ }
+
+ private void reportScreenTurnedOnToWallpaper(int displayId) {
+ WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal();
+ if (wallpaperManagerInternal != null) {
+ wallpaperManagerInternal.onScreenTurnedOn(displayId);
+ }
+ }
+
// Called on the DisplayManager's DisplayPowerController thread.
@Override
public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) {
if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on...");
+ reportScreenTurningOnToWallpaper(displayId);
if (displayId == DEFAULT_DISPLAY) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn",
0 /* cookie */);
@@ -5061,6 +5088,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public void screenTurnedOn(int displayId) {
if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on...");
+ reportScreenTurnedOnToWallpaper(displayId);
+
if (displayId != DEFAULT_DISPLAY) {
return;
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 2e8a150f2b6d..e392c24026a7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -667,15 +667,15 @@ public final class PowerManagerService extends SystemService
// but the DreamService has not yet been told to start (it's an async process).
private boolean mDozeStartInProgress;
- // Whether to keep dreaming when the device is undocked.
- private boolean mKeepDreamingWhenUndocked;
+ // Whether to keep dreaming when the device is unplugging.
+ private boolean mKeepDreamingWhenUnplugging;
private final class DreamManagerStateListener implements
DreamManagerInternal.DreamManagerStateListener {
@Override
- public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) {
+ public void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) {
synchronized (mLock) {
- mKeepDreamingWhenUndocked = keepDreaming;
+ mKeepDreamingWhenUnplugging = keepDreaming;
}
}
}
@@ -2504,14 +2504,12 @@ public final class PowerManagerService extends SystemService
return false;
}
- // Don't wake when undocking while dreaming if configured not to.
- if (mKeepDreamingWhenUndocked
+ // Don't wake when unplugging while dreaming if configured not to.
+ if (mKeepDreamingWhenUnplugging
&& getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING
- && wasPowered && !mIsPowered
- && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) {
+ && wasPowered && !mIsPowered) {
return false;
}
-
// Don't wake when undocked from wireless charger.
// See WirelessChargerDetector for justification.
if (wasPowered && !mIsPowered
@@ -4477,7 +4475,7 @@ public final class PowerManagerService extends SystemService
+ mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig);
pw.println(" mTheaterModeEnabled="
+ mTheaterModeEnabled);
- pw.println(" mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked);
+ pw.println(" mKeepDreamingWhenUnplugging=" + mKeepDreamingWhenUnplugging);
pw.println(" mSuspendWhenScreenOffDueToProximityConfig="
+ mSuspendWhenScreenOffDueToProximityConfig);
pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig);
diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
index 32f1bcfaad76..546dc819b4e6 100644
--- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java
+++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
@@ -295,11 +295,18 @@ public final class ShutdownCheckPoints {
@Nullable
String getProcessName() {
try {
- List<ActivityManager.RunningAppProcessInfo> runningProcesses =
- mActivityManager.getRunningAppProcesses();
- for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
- if (processInfo.pid == mCallerProcessId) {
- return processInfo.processName;
+ List<ActivityManager.RunningAppProcessInfo> runningProcesses = null;
+ if (mActivityManager != null) {
+ runningProcesses = mActivityManager.getRunningAppProcesses();
+ } else {
+ Slog.v(TAG, "No ActivityManager available to find process name with pid="
+ + mCallerProcessId);
+ }
+ if (runningProcesses != null) {
+ for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
+ if (processInfo.pid == mCallerProcessId) {
+ return processInfo.processName;
+ }
}
}
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index c2d4ac694c39..b1430e7138e7 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -703,17 +703,20 @@ public final class ShutdownThread extends Thread {
// vibrate before shutting down
Vibrator vibrator = new SystemVibrator(context);
try {
- vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
+ if (vibrator.hasVibrator()) {
+ vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES);
+ // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
+ try {
+ Thread.sleep(SHUTDOWN_VIBRATE_MS);
+ } catch (InterruptedException unused) {
+ // this is not critical and does not require logging
+ }
+ }
} catch (Exception e) {
// Failure to vibrate shouldn't interrupt shutdown. Just log it.
Log.w(TAG, "Failed to vibrate during shutdown.", e);
}
- // vibrator is asynchronous so we need to wait to avoid shutting down too soon.
- try {
- Thread.sleep(SHUTDOWN_VIBRATE_MS);
- } catch (InterruptedException unused) {
- }
}
// Shutdown power
Log.i(TAG, "Performing low-level shutdown...");
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
index 231ffc6464c1..1d63489f3c4f 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
@@ -55,7 +56,8 @@ public class CpuWakeupStats {
private static final String TAG = "CpuWakeupStats";
private static final String SUBSYSTEM_ALARM_STRING = "Alarm";
- private static final String SUBSYSTEM_ALARM_WIFI = "Wifi";
+ private static final String SUBSYSTEM_WIFI_STRING = "Wifi";
+ private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger";
private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution";
@VisibleForTesting
static final long WAKEUP_REASON_HALF_WINDOW_MS = 500;
@@ -91,12 +93,24 @@ public class CpuWakeupStats {
mConfig.register(new HandlerExecutor(mHandler));
}
+ private static int typeToStatsType(int wakeupType) {
+ switch (wakeupType) {
+ case Wakeup.TYPE_ABNORMAL:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_ABNORMAL;
+ case Wakeup.TYPE_IRQ:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ;
+ }
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN;
+ }
+
private static int subsystemToStatsReason(int subsystem) {
switch (subsystem) {
case CPU_WAKEUP_SUBSYSTEM_ALARM:
return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM;
case CPU_WAKEUP_SUBSYSTEM_WIFI:
return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__WIFI;
+ case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER:
+ return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SOUND_TRIGGER;
}
return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN;
}
@@ -144,7 +158,7 @@ public class CpuWakeupStats {
}
}
FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED,
- FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ,
+ typeToStatsType(wakeupToLog.mType),
subsystemToStatsReason(subsystem),
uids,
wakeupToLog.mElapsedMillis,
@@ -524,8 +538,10 @@ public class CpuWakeupStats {
switch (rawSubsystem) {
case SUBSYSTEM_ALARM_STRING:
return CPU_WAKEUP_SUBSYSTEM_ALARM;
- case SUBSYSTEM_ALARM_WIFI:
+ case SUBSYSTEM_WIFI_STRING:
return CPU_WAKEUP_SUBSYSTEM_WIFI;
+ case SUBSYSTEM_SOUND_TRIGGER_STRING:
+ return CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
}
return CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
}
@@ -535,25 +551,43 @@ public class CpuWakeupStats {
case CPU_WAKEUP_SUBSYSTEM_ALARM:
return SUBSYSTEM_ALARM_STRING;
case CPU_WAKEUP_SUBSYSTEM_WIFI:
- return SUBSYSTEM_ALARM_WIFI;
+ return SUBSYSTEM_WIFI_STRING;
+ case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER:
+ return SUBSYSTEM_SOUND_TRIGGER_STRING;
case CPU_WAKEUP_SUBSYSTEM_UNKNOWN:
return "Unknown";
}
return "N/A";
}
- private static final class Wakeup {
+ @VisibleForTesting
+ static final class Wakeup {
private static final String PARSER_TAG = "CpuWakeupStats.Wakeup";
private static final String ABORT_REASON_PREFIX = "Abort";
- private static final Pattern sIrqPattern = Pattern.compile("^(\\d+)\\s+(\\S+)");
+ private static final Pattern sIrqPattern = Pattern.compile("^(\\-?\\d+)\\s+(\\S+)");
+
+ /**
+ * Classical interrupts, which arrive on a dedicated GPIO pin into the main CPU.
+ * Sometimes, when multiple IRQs happen close to each other, they may get batched together.
+ */
+ static final int TYPE_IRQ = 1;
+
+ /**
+ * Non-IRQ wakeups. The exact mechanism for these is unknown, except that these explicitly
+ * do not use an interrupt line or a GPIO pin.
+ */
+ static final int TYPE_ABNORMAL = 2;
+
+ int mType;
long mElapsedMillis;
long mUptimeMillis;
IrqDevice[] mDevices;
- private Wakeup(IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
+ private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis) {
+ mType = type;
+ mDevices = devices;
mElapsedMillis = elapsedMillis;
mUptimeMillis = uptimeMillis;
- mDevices = devices;
}
static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis) {
@@ -563,6 +597,7 @@ public class CpuWakeupStats {
return null;
}
+ int type = TYPE_IRQ;
int parsedDeviceCount = 0;
final IrqDevice[] parsedDevices = new IrqDevice[components.length];
@@ -574,6 +609,10 @@ public class CpuWakeupStats {
try {
line = Integer.parseInt(matcher.group(1));
device = matcher.group(2);
+ if (line < 0) {
+ // Assuming that IRQ wakeups cannot come batched with non-IRQ wakeups.
+ type = TYPE_ABNORMAL;
+ }
} catch (NumberFormatException e) {
Slog.e(PARSER_TAG,
"Exception while parsing device names from part: " + component, e);
@@ -585,15 +624,16 @@ public class CpuWakeupStats {
if (parsedDeviceCount == 0) {
return null;
}
- return new Wakeup(Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
+ return new Wakeup(type, Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis,
uptimeMillis);
}
@Override
public String toString() {
return "Wakeup{"
- + "mElapsedMillis=" + mElapsedMillis
- + ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis)
+ + "mType=" + mType
+ + ", mElapsedMillis=" + mElapsedMillis
+ + ", mUptimeMillis=" + mUptimeMillis
+ ", mDevices=" + Arrays.toString(mDevices)
+ '}';
}
diff --git a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java b/services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java
index 091d18e30ccc..8644f720c661 100644
--- a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java
+++ b/services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
import android.annotation.XmlRes;
import android.content.Context;
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 7beb1edfe51f..e437be8e01b5 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -105,36 +105,46 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
@Override
public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
@FailureReasons int failureReason, int mitigationCount) {
- // For native crashes, we will roll back any available rollbacks
+ boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
+ .getAvailableRollbacks().isEmpty();
+ int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
+
if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
- && !mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty()) {
- return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
- }
- if (getAvailableRollback(failedPackage) == null) {
- // Don't handle the notification, no rollbacks available for the package
- return PackageHealthObserverImpact.USER_IMPACT_NONE;
- } else {
+ && anyRollbackAvailable) {
+ // For native crashes, we will directly roll back any available rollbacks
+ // Note: For non-native crashes the rollback-all step has higher impact
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (mitigationCount == 1 && getAvailableRollback(failedPackage) != null) {
// Rollback is available, we may get a callback into #execute
- return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
+ } else if (mitigationCount > 1 && anyRollbackAvailable) {
+ // If any rollbacks are available, we will commit them
+ impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
}
+
+ return impact;
}
@Override
public boolean execute(@Nullable VersionedPackage failedPackage,
@FailureReasons int rollbackReason, int mitigationCount) {
if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAll());
+ mHandler.post(() -> rollbackAll(rollbackReason));
return true;
}
- RollbackInfo rollback = getAvailableRollback(failedPackage);
- if (rollback == null) {
- Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
- return false;
+ if (mitigationCount == 1) {
+ RollbackInfo rollback = getAvailableRollback(failedPackage);
+ if (rollback == null) {
+ Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage);
+ return false;
+ }
+ mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
+ } else if (mitigationCount > 1) {
+ mHandler.post(() -> rollbackAll(rollbackReason));
}
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
- // Assume rollback executed successfully
+
+ // Assume rollbacks executed successfully
return true;
}
@@ -468,7 +478,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
}
@WorkerThread
- private void rollbackAll() {
+ private void rollbackAll(@FailureReasons int rollbackReason) {
assertInWorkerThread();
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
@@ -487,7 +497,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver {
for (RollbackInfo rollback : rollbacks) {
VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
+ rollbackPackage(rollback, sample, rollbackReason);
}
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
index 584fbddee478..3699557706fd 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java
@@ -27,4 +27,10 @@ public abstract class WallpaperManagerInternal {
* Notifies the display is ready for adding wallpaper on it.
*/
public abstract void onDisplayReady(int displayId);
+
+ /** Notifies when the screen finished turning on and is visible to the user. */
+ public abstract void onScreenTurnedOn(int displayId);
+
+ /** Notifies when the screen starts turning on and is not yet visible to the user. */
+ public abstract void onScreenTurningOn(int displayId);
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b1b0c559aad4..e17866922990 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1613,6 +1613,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
public void onDisplayReady(int displayId) {
onDisplayReadyInternal(displayId);
}
+
+ @Override
+ public void onScreenTurnedOn(int displayId) {
+ notifyScreenTurnedOn(displayId);
+ }
+ @Override
+ public void onScreenTurningOn(int displayId) {
+ notifyScreenTurningOn(displayId);
+ }
}
void initialize() {
@@ -2442,6 +2451,54 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ /**
+ * Propagates screen turned on event to wallpaper engine.
+ */
+ @Override
+ public void notifyScreenTurnedOn(int displayId) {
+ synchronized (mLock) {
+ 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) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Propagate screen turning on event to wallpaper engine.
+ */
+ @Override
+ public void notifyScreenTurningOn(int displayId) {
+ synchronized (mLock) {
+ 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) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ }
+
@Override
public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) {
checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW);
@@ -3569,6 +3626,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
private void dumpWallpaper(WallpaperData wallpaper, PrintWriter pw) {
if (wallpaper == null) {
pw.println(" (null entry)");
+ return;
}
pw.print(" User "); pw.print(wallpaper.userId);
pw.print(": id="); pw.print(wallpaper.wallpaperId);
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index 83804f75afea..32f7b9682fb7 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -466,8 +466,7 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
}
boolean isAnimatingByRecents(@NonNull Task task) {
- return task.isAnimatingByRecents()
- || mService.mAtmService.getTransitionController().inRecentsTransition(task);
+ return task.isAnimatingByRecents();
}
void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index fa3a186a6153..f3001133338a 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -25,6 +25,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFI
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
+import static com.android.internal.util.DumpUtils.dumpSparseArrayValues;
import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER;
import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H;
@@ -542,15 +544,12 @@ final class AccessibilityController {
}
void dump(PrintWriter pw, String prefix) {
- for (int i = 0; i < mDisplayMagnifiers.size(); i++) {
- final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i);
- if (displayMagnifier != null) {
- displayMagnifier.dump(pw, prefix
- + "Magnification display# " + mDisplayMagnifiers.keyAt(i));
- }
- }
- pw.println(prefix
- + "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
+ dumpSparseArray(pw, prefix, mDisplayMagnifiers, "magnification display",
+ (index, key) -> pw.printf("%sDisplay #%d:", prefix + " ", key),
+ dm -> dm.dump(pw, ""));
+ dumpSparseArrayValues(pw, prefix, mWindowsForAccessibilityObserver,
+ "windows for accessibility observer");
+ mAccessibilityWindowsPopulator.dump(pw, prefix);
}
void onFocusChanged(InputTarget lastTarget, InputTarget newTarget) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
index 21b241a0d117..70f20075b48c 100644
--- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
+++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java
@@ -16,6 +16,9 @@
package com.android.server.wm;
+import static com.android.internal.util.DumpUtils.KeyDumper;
+import static com.android.internal.util.DumpUtils.ValueDumper;
+import static com.android.internal.util.DumpUtils.dumpSparseArray;
import static com.android.server.wm.utils.RegionUtils.forEachRect;
import android.annotation.NonNull;
@@ -40,6 +43,7 @@ import android.window.WindowInfosListener;
import com.android.internal.annotations.GuardedBy;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -562,6 +566,35 @@ public final class AccessibilityWindowsPopulator extends WindowInfosListener {
notifyWindowsChanged(displayIdsForWindowsChanged);
}
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix); pw.println("AccessibilityWindowsPopulator");
+ String prefix2 = prefix + " ";
+
+ pw.print(prefix2); pw.print("mWindowsNotificationEnabled: ");
+ pw.println(mWindowsNotificationEnabled);
+
+ if (mVisibleWindows.isEmpty()) {
+ pw.print(prefix2); pw.println("No visible windows");
+ } else {
+ pw.print(prefix2); pw.print(mVisibleWindows.size());
+ pw.print(" visible windows: "); pw.println(mVisibleWindows);
+ }
+ KeyDumper noKeyDumper = (i, k) -> {}; // display id is already shown on value;
+ KeyDumper displayDumper = (i, d) -> pw.printf("%sDisplay #%d: ", prefix, d);
+ // Ideally magnificationSpecDumper should use spec.dump(pw), but there is no such method
+ ValueDumper<MagnificationSpec> magnificationSpecDumper = spec -> pw.print(spec);
+
+ dumpSparseArray(pw, prefix2, mDisplayInfos, "display info", noKeyDumper, d -> pw.print(d));
+ dumpSparseArray(pw, prefix2, mInputWindowHandlesOnDisplays, "window handles on display",
+ displayDumper, list -> pw.print(list));
+ dumpSparseArray(pw, prefix2, mMagnificationSpecInverseMatrix, "magnification spec matrix",
+ noKeyDumper, matrix -> matrix.dump(pw));
+ dumpSparseArray(pw, prefix2, mCurrentMagnificationSpec, "current magnification spec",
+ noKeyDumper, magnificationSpecDumper);
+ dumpSparseArray(pw, prefix2, mPreviousMagnificationSpec, "previous magnification spec",
+ noKeyDumper, magnificationSpecDumper);
+ }
+
@GuardedBy("mLock")
private void releaseResources() {
mInputWindowHandlesOnDisplays.clear();
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index ff1c28ad1973..8bbcd2787931 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -78,8 +78,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.Bundle;
@@ -99,7 +97,6 @@ import android.window.TransitionInfo;
import com.android.internal.app.AssistUtils;
import com.android.internal.policy.IKeyguardDismissCallback;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
@@ -1144,19 +1141,11 @@ class ActivityClientController extends IActivityClientController.Stub {
// Initiate the transition.
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller,
mService.mWindowManager.mSyncEngine);
- if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Creating Pending Multiwindow Fullscreen Request: %s", transition);
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- () -> r.mTransitionController.moveToCollecting(transition),
- () -> {
- executeFullscreenRequestTransition(fullscreenRequest, callback, r,
- transition, true /* queued */);
- });
- } else {
- executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition,
- false /* queued */);
- }
+ r.mTransitionController.startCollectOrQueue(transition,
+ (deferred) -> {
+ executeFullscreenRequestTransition(fullscreenRequest, callback, r,
+ transition, deferred);
+ });
}
private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback,
@@ -1645,18 +1634,15 @@ class ActivityClientController extends IActivityClientController.Stub {
launchedFromHome = root.isLaunchSourceType(ActivityRecord.LAUNCH_SOURCE_TYPE_HOME);
}
- // If the activity is one of the main entry points for the application, then we should
+ // If the activity was launched directly from the home screen, then we should
// refrain from finishing the activity and instead move it to the back to keep it in
// memory. The requirements for this are:
// 1. The activity is the last running activity in the task.
// 2. The current activity is the base activity for the task.
- // 3. a. If the activity was launched by the home process, we trust that its intent
- // was resolved, so we check if the it is a main intent for the application.
- // b. Otherwise, we query Package Manager to verify whether the activity is a
- // launcher activity for the application.
+ // 3. The activity was launched by the home process, and is one of the main entry
+ // points for the application.
if (baseActivityIntent != null && isLastRunningActivity
- && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent))
- || isLauncherActivity(baseActivityIntent.getComponent()))) {
+ && launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) {
moveActivityTaskToBack(token, true /* nonRoot */);
return;
}
@@ -1668,31 +1654,6 @@ class ActivityClientController extends IActivityClientController.Stub {
}
}
- /**
- * Queries PackageManager to see if the given activity is one of the main entry point for the
- * application. This should not be called with the WM lock held.
- */
- @SuppressWarnings("unchecked")
- private boolean isLauncherActivity(@NonNull ComponentName activity) {
- final Intent queryIntent = new Intent(Intent.ACTION_MAIN);
- queryIntent.addCategory(Intent.CATEGORY_LAUNCHER);
- queryIntent.setPackage(activity.getPackageName());
- try {
- final ParceledListSlice<ResolveInfo> resolved =
- mService.getPackageManager().queryIntentActivities(
- queryIntent, null, 0, mContext.getUserId());
- if (resolved == null) return false;
- for (final ResolveInfo ri : resolved.getList()) {
- if (ri.getComponentInfo().getComponentName().equals(activity)) {
- return true;
- }
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to query intent activities", e);
- }
- return false;
- }
-
@Override
public void enableTaskLocaleOverride(IBinder token) {
if (UserHandle.getAppId(Binder.getCallingUid()) != SYSTEM_UID) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8346e7c83190..64e1ae5b8c9e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2865,11 +2865,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return;
}
- if (animate && mTransitionController.inCollectingTransition(startingWindow)
- && startingWindow.cancelAndRedraw()) {
+ if (animate && mTransitionController.inCollectingTransition(startingWindow)) {
// Defer remove starting window after transition start.
- // If splash screen window was in collecting, the client side is unable to draw because
- // of Session#cancelDraw, which will blocking the remove animation.
+ // The surface of app window could really show after the transition finish.
startingWindow.mSyncTransaction.addTransactionCommittedListener(Runnable::run, () -> {
synchronized (mAtmService.mGlobalLock) {
surface.remove(true);
@@ -3522,7 +3520,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean endTask = task.getTopNonFinishingActivity() == null
&& !task.isClearingToReuseTask();
- mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
+ final Transition newTransition =
+ mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);
if (isState(RESUMED)) {
if (endTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemovalStarted(
@@ -3543,8 +3542,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// the best capture timing (e.g. IME window capture),
// No need additional task capture while task is controlled by RecentsAnimation.
if (mAtmService.mWindowManager.mTaskSnapshotController != null
- && !(task.isAnimatingByRecents()
- || mTransitionController.inRecentsTransition(task))) {
+ && !task.isAnimatingByRecents()) {
final ArraySet<Task> tasks = Sets.newArraySet(task);
mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks);
mAtmService.mWindowManager.mTaskSnapshotController
@@ -3576,7 +3574,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
} else if (!isState(PAUSING)) {
if (mVisibleRequested) {
// Prepare and execute close transition.
- prepareActivityHideTransitionAnimation();
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ setVisibility(false);
+ if (newTransition != null) {
+ // This is a transition specifically for this close operation, so set
+ // ready now.
+ newTransition.setReady(mDisplayContent, true);
+ }
+ } else {
+ prepareActivityHideTransitionAnimation();
+ }
}
final boolean removedActivity = completeFinishing("finishIfPossible") == null;
@@ -7917,6 +7924,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAtmService.getTaskChangeNotificationController().notifyActivityRequestedOrientationChanged(
task.mTaskId, requestedOrientation);
+
+ mDisplayContent.getDisplayRotation().onSetRequestedOrientation();
}
/*
@@ -9760,6 +9769,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* directly with keeping its record.
*/
void restartProcessIfVisible() {
+ if (finishing) return;
Slog.i(TAG, "Request to restart process of " + this);
// Reset the existing override configuration so it can be updated according to the latest
@@ -9794,7 +9804,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mTransitionController.isShellTransitionsEnabled()) {
final Transition transition = new Transition(TRANSIT_RELAUNCH, 0 /* flags */,
mTransitionController, mWmService.mSyncEngine);
- final Runnable executeRestart = () -> {
+ mTransitionController.startCollectOrQueue(transition, (deferred) -> {
if (mState != RESTARTING_PROCESS || !attachedToProcess()) {
transition.abort();
return;
@@ -9806,14 +9816,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTransitionController.requestStartTransition(transition, task,
null /* remoteTransition */, null /* displayChange */);
scheduleStopForRestartProcess();
- };
- if (mWmService.mSyncEngine.hasActiveSync()) {
- mWmService.mSyncEngine.queueSyncSet(
- () -> mTransitionController.moveToCollecting(transition), executeRestart);
- } else {
- mTransitionController.moveToCollecting(transition);
- executeRestart.run();
- }
+ });
} else {
startFreezingScreen();
scheduleStopForRestartProcess();
@@ -10522,11 +10525,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
boolean isSyncFinished() {
- if (task != null && mTransitionController.isTransientHide(task)) {
- // The activity keeps visibleRequested but may be hidden later, so no need to wait for
- // it to be drawn.
- return true;
- }
if (!super.isSyncFinished()) return false;
if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
.isVisibilityUnknown(this)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 7c1e9071b926..bfe298653584 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -577,7 +577,6 @@ public class ActivityStartController {
final Transition transition = controller.getCollectingTransition();
if (transition != null) {
transition.setRemoteAnimationApp(r.app.getThread());
- controller.collect(task);
controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask));
}
task.moveToFront("startExistingRecents");
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d4f151f5c66d..c5e75faf2c6c 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1606,6 +1606,8 @@ class ActivityStarter {
transitionController.requestStartTransition(newTransition,
mTargetTask == null ? started.getTask() : mTargetTask,
remoteTransition, null /* displayChange */);
+ } else if (result == START_SUCCESS && mStartActivity.isState(RESUMED)) {
+ // Do nothing if the activity is started and is resumed directly.
} else if (isStarted) {
// Make the collecting transition wait until this request is ready.
transitionController.setReady(started, false);
@@ -2546,6 +2548,7 @@ class ActivityStarter {
mAvoidMoveToFront = false;
mFrozeTaskList = false;
mTransientLaunch = false;
+ mPriorAboveTask = null;
mDisplayLockAndOccluded = false;
mVoiceSession = null;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 12fe6a0dba25..064af0f3165e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -250,7 +250,6 @@ import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.TransferPipe;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.KeyguardDismissCallback;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
@@ -831,7 +830,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private final Runnable mUpdateOomAdjRunnable = new Runnable() {
@Override
public void run() {
- mAmInternal.updateOomAdj();
+ mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY);
}
};
@@ -2873,29 +2872,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
getTransitionController(), mWindowManager.mSyncEngine);
- if (mWindowManager.mSyncEngine.hasActiveSync()) {
- mWindowManager.mSyncEngine.queueSyncSet(
- () -> getTransitionController().moveToCollecting(transition),
- () -> {
- if (!task.getWindowConfiguration().canResizeTask()) {
- Slog.w(TAG, "resizeTask not allowed on task=" + task);
- transition.abort();
- return;
- }
- getTransitionController().requestStartTransition(transition, task,
- null /* remoteTransition */, null /* displayChange */);
- getTransitionController().collect(task);
- task.resize(bounds, resizeMode, preserveWindow);
- transition.setReady(task, true);
- });
- } else {
- getTransitionController().moveToCollecting(transition);
- getTransitionController().requestStartTransition(transition, task,
- null /* remoteTransition */, null /* displayChange */);
- getTransitionController().collect(task);
- task.resize(bounds, resizeMode, preserveWindow);
- transition.setReady(task, true);
- }
+ getTransitionController().startCollectOrQueue(transition,
+ (deferred) -> {
+ if (deferred && !task.getWindowConfiguration().canResizeTask()) {
+ Slog.w(TAG, "resizeTask not allowed on task=" + task);
+ transition.abort();
+ return;
+ }
+ getTransitionController().requestStartTransition(transition, task,
+ null /* remoteTransition */, null /* displayChange */);
+ getTransitionController().collect(task);
+ task.resize(bounds, resizeMode, preserveWindow);
+ transition.setReady(task, true);
+ });
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3626,34 +3615,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mActivityClientController.dismissKeyguard(r.token, new KeyguardDismissCallback() {
@Override
public void onDismissSucceeded() {
- if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Creating Pending Pip-Enter: %s", transition);
- mWindowManager.mSyncEngine.queueSyncSet(
- () -> getTransitionController().moveToCollecting(transition),
- enterPipRunnable);
- } else {
- // Move to collecting immediately to "claim" the sync-engine for this
- // transition.
- if (transition != null) {
- getTransitionController().moveToCollecting(transition);
- }
+ if (transition == null) {
mH.post(enterPipRunnable);
+ return;
}
+ getTransitionController().startCollectOrQueue(transition, (deferred) -> {
+ if (deferred) {
+ enterPipRunnable.run();
+ } else {
+ mH.post(enterPipRunnable);
+ }
+ });
}
}, null /* message */);
} else {
// Enter picture in picture immediately otherwise
- if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Creating Pending Pip-Enter: %s", transition);
- mWindowManager.mSyncEngine.queueSyncSet(
- () -> getTransitionController().moveToCollecting(transition),
- enterPipRunnable);
+ if (transition != null) {
+ getTransitionController().startCollectOrQueue(transition,
+ (deferred) -> enterPipRunnable.run());
} else {
- if (transition != null) {
- getTransitionController().moveToCollecting(transition);
- }
enterPipRunnable.run();
}
}
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 48cf567ba9be..d916a1be1a03 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -23,6 +23,7 @@ import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.os.Handler;
import android.os.Trace;
import android.util.ArraySet;
import android.util.Slog;
@@ -172,7 +173,7 @@ class BLASTSyncEngine {
if (ran) {
return;
}
- mWm.mH.removeCallbacks(this);
+ mHandler.removeCallbacks(this);
ran = true;
SurfaceControl.Transaction t = new SurfaceControl.Transaction();
for (WindowContainer wc : wcAwaitingCommit) {
@@ -199,13 +200,13 @@ class BLASTSyncEngine {
};
CommitCallback callback = new CommitCallback();
merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted);
- mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION);
+ mHandler.postDelayed(callback, BLAST_TIMEOUT_DURATION);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady");
mListener.onTransactionReady(mSyncId, merged);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
mActiveSyncs.remove(mSyncId);
- mWm.mH.removeCallbacks(mOnTimeout);
+ mHandler.removeCallbacks(mOnTimeout);
// Immediately start the next pending sync-transaction if there is one.
if (mActiveSyncs.size() == 0 && !mPendingSyncSets.isEmpty()) {
@@ -216,7 +217,7 @@ class BLASTSyncEngine {
throw new IllegalStateException("Pending Sync Set didn't start a sync.");
}
// Post this so that the now-playing transition setup isn't interrupted.
- mWm.mH.post(() -> {
+ mHandler.post(() -> {
synchronized (mWm.mGlobalLock) {
pt.mApplySync.run();
}
@@ -228,7 +229,7 @@ class BLASTSyncEngine {
if (mReady == ready) {
return;
}
- ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready", mSyncId);
+ ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Set ready %b", mSyncId, ready);
mReady = ready;
if (!ready) return;
mWm.mWindowPlacerLocked.requestTraversal();
@@ -269,6 +270,7 @@ class BLASTSyncEngine {
}
private final WindowManagerService mWm;
+ private final Handler mHandler;
private int mNextSyncId = 0;
private final SparseArray<SyncGroup> mActiveSyncs = new SparseArray<>();
@@ -280,7 +282,13 @@ class BLASTSyncEngine {
private final ArrayList<PendingSyncSet> mPendingSyncSets = new ArrayList<>();
BLASTSyncEngine(WindowManagerService wms) {
+ this(wms, wms.mH);
+ }
+
+ @VisibleForTesting
+ BLASTSyncEngine(WindowManagerService wms, Handler mainHandler) {
mWm = wms;
+ mHandler = mainHandler;
}
/**
@@ -305,8 +313,8 @@ class BLASTSyncEngine {
if (mActiveSyncs.size() != 0) {
// We currently only support one sync at a time, so start a new SyncGroup when there is
// another may cause issue.
- ProtoLog.w(WM_DEBUG_SYNC_ENGINE,
- "SyncGroup %d: Started when there is other active SyncGroup", s.mSyncId);
+ Slog.e(TAG, "SyncGroup " + s.mSyncId
+ + ": Started when there is other active SyncGroup");
}
mActiveSyncs.put(s.mSyncId, s);
ProtoLog.v(WM_DEBUG_SYNC_ENGINE, "SyncGroup %d: Started for listener: %s",
@@ -325,7 +333,7 @@ class BLASTSyncEngine {
@VisibleForTesting
void scheduleTimeout(SyncGroup s, long timeoutMs) {
- mWm.mH.postDelayed(s.mOnTimeout, timeoutMs);
+ mHandler.postDelayed(s.mOnTimeout, timeoutMs);
}
void addToSyncSet(int id, WindowContainer wc) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 587e7204f993..11d84ffbd064 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -286,8 +286,8 @@ class BackNavigationController {
currentActivity.getCustomAnimation(false/* open */);
if (customAppTransition != null) {
infoBuilder.setCustomAnimation(currentActivity.packageName,
- customAppTransition.mExitAnim,
customAppTransition.mEnterAnim,
+ customAppTransition.mExitAnim,
customAppTransition.mBackgroundColor);
}
}
@@ -532,14 +532,7 @@ class BackNavigationController {
if (newFocus != null && newFocus != mNavigatingWindow
&& (newFocus.mActivityRecord == null
|| (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) {
- EventLogTags.writeWmBackNaviCanceled("focusWindowChanged");
- if (isMonitorForRemote()) {
- mObserver.sendResult(null /* result */);
- }
- if (isMonitorAnimationOrTransition()) {
- // transition won't happen, cancel internal status
- clearBackAnimations();
- }
+ cancelBackNavigating("focusWindowChanged");
}
}
@@ -553,19 +546,12 @@ class BackNavigationController {
}
final ArrayList<WindowContainer> all = new ArrayList<>(opening);
all.addAll(closing);
- for (WindowContainer app : all) {
- if (app.hasChild(mNavigatingWindow)) {
- EventLogTags.writeWmBackNaviCanceled("transitionHappens");
- if (isMonitorForRemote()) {
- mObserver.sendResult(null /* result */);
- }
- if (isMonitorAnimationOrTransition()) {
- clearBackAnimations();
- }
+ for (int i = all.size() - 1; i >= 0; --i) {
+ if (all.get(i).hasChild(mNavigatingWindow)) {
+ cancelBackNavigating("transitionHappens");
break;
}
}
-
}
private boolean atSameDisplay(WindowState newFocus) {
@@ -575,6 +561,17 @@ class BackNavigationController {
final int navigatingDisplayId = mNavigatingWindow.getDisplayId();
return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId;
}
+
+ private void cancelBackNavigating(String reason) {
+ EventLogTags.writeWmBackNaviCanceled(reason);
+ if (isMonitorForRemote()) {
+ mObserver.sendResult(null /* result */);
+ }
+ if (isMonitorAnimationOrTransition()) {
+ clearBackAnimations();
+ }
+ cancelPendingAnimation();
+ }
}
// For shell transition
@@ -585,14 +582,14 @@ class BackNavigationController {
* The closing target should only exist in close list, but the opening target can be either in
* open or close list.
*/
- void onTransactionReady(Transition transition) {
+ void onTransactionReady(Transition transition, ArrayList<Transition.ChangeInfo> targets) {
if (!isMonitoringTransition()) {
return;
}
- final ArraySet<WindowContainer> targets = transition.mParticipants;
for (int i = targets.size() - 1; i >= 0; --i) {
- final WindowContainer wc = targets.valueAt(i);
- if (wc.asActivityRecord() == null && wc.asTask() == null) {
+ final WindowContainer wc = targets.get(i).mContainer;
+ if (wc.asActivityRecord() == null && wc.asTask() == null
+ && wc.asTaskFragment() == null) {
continue;
}
// WC can be visible due to setLaunchBehind
@@ -605,6 +602,9 @@ class BackNavigationController {
final boolean matchAnimationTargets = isWaitBackTransition()
&& (transition.mType == TRANSIT_CLOSE || transition.mType == TRANSIT_TO_BACK)
&& mAnimationHandler.containsBackAnimationTargets(mTmpOpenApps, mTmpCloseApps);
+ ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
+ "onTransactionReady, opening: %s, closing: %s, animating: %s, match: %b",
+ mTmpOpenApps, mTmpCloseApps, mAnimationHandler, matchAnimationTargets);
if (!matchAnimationTargets) {
mNavigationMonitor.onTransitionReadyWhileNavigate(mTmpOpenApps, mTmpCloseApps);
} else {
@@ -645,31 +645,28 @@ class BackNavigationController {
if (finishedTransition == mWaitTransitionFinish) {
clearBackAnimations();
}
+
if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) {
return false;
}
-
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Handling the deferred animation after transition finished");
- // Show the target surface and its parents to prevent it or its parents hidden when
- // the transition finished.
- // The target could be affected by transition when :
- // Open transition -> the open target in back navigation
- // Close transition -> the close target in back navigation.
+ // Find the participated container collected by transition when :
+ // Open transition -> the open target in back navigation, the close target in transition.
+ // Close transition -> the close target in back navigation, the open target in transition.
boolean hasTarget = false;
- final SurfaceControl.Transaction t =
- mPendingAnimationBuilder.mCloseTarget.getPendingTransaction();
- for (int i = 0; i < targets.size(); i++) {
- final WindowContainer wc = targets.get(i).mContainer;
- if (wc.asActivityRecord() == null && wc.asTask() == null) {
- continue;
- } else if (!mPendingAnimationBuilder.containTarget(wc)) {
+ for (int i = 0; i < finishedTransition.mParticipants.size(); i++) {
+ final WindowContainer wc = finishedTransition.mParticipants.valueAt(i);
+ if (wc.asActivityRecord() == null && wc.asTask() == null
+ && wc.asTaskFragment() == null) {
continue;
}
- hasTarget = true;
- t.show(wc.getSurfaceControl());
+ if (mPendingAnimationBuilder.containTarget(wc)) {
+ hasTarget = true;
+ break;
+ }
}
if (!hasTarget) {
@@ -677,20 +674,33 @@ class BackNavigationController {
Slog.w(TAG, "Finished transition didn't include the targets"
+ " open: " + mPendingAnimationBuilder.mOpenTarget
+ " close: " + mPendingAnimationBuilder.mCloseTarget);
- try {
- mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
- } catch (RemoteException e) {
- throw new RuntimeException(e);
- }
- mPendingAnimationBuilder = null;
+ cancelPendingAnimation();
return false;
}
+ // Ensure the final animation targets which hidden by transition could be visible.
+ for (int i = 0; i < targets.size(); i++) {
+ final WindowContainer wc = targets.get(i).mContainer;
+ wc.prepareSurfaces();
+ }
+
scheduleAnimation(mPendingAnimationBuilder);
mPendingAnimationBuilder = null;
return true;
}
+ private void cancelPendingAnimation() {
+ if (mPendingAnimationBuilder == null) {
+ return;
+ }
+ try {
+ mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote animation gone", e);
+ }
+ mPendingAnimationBuilder = null;
+ }
+
/**
* Create and handling animations status for an open/close animation targets.
*/
@@ -829,10 +839,16 @@ class BackNavigationController {
if (!mComposed) {
return false;
}
+
+ // WC must be ActivityRecord in legacy transition, but it also can be Task or
+ // TaskFragment when using Shell transition.
+ // Open target: Can be Task or ActivityRecord or TaskFragment
+ // Close target: Limit to the top activity for now, to reduce the chance of misjudgment.
final WindowContainer target = open ? mOpenAdaptor.mTarget : mCloseAdaptor.mTarget;
if (mSwitchType == TASK_SWITCH) {
return wc == target
- || (wc.asTask() != null && wc.hasChild(target));
+ || (wc.asTask() != null && wc.hasChild(target))
+ || (wc.asActivityRecord() != null && target.hasChild(wc));
} else if (mSwitchType == ACTIVITY_SWITCH) {
return wc == target || (wc.asTaskFragment() != null && wc.hasChild(target));
}
@@ -1067,7 +1083,7 @@ class BackNavigationController {
boolean containTarget(@NonNull WindowContainer wc) {
return wc == mOpenTarget || wc == mCloseTarget
- || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget);
+ || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc);
}
/**
@@ -1142,6 +1158,11 @@ class BackNavigationController {
private static void setLaunchBehind(@NonNull ActivityRecord activity) {
if (!activity.isVisibleRequested()) {
activity.setVisibility(true);
+ // The transition could commit the visibility and in the finishing state, that could
+ // skip commitVisibility call in setVisibility cause the activity won't visible here.
+ // Call it again to make sure the activity could be visible while handling the pending
+ // animation.
+ activity.commitVisibility(true, true);
}
activity.mLaunchTaskBehind = true;
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index c6db8a7acb6f..8660becf56a9 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -263,16 +263,19 @@ class Dimmer {
* {@link WindowContainer#prepareSurfaces}. After calling this, the container should
* chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
* a chance to request dims to continue.
- * @return Non-null dim bounds if the dimmer is showing.
*/
- Rect resetDimStates() {
+ void resetDimStates() {
if (mDimState == null) {
- return null;
+ return;
}
if (!mDimState.mDontReset) {
mDimState.mDimming = false;
}
- return mDimState.mDimBounds;
+ }
+
+ /** Returns non-null bounds if the dimmer is showing. */
+ Rect getDimBounds() {
+ return mDimState != null ? mDimState.mDimBounds : null;
}
void dontAnimateExit() {
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 26f56a2e5c0b..9f59f5a30caf 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -779,8 +779,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
@Override
void prepareSurfaces() {
- final Rect dimBounds = mDimmer.resetDimStates();
+ mDimmer.resetDimStates();
super.prepareSurfaces();
+ final Rect dimBounds = mDimmer.getDimBounds();
if (dimBounds != null) {
// Bounds need to be relative, as the dim layer is a child.
getBounds(dimBounds);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index bec58b848478..c2bc4591ce0d 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -775,6 +775,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
DisplayWindowPolicyControllerHelper mDwpcHelper;
+ private final DisplayRotationReversionController mRotationReversionController;
+
private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
WindowStateAnimator winAnimator = w.mWinAnimator;
final ActivityRecord activity = w.mActivityRecord;
@@ -1204,6 +1206,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ false)
? new DisplayRotationCompatPolicy(this) : null;
+ mRotationReversionController = new DisplayRotationReversionController(this);
mInputMonitor = new InputMonitor(mWmService, this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
@@ -1333,6 +1336,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
.show(mA11yOverlayLayer);
}
+ DisplayRotationReversionController getRotationReversionController() {
+ return mRotationReversionController;
+ }
+
boolean isReady() {
// The display is ready when the system and the individual display are both ready.
return mWmService.mDisplayReady && mDisplayReady;
@@ -1711,9 +1718,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
private boolean updateOrientation(boolean forceUpdate) {
+ final WindowContainer prevOrientationSource = mLastOrientationSource;
final int orientation = getOrientation();
// The last orientation source is valid only after getOrientation.
final WindowContainer orientationSource = getLastOrientationSource();
+ if (orientationSource != prevOrientationSource
+ && mRotationReversionController.isRotationReversionEnabled()) {
+ mRotationReversionController.updateForNoSensorOverride();
+ }
final ActivityRecord r =
orientationSource != null ? orientationSource.asActivityRecord() : null;
if (r != null) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 628f4d3a85d6..20048ce543f3 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -33,6 +33,9 @@ import static com.android.server.wm.DisplayRotationProto.IS_FIXED_TO_USER_ROTATI
import static com.android.server.wm.DisplayRotationProto.LAST_ORIENTATION;
import static com.android.server.wm.DisplayRotationProto.ROTATION;
import static com.android.server.wm.DisplayRotationProto.USER_ROTATION;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_HALF_FOLD;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_NOSENSOR;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_ACTIVE;
@@ -97,6 +100,8 @@ public class DisplayRotation {
// config changes and unexpected jumps while folding the device to closed state.
private static final int FOLDING_RECOMPUTE_CONFIG_DELAY_MS = 800;
+ private static final int ROTATION_UNDEFINED = -1;
+
private static class RotationAnimationPair {
@AnimRes
int mEnter;
@@ -104,6 +109,9 @@ public class DisplayRotation {
int mExit;
}
+ @Nullable
+ final FoldController mFoldController;
+
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
private final DisplayPolicy mDisplayPolicy;
@@ -125,8 +133,6 @@ public class DisplayRotation {
private OrientationListener mOrientationListener;
private StatusBarManagerInternal mStatusBarManagerInternal;
private SettingsObserver mSettingsObserver;
- @Nullable
- private FoldController mFoldController;
@NonNull
private final DeviceStateController mDeviceStateController;
@NonNull
@@ -189,6 +195,12 @@ public class DisplayRotation {
*/
private int mShowRotationSuggestions;
+ /**
+ * The most recent {@link Surface.Rotation} choice shown to the user for confirmation, or
+ * {@link #ROTATION_UNDEFINED}
+ */
+ private int mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
+
private static final int ALLOW_ALL_ROTATIONS_UNDEFINED = -1;
private static final int ALLOW_ALL_ROTATIONS_DISABLED = 0;
private static final int ALLOW_ALL_ROTATIONS_ENABLED = 1;
@@ -291,7 +303,11 @@ public class DisplayRotation {
if (mSupportAutoRotation && mContext.getResources().getBoolean(
R.bool.config_windowManagerHalfFoldAutoRotateOverride)) {
mFoldController = new FoldController();
+ } else {
+ mFoldController = null;
}
+ } else {
+ mFoldController = null;
}
}
@@ -349,6 +365,11 @@ public class DisplayRotation {
return -1;
}
+ @VisibleForTesting
+ boolean useDefaultSettingsProvider() {
+ return isDefaultDisplay;
+ }
+
/**
* Updates the configuration which may have different values depending on current user, e.g.
* runtime resource overlay.
@@ -894,7 +915,8 @@ public class DisplayRotation {
@VisibleForTesting
void setUserRotation(int userRotationMode, int userRotation) {
- if (isDefaultDisplay) {
+ mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
+ if (useDefaultSettingsProvider()) {
// We'll be notified via settings listener, so we don't need to update internal values.
final ContentResolver res = mContext.getContentResolver();
final int accelerometerRotation =
@@ -1613,6 +1635,17 @@ public class DisplayRotation {
}
}
+ /**
+ * Called from {@link ActivityRecord#setRequestedOrientation(int)}
+ */
+ void onSetRequestedOrientation() {
+ if (mCompatPolicyForImmersiveApps == null
+ || mRotationChoiceShownToUserForConfirmation == ROTATION_UNDEFINED) {
+ return;
+ }
+ mOrientationListener.onProposedRotationChanged(mRotationChoiceShownToUserForConfirmation);
+ }
+
void dump(String prefix, PrintWriter pw) {
pw.println(prefix + "DisplayRotation");
pw.println(prefix + " mCurrentAppOrientation="
@@ -1839,7 +1872,7 @@ public class DisplayRotation {
return false;
}
if (mDeviceState == DeviceStateController.DeviceState.HALF_FOLDED) {
- return !(isTabletop ^ mTabletopRotations.contains(mRotation));
+ return isTabletop == mTabletopRotations.contains(mRotation);
}
return true;
}
@@ -1863,14 +1896,17 @@ public class DisplayRotation {
return mDeviceState == DeviceStateController.DeviceState.OPEN
&& !mShouldIgnoreSensorRotation // Ignore if the hinge angle still moving
&& mInHalfFoldTransition
- && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
+ && mDisplayContent.getRotationReversionController().isOverrideActive(
+ REVERSION_TYPE_HALF_FOLD)
&& mUserRotationMode
- == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
+ == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
}
int revertOverriddenRotation() {
int savedRotation = mHalfFoldSavedRotation;
mHalfFoldSavedRotation = -1;
+ mDisplayContent.getRotationReversionController()
+ .revertOverride(REVERSION_TYPE_HALF_FOLD);
mInHalfFoldTransition = false;
return savedRotation;
}
@@ -1890,6 +1926,8 @@ public class DisplayRotation {
&& mDeviceState != DeviceStateController.DeviceState.HALF_FOLDED) {
// The device has transitioned to HALF_FOLDED state: save the current rotation and
// update the device rotation.
+ mDisplayContent.getRotationReversionController().beforeOverrideApplied(
+ REVERSION_TYPE_HALF_FOLD);
mHalfFoldSavedRotation = mRotation;
mDeviceState = newState;
// Now mFoldState is set to HALF_FOLDED, the overrideFrozenRotation function will
@@ -2012,9 +2050,11 @@ public class DisplayRotation {
mService.mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0);
dispatchProposedRotation(rotation);
if (isRotationChoiceAllowed(rotation)) {
+ mRotationChoiceShownToUserForConfirmation = rotation;
final boolean isValid = isValidRotationChoice(rotation);
sendProposedRotationChangeToStatusBarInternal(rotation, isValid);
} else {
+ mRotationChoiceShownToUserForConfirmation = ROTATION_UNDEFINED;
mService.updateRotation(false /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
@@ -2093,6 +2133,8 @@ public class DisplayRotation {
final int mHalfFoldSavedRotation;
final boolean mInHalfFoldTransition;
final DeviceStateController.DeviceState mDeviceState;
+ @Nullable final boolean[] mRotationReversionSlots;
+
@Nullable final String mDisplayRotationCompatPolicySummary;
Record(DisplayRotation dr, int fromRotation, int toRotation) {
@@ -2133,6 +2175,8 @@ public class DisplayRotation {
? null
: dc.mDisplayRotationCompatPolicy
.getSummaryForDisplayRotationHistoryRecord();
+ mRotationReversionSlots =
+ dr.mDisplayContent.getRotationReversionController().getSlotsCopy();
}
void dump(String prefix, PrintWriter pw) {
@@ -2158,6 +2202,12 @@ public class DisplayRotation {
if (mDisplayRotationCompatPolicySummary != null) {
pw.println(prefix + mDisplayRotationCompatPolicySummary);
}
+ if (mRotationReversionSlots != null) {
+ pw.println(prefix + " reversionSlots= NOSENSOR "
+ + mRotationReversionSlots[REVERSION_TYPE_NOSENSOR] + ", CAMERA "
+ + mRotationReversionSlots[REVERSION_TYPE_CAMERA_COMPAT] + " HALF_FOLD "
+ + mRotationReversionSlots[REVERSION_TYPE_HALF_FOLD]);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index fb72d6c6b56d..ae93a9496f7c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -33,6 +33,7 @@ import static android.view.Display.TYPE_INTERNAL;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+import static com.android.server.wm.DisplayRotationReversionController.REVERSION_TYPE_CAMERA_COMPAT;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -156,6 +157,11 @@ final class DisplayRotationCompatPolicy {
@ScreenOrientation
int getOrientation() {
mLastReportedOrientation = getOrientationInternal();
+ if (mLastReportedOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+ rememberOverriddenOrientationIfNeeded();
+ } else {
+ restoreOverriddenOrientationIfNeeded();
+ }
return mLastReportedOrientation;
}
@@ -277,6 +283,34 @@ final class DisplayRotationCompatPolicy {
+ " }";
}
+ private void restoreOverriddenOrientationIfNeeded() {
+ if (!isOrientationOverridden()) {
+ return;
+ }
+ if (mDisplayContent.getRotationReversionController().revertOverride(
+ REVERSION_TYPE_CAMERA_COMPAT)) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Reverting orientation after camera compat force rotation");
+ // Reset last orientation source since we have reverted the orientation.
+ mDisplayContent.mLastOrientationSource = null;
+ }
+ }
+
+ private boolean isOrientationOverridden() {
+ return mDisplayContent.getRotationReversionController().isOverrideActive(
+ REVERSION_TYPE_CAMERA_COMPAT);
+ }
+
+ private void rememberOverriddenOrientationIfNeeded() {
+ if (!isOrientationOverridden()) {
+ mDisplayContent.getRotationReversionController().beforeOverrideApplied(
+ REVERSION_TYPE_CAMERA_COMPAT);
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Saving original orientation before camera compat, last orientation is %d",
+ mDisplayContent.getLastOrientation());
+ }
+ }
+
// Refreshing only when configuration changes after rotation.
private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
Configuration lastReportedConfig) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotationReversionController.java b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
new file mode 100644
index 000000000000..d3a8a82f8f87
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationReversionController.java
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+
+import android.annotation.Nullable;
+import android.app.WindowConfiguration;
+import android.content.ActivityInfoProto;
+import android.view.Surface;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.policy.WindowManagerPolicy;
+
+/**
+ * Defines the behavior of reversion from device rotation overrides.
+ *
+ * <p>There are 3 override types:
+ * <ol>
+ * <li>The top application has {@link SCREEN_ORIENTATION_NOSENSOR} set and is rotated to
+ * {@link ROTATION_0}.
+ * <li>Camera compat treatment has rotated the app {@link DisplayRotationCompatPolicy}.
+ * <li>The device is half-folded and has auto-rotate is temporarily enabled.
+ * </ol>
+ *
+ * <p>Before an override is enabled, a component should call {@code beforeOverrideApplied}. When
+ * it wishes to revert, it should call {@code revertOverride}. The user rotation will be restored
+ * if there are no other overrides present.
+ */
+final class DisplayRotationReversionController {
+
+ static final int REVERSION_TYPE_NOSENSOR = 0;
+ static final int REVERSION_TYPE_CAMERA_COMPAT = 1;
+ static final int REVERSION_TYPE_HALF_FOLD = 2;
+ private static final int NUM_SLOTS = 3;
+
+ @Surface.Rotation
+ private int mUserRotationOverridden = WindowConfiguration.ROTATION_UNDEFINED;
+ @WindowManagerPolicy.UserRotationMode
+ private int mUserRotationModeOverridden;
+
+ private final boolean[] mSlots = new boolean[NUM_SLOTS];
+ private final DisplayContent mDisplayContent;
+
+ DisplayRotationReversionController(DisplayContent content) {
+ mDisplayContent = content;
+ }
+
+ boolean isRotationReversionEnabled() {
+ return mDisplayContent.mDisplayRotationCompatPolicy != null
+ || mDisplayContent.getDisplayRotation().mFoldController != null
+ || mDisplayContent.getIgnoreOrientationRequest();
+ }
+
+ void beforeOverrideApplied(int slotIndex) {
+ if (mSlots[slotIndex]) return;
+ maybeSaveUserRotation();
+ mSlots[slotIndex] = true;
+ }
+
+ boolean isOverrideActive(int slotIndex) {
+ return mSlots[slotIndex];
+ }
+
+ @Nullable
+ boolean[] getSlotsCopy() {
+ return isRotationReversionEnabled() ? mSlots.clone() : null;
+ }
+
+ void updateForNoSensorOverride() {
+ if (!mSlots[REVERSION_TYPE_NOSENSOR]) {
+ if (isTopFullscreenActivityNoSensor()) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override detected");
+ beforeOverrideApplied(REVERSION_TYPE_NOSENSOR);
+ }
+ } else {
+ if (!isTopFullscreenActivityNoSensor()) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION, "NOSENSOR override is absent: reverting");
+ revertOverride(REVERSION_TYPE_NOSENSOR);
+ }
+ }
+ }
+
+ boolean isAnyOverrideActive() {
+ for (int i = 0; i < NUM_SLOTS; ++i) {
+ if (mSlots[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ boolean revertOverride(int slotIndex) {
+ if (!mSlots[slotIndex]) return false;
+ mSlots[slotIndex] = false;
+ if (isAnyOverrideActive()) {
+ ProtoLog.v(WM_DEBUG_ORIENTATION,
+ "Other orientation overrides are in place: not reverting");
+ return false;
+ }
+ // Only override if the rotation is frozen and there are no other active slots.
+ if (mDisplayContent.getDisplayRotation().isRotationFrozen()) {
+ mDisplayContent.getDisplayRotation().setUserRotation(
+ mUserRotationModeOverridden,
+ mUserRotationOverridden);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void maybeSaveUserRotation() {
+ if (!isAnyOverrideActive()) {
+ mUserRotationModeOverridden =
+ mDisplayContent.getDisplayRotation().getUserRotationMode();
+ mUserRotationOverridden = mDisplayContent.getDisplayRotation().getUserRotation();
+ }
+ }
+
+ private boolean isTopFullscreenActivityNoSensor() {
+ final Task topFullscreenTask =
+ mDisplayContent.getTask(
+ t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+ if (topFullscreenTask != null) {
+ final ActivityRecord topActivity =
+ topFullscreenTask.topRunningActivity();
+ return topActivity != null && topActivity.getOrientation()
+ == ActivityInfoProto.SCREEN_ORIENTATION_NOSENSOR;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 93233dd4bda8..ff1deaf415b3 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1597,11 +1597,10 @@ final class LetterboxUiController {
inheritConfiguration(firstOpaqueActivityBeneath);
mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation(
mActivityRecord, firstOpaqueActivityBeneath,
- (opaqueConfig, transparentConfig) -> {
- final Configuration mutatedConfiguration =
- fromOriginalTranslucentConfig(transparentConfig);
+ (opaqueConfig, transparentOverrideConfig) -> {
+ resetTranslucentOverrideConfig(transparentOverrideConfig);
final Rect parentBounds = parent.getWindowConfiguration().getBounds();
- final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds();
+ final Rect bounds = transparentOverrideConfig.windowConfiguration.getBounds();
final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds();
// We cannot use letterboxBounds directly here because the position relies on
// letterboxing. Using letterboxBounds directly, would produce a double offset.
@@ -1610,9 +1609,9 @@ final class LetterboxUiController {
parentBounds.top + letterboxBounds.height());
// We need to initialize appBounds to avoid NPE. The actual value will
// be set ahead when resolving the Configuration for the activity.
- mutatedConfiguration.windowConfiguration.setAppBounds(new Rect());
+ transparentOverrideConfig.windowConfiguration.setAppBounds(new Rect());
inheritConfiguration(firstOpaqueActivityBeneath);
- return mutatedConfiguration;
+ return transparentOverrideConfig;
});
}
@@ -1691,20 +1690,16 @@ final class LetterboxUiController {
true /* traverseTopToBottom */));
}
- // When overriding translucent activities configuration we need to keep some of the
- // original properties
- private Configuration fromOriginalTranslucentConfig(Configuration translucentConfig) {
- final Configuration configuration = new Configuration(translucentConfig);
+ /** Resets the screen size related fields so they can be resolved by requested bounds later. */
+ private static void resetTranslucentOverrideConfig(Configuration config) {
// The values for the following properties will be defined during the configuration
// resolution in {@link ActivityRecord#resolveOverrideConfiguration} using the
// properties inherited from the first not finishing opaque activity beneath.
- configuration.orientation = ORIENTATION_UNDEFINED;
- configuration.screenWidthDp = configuration.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
- configuration.screenHeightDp =
- configuration.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
- configuration.smallestScreenWidthDp =
- configuration.compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
- return configuration;
+ config.orientation = ORIENTATION_UNDEFINED;
+ config.screenWidthDp = config.compatScreenWidthDp = SCREEN_WIDTH_DP_UNDEFINED;
+ config.screenHeightDp = config.compatScreenHeightDp = SCREEN_HEIGHT_DP_UNDEFINED;
+ config.smallestScreenWidthDp = config.compatSmallestScreenWidthDp =
+ SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
}
private void inheritConfiguration(ActivityRecord firstOpaque) {
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f8f0211e108f..f5079d37b324 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1512,7 +1512,7 @@ class RecentTasks {
// callbacks here.
final Task removedTask = mTasks.remove(removeIndex);
if (removedTask != task) {
- if (removedTask.hasChild()) {
+ if (removedTask.hasChild() && !removedTask.isActivityTypeHome()) {
Slog.i(TAG, "Add " + removedTask + " to hidden list because adding " + task);
// A non-empty task is replaced by a new task. Because the removed task is no longer
// managed by the recent tasks list, add it to the hidden list to prevent the task
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 57fca3aeea79..237846997e9e 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -146,8 +146,6 @@ public class RecentsAnimationController implements DeathRecipient {
@VisibleForTesting
boolean mIsAddingTaskToTargets;
- @VisibleForTesting
- boolean mShouldAttachNavBarToAppDuringTransition;
private boolean mNavigationBarAttachedToApp;
private ActivityRecord mNavBarAttachedApp;
@@ -379,8 +377,6 @@ public class RecentsAnimationController implements DeathRecipient {
mDisplayId = displayId;
mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
mDisplayContent = service.mRoot.getDisplayContent(displayId);
- mShouldAttachNavBarToAppDuringTransition =
- mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition();
}
/**
@@ -577,7 +573,7 @@ public class RecentsAnimationController implements DeathRecipient {
}
private void attachNavigationBarToApp() {
- if (!mShouldAttachNavBarToAppDuringTransition
+ if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
// Skip the case where the nav bar is controlled by fade rotation.
|| mDisplayContent.getAsyncRotationController() != null) {
return;
@@ -652,7 +648,7 @@ public class RecentsAnimationController implements DeathRecipient {
}
void animateNavigationBarForAppLaunch(long duration) {
- if (!mShouldAttachNavBarToAppDuringTransition
+ if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
// Skip the case where the nav bar is controlled by fade rotation.
|| mDisplayContent.getAsyncRotationController() != null
|| mNavigationBarAttachedToApp
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 07daa4b22ac9..cd4b3c565a41 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2328,19 +2328,23 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// this as a signal to the transition-player.
final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */,
display.mTransitionController, mWmService.mSyncEngine);
- final Runnable sendSleepTransition = () -> {
+ final TransitionController.OnStartCollect sendSleepTransition = (deferred) -> {
display.mTransitionController.requestStartTransition(transition,
null /* trigger */, null /* remote */, null /* display */);
// Force playing immediately so that unrelated ops can't be collected.
transition.playNow();
};
- if (display.mTransitionController.isCollecting()) {
- mWmService.mSyncEngine.queueSyncSet(
- () -> display.mTransitionController.moveToCollecting(transition),
- sendSleepTransition);
- } else {
+ if (!display.mTransitionController.isCollecting()) {
+ // Since this bypasses sync, submit directly ignoring whether sync-engine
+ // is active.
+ if (mWindowManager.mSyncEngine.hasActiveSync()) {
+ Slog.w(TAG, "Ongoing sync outside of a transition.");
+ }
display.mTransitionController.moveToCollecting(transition);
- sendSleepTransition.run();
+ sendSleepTransition.onCollectStarted(false /* deferred */);
+ } else {
+ display.mTransitionController.startCollectOrQueue(transition,
+ sendSleepTransition);
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 0857898ca1d2..9363eb5cefc6 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -188,7 +188,6 @@ import android.window.WindowContainerToken;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
-import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -3006,7 +3005,8 @@ class Task extends TaskFragment {
/** Checking if self or its child tasks are animated by recents animation. */
boolean isAnimatingByRecents() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS);
+ return isAnimating(CHILDREN, ANIMATION_TYPE_RECENTS)
+ || mTransitionController.isTransientHide(this);
}
WindowState getTopVisibleAppMainWindow() {
@@ -3252,9 +3252,10 @@ class Task extends TaskFragment {
@Override
void prepareSurfaces() {
- final Rect dimBounds = mDimmer.resetDimStates();
+ mDimmer.resetDimStates();
super.prepareSurfaces();
+ final Rect dimBounds = mDimmer.getDimBounds();
if (dimBounds != null) {
getDimBounds(dimBounds);
@@ -4687,7 +4688,7 @@ class Task extends TaskFragment {
if (!isAttached()) {
return;
}
- mTransitionController.collect(this);
+ mTransitionController.recordTaskOrder(this);
final TaskDisplayArea taskDisplayArea = getDisplayArea();
@@ -5631,30 +5632,20 @@ class Task extends TaskFragment {
final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */,
mTransitionController, mWmService.mSyncEngine);
// Guarantee that this gets its own transition by queueing on SyncEngine
- if (mWmService.mSyncEngine.hasActiveSync()) {
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Creating Pending Move-to-back: %s", transition);
- mWmService.mSyncEngine.queueSyncSet(
- () -> mTransitionController.moveToCollecting(transition),
- () -> {
- // Need to check again since this happens later and the system might
- // be in a different state.
- if (!canMoveTaskToBack(tr)) {
- Slog.e(TAG, "Failed to move task to back after saying we could: "
- + tr.mTaskId);
- transition.abort();
- return;
- }
- mTransitionController.requestStartTransition(transition, tr,
- null /* remoteTransition */, null /* displayChange */);
- moveTaskToBackInner(tr);
- });
- } else {
- mTransitionController.moveToCollecting(transition);
- mTransitionController.requestStartTransition(transition, tr,
- null /* remoteTransition */, null /* displayChange */);
- moveTaskToBackInner(tr);
- }
+ mTransitionController.startCollectOrQueue(transition,
+ (deferred) -> {
+ // Need to check again if deferred since the system might
+ // be in a different state.
+ if (deferred && !canMoveTaskToBack(tr)) {
+ Slog.e(TAG, "Failed to move task to back after saying we could: "
+ + tr.mTaskId);
+ transition.abort();
+ return;
+ }
+ mTransitionController.requestStartTransition(transition, tr,
+ null /* remoteTransition */, null /* displayChange */);
+ moveTaskToBackInner(tr);
+ });
} else {
// Skip the transition for pinned task.
if (!inPinnedWindowingMode()) {
@@ -5683,6 +5674,7 @@ class Task extends TaskFragment {
// Usually resuming a top activity triggers the next app transition, but nothing's got
// resumed in this case, so we need to execute it explicitly.
mDisplayContent.executeAppTransition();
+ mDisplayContent.setFocusedApp(topActivity);
} else {
mRootWindowContainer.resumeFocusedTasksTopActivities();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1d232fe99e3c..311b9a6d2876 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2923,9 +2923,10 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return;
}
- final Rect dimBounds = mDimmer.resetDimStates();
+ mDimmer.resetDimStates();
super.prepareSurfaces();
+ final Rect dimBounds = mDimmer.getDimBounds();
if (dimBounds != null) {
// Bounds need to be relative, as the dim layer is a child.
dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 1fb353476592..76b0e7b82ba6 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -109,7 +109,7 @@ import java.util.function.Predicate;
*/
class Transition implements BLASTSyncEngine.TransactionReadyListener {
private static final String TAG = "Transition";
- private static final String TRACE_NAME_PLAY_TRANSITION = "PlayTransition";
+ private static final String TRACE_NAME_PLAY_TRANSITION = "playing";
/** The default package for resources */
private static final String DEFAULT_PACKAGE = "android";
@@ -287,7 +287,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (restoreBelow != null) {
final Task transientRootTask = activity.getRootTask();
- // Collect all visible activities which can be occluded by the transient activity to
+ // Collect all visible tasks which can be occluded by the transient activity to
// make sure they are in the participants so their visibilities can be updated when
// finishing transition.
((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> {
@@ -297,11 +297,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mTransientHideTasks.add(t);
}
if (t.isLeafTask()) {
- t.forAllActivities(r -> {
- if (r.isVisibleRequested()) {
- collect(r);
- }
- });
+ collect(t);
}
}
return t == restoreBelow;
@@ -511,8 +507,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (mParticipants.contains(wc)) return;
// Wallpaper is like in a static drawn state unless display may have changes, so exclude
// the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
- final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent);
- if (needSyncDraw) {
+ final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent))
+ // Transient-hide may be hidden later, so no need to request redraw.
+ && !isInTransientHide(wc);
+ if (needSync) {
mSyncEngine.addToSyncSet(mSyncId, wc);
}
ChangeInfo info = mChanges.get(wc);
@@ -521,10 +519,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mChanges.put(wc, info);
}
mParticipants.add(wc);
- if (wc.getDisplayContent() != null && !mTargetDisplays.contains(wc.getDisplayContent())) {
- mTargetDisplays.add(wc.getDisplayContent());
- addOnTopTasks(wc.getDisplayContent(), mOnTopTasksStart);
- }
+ recordDisplay(wc.getDisplayContent());
if (info.mShowWallpaper) {
// Collect the wallpaper token (for isWallpaper(wc)) so it is part of the sync set.
final WindowState wallpaper =
@@ -535,6 +530,20 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
+ private void recordDisplay(DisplayContent dc) {
+ if (dc == null || mTargetDisplays.contains(dc)) return;
+ mTargetDisplays.add(dc);
+ addOnTopTasks(dc, mOnTopTasksStart);
+ }
+
+ /**
+ * Records information about the initial task order. This does NOT collect anything. Call this
+ * before any ordering changes *could* occur, but it is not known yet if it will occur.
+ */
+ void recordTaskOrder(WindowContainer from) {
+ recordDisplay(from.getDisplayContent());
+ }
+
/** Adds the top non-alwaysOnTop tasks within `task` to `out`. */
private static void addOnTopTasks(Task task, ArrayList<Task> out) {
for (int i = task.getChildCount() - 1; i >= 0; --i) {
@@ -870,8 +879,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
*/
void finishTransition() {
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER) && mIsPlayerEnabled) {
- Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
- System.identityHashCode(this));
+ asyncTraceEnd(System.identityHashCode(this));
}
mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos();
mController.mLoggerHandler.post(mLogger::logOnFinish);
@@ -892,6 +900,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.mFinishingTransition = this;
if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) {
+ // Record all the now-hiding activities so that they are committed after
+ // recalculating visibilities. We just use mParticipants because we can and it will
+ // ensure proper reporting of `isInFinishTransition`.
+ for (int i = 0; i < mTransientHideTasks.size(); ++i) {
+ mTransientHideTasks.get(i).forAllActivities(r -> {
+ // Only check leaf-tasks that were collected
+ if (!mParticipants.contains(r.getTask())) return;
+ // Only concern ourselves with anything that can become invisible
+ if (!r.isVisible()) return;
+ mParticipants.add(r);
+ });
+ }
// The transient hide tasks could be occluded now, e.g. returning to home. So trigger
// the update to make the activities in the tasks invisible-requested, then the next
// step can continue to commit the visibility.
@@ -907,6 +927,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final WindowContainer<?> participant = mParticipants.valueAt(i);
final ActivityRecord ar = participant.asActivityRecord();
if (ar != null) {
+ final Task task = ar.getTask();
+ if (task == null) continue;
boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar);
// We need both the expected visibility AND current requested-visibility to be
// false. If it is expected-visible but not currently visible, it means that
@@ -925,9 +947,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (commitVisibility) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Commit activity becoming invisible: %s", ar);
- final Task task = ar.getTask();
- if (task != null && !task.isVisibleRequested()
- && mTransientLaunches != null) {
+ if (mTransientLaunches != null && !task.isVisibleRequested()) {
// If transition is transient, then snapshots are taken at end of
// transition.
mController.mSnapshotController.mTaskSnapshotController
@@ -941,7 +961,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
enterAutoPip = true;
}
}
- if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
+ final ChangeInfo changeInfo = mChanges.get(ar);
+ // Due to transient-hide, there may be some activities here which weren't in the
+ // transition.
+ if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) {
// Legacy dispatch relies on this (for now).
ar.mEnteringAnimation = visibleAtTransitionEnd;
} else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar)
@@ -952,8 +975,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
// Since transient launches don't automatically take focus, make sure we
// synchronize focus since we committed to the launch.
- if (ar.isTopRunningActivity()) {
- ar.moveFocusableActivityToTop("transitionFinished");
+ if (!task.isFocused() && ar.isTopRunningActivity()) {
+ mController.mAtm.setLastResumedActivityUncheckLocked(ar,
+ "transitionFinished");
}
}
continue;
@@ -1199,13 +1223,12 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (primaryDisplay.isKeyguardLocked()) {
mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED;
}
- // Check whether the participants were animated from back navigation.
- mController.mAtm.mBackNavigationController.onTransactionReady(this);
-
collectOrderChanges();
// Resolve the animating targets from the participants.
mTargets = calculateTargets(mParticipants, mChanges);
+ // Check whether the participants were animated from back navigation.
+ mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
info.setDebugId(mSyncId);
@@ -1322,8 +1345,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
mController.getTransitionPlayer().onTransitionReady(
mToken, info, transaction, mFinishTransaction);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
- Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, TRACE_NAME_PLAY_TRANSITION,
- System.identityHashCode(this));
+ asyncTraceBegin(TRACE_NAME_PLAY_TRANSITION, System.identityHashCode(this));
}
} catch (RemoteException e) {
// If there's an exception when trying to send the mergedTransaction to the
@@ -1598,7 +1620,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" id=" + mSyncId);
sb.append(" type=" + transitTypeToString(mType));
- sb.append(" flags=" + mFlags);
+ sb.append(" flags=0x" + Integer.toHexString(mFlags));
sb.append('}');
return sb.toString();
}
@@ -2310,6 +2332,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
return isCollecting() && mSyncId >= 0;
}
+ static void asyncTraceBegin(@NonNull String name, int cookie) {
+ Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, name, cookie);
+ }
+
+ static void asyncTraceEnd(int cookie) {
+ Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_WINDOW_MANAGER, TAG, cookie);
+ }
+
@VisibleForTesting
static class ChangeInfo {
private static final int FLAG_NONE = 0;
@@ -2805,7 +2835,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc;
SurfaceControl snapshotSurface = wc.makeAnimationLeash()
.setName(name)
- .setOpaque(true)
+ .setOpaque(wc.fillsParent())
.setParent(wc.getSurfaceControl())
.setSecure(screenshotBuffer.containsSecureLayers())
.setCallsite("Transition.ScreenshotSync")
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index bcb8c46de5ed..cbb4fe2eaa21 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -37,7 +37,6 @@ import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.Trace;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.TimeUtils;
@@ -334,28 +333,6 @@ class TransitionController {
return inCollectingTransition(wc) || inPlayingTransition(wc);
}
- boolean inRecentsTransition(@NonNull WindowContainer wc) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- // TODO(b/221417431): replace this with deterministic snapshots
- if (mCollectingTransition == null) break;
- if ((mCollectingTransition.getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
- && mCollectingTransition.mParticipants.contains(wc)) {
- return true;
- }
- }
-
- for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- // TODO(b/221417431): replace this with deterministic snapshots
- if ((mPlayingTransitions.get(i).getFlags() & TRANSIT_FLAG_IS_RECENTS) != 0
- && mPlayingTransitions.get(i).mParticipants.contains(p)) {
- return true;
- }
- }
- }
- return false;
- }
-
/** @return {@code true} if wc is in a participant subtree */
boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
@@ -466,26 +443,36 @@ class TransitionController {
return type == TRANSIT_OPEN || type == TRANSIT_CLOSE;
}
- /** Whether the display change should run with blast sync. */
- private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) {
- if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) {
+ /** Sets the sync method for the display change. */
+ private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange,
+ @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) {
+ final int startRotation = displayChange.getStartRotation();
+ final int endRotation = displayChange.getEndRotation();
+ if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) {
// 180 degrees rotation change may not change screen size. So the clients may draw
// some frames before and after the display projection transaction is applied by the
// remote player. That may cause some buffers to show in different rotation. So use
// sync method to pause clients drawing until the projection transaction is applied.
- return true;
+ mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(),
+ BLASTSyncEngine.METHOD_BLAST);
}
final Rect startBounds = displayChange.getStartAbsBounds();
final Rect endBounds = displayChange.getEndAbsBounds();
- if (startBounds == null || endBounds == null) return false;
+ if (startBounds == null || endBounds == null) return;
final int startWidth = startBounds.width();
final int startHeight = startBounds.height();
final int endWidth = endBounds.width();
final int endHeight = endBounds.height();
// This is changing screen resolution. Because the screen decor layers are excluded from
// screenshot, their draw transactions need to run with the start transaction.
- return (endWidth > startWidth) == (endHeight > startHeight)
- && (endWidth != startWidth || endHeight != startHeight);
+ if ((endWidth > startWidth) == (endHeight > startHeight)
+ && (endWidth != startWidth || endHeight != startHeight)) {
+ displayContent.forAllWindows(w -> {
+ if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) {
+ w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST;
+ }
+ }, true /* traverseTopToBottom */);
+ }
}
/**
@@ -517,9 +504,9 @@ class TransitionController {
} else {
newTransition = requestStartTransition(createTransition(type, flags),
trigger != null ? trigger.asTask() : null, remoteTransition, displayChange);
- if (newTransition != null && displayChange != null && shouldSync(displayChange)) {
- mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(),
- BLASTSyncEngine.METHOD_BLAST);
+ if (newTransition != null && displayChange != null && trigger != null
+ && trigger.asDisplayContent() != null) {
+ setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent());
}
}
if (trigger != null) {
@@ -577,12 +564,16 @@ class TransitionController {
return transition;
}
- /** Requests transition for a window container which will be removed or invisible. */
- void requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
- if (mTransitionPlayer == null) return;
+ /**
+ * Requests transition for a window container which will be removed or invisible.
+ * @return the new transition if it was created for this request, `null` otherwise.
+ */
+ Transition requestCloseTransitionIfNeeded(@NonNull WindowContainer<?> wc) {
+ if (mTransitionPlayer == null) return null;
+ Transition out = null;
if (wc.isVisibleRequested()) {
if (!isCollecting()) {
- requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
+ out = requestStartTransition(createTransition(TRANSIT_CLOSE, 0 /* flags */),
wc.asTask(), null /* remoteTransition */, null /* displayChange */);
}
collectExistenceChange(wc);
@@ -591,6 +582,7 @@ class TransitionController {
// collecting, this should be a member just in case.
collect(wc);
}
+ return out;
}
/** @see Transition#collect */
@@ -605,6 +597,12 @@ class TransitionController {
mCollectingTransition.collectExistenceChange(wc);
}
+ /** @see Transition#recordTaskOrder */
+ void recordTaskOrder(@NonNull WindowContainer wc) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.recordTaskOrder(wc);
+ }
+
/**
* Collects the window containers which need to be synced with the changing display area into
* the current collecting transition.
@@ -762,12 +760,12 @@ class TransitionController {
// happening in app), so pause task snapshot persisting to not increase the load.
mAtm.mWindowManager.mSnapshotController.setPause(true);
mAnimatingState = true;
- Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+ Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
} else if (!animatingState && mAnimatingState) {
t.setEarlyWakeupEnd();
mAtm.mWindowManager.mSnapshotController.setPause(false);
mAnimatingState = false;
- Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, "transitAnim", 0);
+ Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
}
}
@@ -886,6 +884,32 @@ class TransitionController {
proto.end(token);
}
+ /** Returns {@code true} if it started collecting, {@code false} if it was queued. */
+ boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) {
+ if (mAtm.mWindowManager.mSyncEngine.hasActiveSync()) {
+ if (!isCollecting()) {
+ Slog.w(TAG, "Ongoing Sync outside of transition.");
+ }
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN,
+ "Queueing transition: %s", transit);
+ mAtm.mWindowManager.mSyncEngine.queueSyncSet(
+ // Make sure to collect immediately to prevent another transition
+ // from sneaking in before it. Note: moveToCollecting internally
+ // calls startSyncSet.
+ () -> moveToCollecting(transit),
+ () -> onStartCollect.onCollectStarted(true /* deferred */));
+ return false;
+ } else {
+ moveToCollecting(transit);
+ onStartCollect.onCollectStarted(false /* deferred */);
+ return true;
+ }
+ }
+
+ interface OnStartCollect {
+ void onCollectStarted(boolean deferred);
+ }
+
/**
* This manages the animating state of processes that are running remote animations for
* {@link #mTransitionPlayerProc}.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 41176410a789..cf6efd28acb7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4047,7 +4047,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
final Configuration mergedConfiguration =
configurationMerger != null
? configurationMerger.merge(mergedOverrideConfig,
- receiver.getConfiguration())
+ receiver.getRequestedOverrideConfiguration())
: supplier.getConfiguration();
receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index cd4d6e4f1600..8fecf111d98c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6697,9 +6697,8 @@ public class WindowManagerService extends IWindowManager.Stub
mInputManagerCallback.dump(pw, " ");
mSnapshotController.dump(pw, " ");
- if (mAccessibilityController.hasCallbacks()) {
- mAccessibilityController.dump(pw, " ");
- }
+
+ dumpAccessibilityController(pw, /* force= */ false);
if (dumpAll) {
final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
@@ -6736,6 +6735,23 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ private void dumpAccessibilityController(PrintWriter pw, boolean force) {
+ boolean hasCallbacks = mAccessibilityController.hasCallbacks();
+ if (!hasCallbacks && !force) {
+ return;
+ }
+ if (!hasCallbacks) {
+ pw.println("AccessibilityController doesn't have callbacks, but printing it anways:");
+ } else {
+ pw.println("AccessibilityController:");
+ }
+ mAccessibilityController.dump(pw, " ");
+ }
+
+ private void dumpAccessibilityLocked(PrintWriter pw) {
+ dumpAccessibilityController(pw, /* force= */ true);
+ }
+
private boolean dumpWindows(PrintWriter pw, String name, boolean dumpAll) {
final ArrayList<WindowState> windows = new ArrayList();
if ("apps".equals(name) || "visible".equals(name) || "visible-apps".equals(name)) {
@@ -6855,6 +6871,7 @@ public class WindowManagerService extends IWindowManager.Stub
pw.println(" d[isplays]: active display contents");
pw.println(" t[okens]: token list");
pw.println(" w[indows]: window list");
+ pw.println(" a11y[accessibility]: accessibility-related state");
pw.println(" package-config: installed packages having app-specific config");
pw.println(" trace: print trace status and write Winscope trace to file");
pw.println(" cmd may also be a NAME to dump windows. NAME may");
@@ -6918,6 +6935,11 @@ public class WindowManagerService extends IWindowManager.Stub
dumpWindowsLocked(pw, true, null);
}
return;
+ } else if ("accessibility".equals(cmd) || "a11y".equals(cmd)) {
+ synchronized (mGlobalLock) {
+ dumpAccessibilityLocked(pw);
+ }
+ return;
} else if ("all".equals(cmd)) {
synchronized (mGlobalLock) {
dumpWindowsLocked(pw, true, null);
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 32d54d774b40..ee86b97e9404 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -280,43 +280,31 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
applyTransaction(t, -1 /* syncId */, null, caller);
return null;
}
- // In cases where transition is already provided, the "readiness lifecycle" of the
- // transition is determined outside of this transaction. However, if this is a
- // direct call from shell, the entire transition lifecycle is contained in the
- // provided transaction and thus we can setReady immediately after apply.
- final boolean needsSetReady = transition == null && t != null;
final WindowContainerTransaction wct =
t != null ? t : new WindowContainerTransaction();
if (transition == null) {
if (type < 0) {
throw new IllegalArgumentException("Can't create transition with no type");
}
- // If there is already a collecting transition, queue up a new transition and
- // return that. The actual start and apply will then be deferred until that
- // transition starts collecting. This should almost never happen except during
- // tests.
- if (mService.mWindowManager.mSyncEngine.hasActiveSync()) {
- Slog.w(TAG, "startTransition() while one is already collecting.");
- final Transition nextTransition = new Transition(type, 0 /* flags */,
- mTransitionController, mService.mWindowManager.mSyncEngine);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Creating Pending Transition: %s", nextTransition);
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- // Make sure to collect immediately to prevent another transition
- // from sneaking in before it. Note: moveToCollecting internally
- // calls startSyncSet.
- () -> mTransitionController.moveToCollecting(nextTransition),
- () -> {
- nextTransition.start();
- applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
- if (needsSetReady) {
- nextTransition.setAllReady();
- }
- });
- return nextTransition.getToken();
- }
- transition = mTransitionController.createTransition(type);
+ // This is a direct call from shell, so the entire transition lifecycle is
+ // contained in the provided transaction if provided. Thus, we can setReady
+ // immediately after apply.
+ final boolean needsSetReady = t != null;
+ final Transition nextTransition = new Transition(type, 0 /* flags */,
+ mTransitionController, mService.mWindowManager.mSyncEngine);
+ mTransitionController.startCollectOrQueue(nextTransition,
+ (deferred) -> {
+ nextTransition.start();
+ nextTransition.mLogger.mStartWCT = wct;
+ applyTransaction(wct, -1 /*syncId*/, nextTransition, caller);
+ if (needsSetReady) {
+ nextTransition.setAllReady();
+ }
+ });
+ return nextTransition.getToken();
}
+ // The transition already started collecting before sending a request to shell,
+ // so just start here.
if (!transition.isCollecting() && !transition.isForcePlaying()) {
Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably"
+ " means Shell took too long to respond to a request. WM State may be"
@@ -327,9 +315,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
transition.start();
transition.mLogger.mStartWCT = wct;
applyTransaction(wct, -1 /*syncId*/, transition, caller);
- if (needsSetReady) {
- transition.setAllReady();
- }
+ // Since the transition is already provided, it means WMCore is determining the
+ // "readiness lifecycle" outside the provided transaction, so don't set ready here.
return transition.getToken();
}
} finally {
@@ -434,23 +421,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return;
}
- if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) {
- // Sync is for either transition or applySyncTransaction(). We don't support
- // multiple sync at the same time because it may cause conflict.
- // Create a new transition when there is no active sync to collect the changes.
- final Transition transition = mTransitionController.createTransition(type);
- if (applyTransaction(wct, -1 /* syncId */, transition, caller)
- == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
- transition.abort();
- return;
- }
- mTransitionController.requestStartTransition(transition, null /* startTask */,
- null /* remoteTransition */, null /* displayChange */);
- transition.setAllReady();
- return;
- }
-
- if (!shouldApplyIndependently) {
+ if (mService.mWindowManager.mSyncEngine.hasActiveSync()
+ && !shouldApplyIndependently) {
// Although there is an active sync, we want to apply the transaction now.
// TODO(b/232042367) Redesign the organizer update on activity callback so that we
// we will know about the transition explicitly.
@@ -469,29 +441,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return;
}
- // It is ok to queue the WCT until the sync engine is free.
- final Transition nextTransition = new Transition(type, 0 /* flags */,
+ final Transition transition = new Transition(type, 0 /* flags */,
mTransitionController, mService.mWindowManager.mSyncEngine);
- ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
- "Creating Pending Transition for TaskFragment: %s", nextTransition);
- mService.mWindowManager.mSyncEngine.queueSyncSet(
- // Make sure to collect immediately to prevent another transition
- // from sneaking in before it. Note: moveToCollecting internally
- // calls startSyncSet.
- () -> mTransitionController.moveToCollecting(nextTransition),
- () -> {
- if (mTaskFragmentOrganizerController.isValidTransaction(wct)
- && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller)
- != TRANSACT_EFFECTS_NONE
- || !nextTransition.mParticipants.isEmpty())) {
- mTransitionController.requestStartTransition(nextTransition,
- null /* startTask */, null /* remoteTransition */,
- null /* displayChange */);
- nextTransition.setAllReady();
- return;
- }
- nextTransition.abort();
- });
+ TransitionController.OnStartCollect doApply = (deferred) -> {
+ if (deferred && !mTaskFragmentOrganizerController.isValidTransaction(wct)) {
+ transition.abort();
+ return;
+ }
+ if (applyTransaction(wct, -1 /* syncId */, transition, caller)
+ == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) {
+ transition.abort();
+ return;
+ }
+ mTransitionController.requestStartTransition(transition, null /* startTask */,
+ null /* remoteTransition */, null /* displayChange */);
+ transition.setAllReady();
+ };
+ mTransitionController.startCollectOrQueue(transition, doApply);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6e3924baadf3..e5a49c3a0ee2 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5647,7 +5647,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@Override
boolean isSyncFinished() {
- if (!isVisibleRequested()) {
+ if (!isVisibleRequested() || isFullyTransparent()) {
// Don't wait for invisible windows. However, we don't alter the state in case the
// window becomes visible while the sync group is still active.
return true;
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
index 5c77aa22ece8..19a0c5e8adcb 100644
--- a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -27,7 +27,7 @@ import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.util.Log;
+import android.util.Slog;
import java.util.ArrayList;
import java.util.Set;
@@ -67,7 +67,8 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
.createNewSession(mContext, mUserId, providerInfo,
this, remoteCredentialService);
if (providerClearSession != null) {
- Log.i(TAG, "In startProviderSession - provider session created and being added");
+ Slog.d(TAG, "In startProviderSession - provider session created "
+ + "and being added for: " + providerInfo.getComponentName());
mProviders.put(providerClearSession.getComponentName().flattenToString(),
providerClearSession);
}
@@ -77,12 +78,12 @@ public final class ClearRequestSession extends RequestSession<ClearCredentialSta
@Override // from provider session
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Log.i(TAG, "in onStatusChanged with status: " + status);
+ Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
if (ProviderSession.isTerminatingStatus(status)) {
- Log.i(TAG, "in onStatusChanged terminating status");
+ Slog.d(TAG, "in onProviderStatusChanged terminating status");
onProviderTerminated(componentName);
} else if (ProviderSession.isCompletionStatus(status)) {
- Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+ Slog.d(TAG, "in onProviderStatusChanged isCompletionStatus status");
onProviderResponseComplete(componentName);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 02aaf867fa7b..5a718086d71c 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -33,7 +33,7 @@ import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
-import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
@@ -77,7 +77,8 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
.createNewSession(mContext, mUserId, providerInfo,
this, remoteCredentialService);
if (providerCreateSession != null) {
- Log.i(TAG, "In startProviderSession - provider session created and being added");
+ Slog.d(TAG, "In initiateProviderSession - provider session created and "
+ + "being added for: " + providerInfo.getComponentName());
mProviders.put(providerCreateSession.getComponentName().flattenToString(),
providerCreateSession);
}
@@ -88,14 +89,16 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
+ cancelExistingPendingIntent();
try {
- mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+ mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newCreateRequestInfo(
mRequestId, mClientRequest,
mClientAppInfo.getPackageName(),
PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
- providerDataList));
+ providerDataList);
+ mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
@@ -120,7 +123,7 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
@Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable CreateCredentialResponse response) {
- Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ Slog.d(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(mProviders.get(
componentName.flattenToString()).mProviderSessionMetric
@@ -163,13 +166,13 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Log.i(TAG, "in onProviderStatusChanged with status: " + status);
+ Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source);
// If all provider responses have been received, we can either need the UI,
// or we need to respond with error. The only other case is the entry being
// selected after the UI has been invoked which has a separate code path.
if (!isAnyProviderPending()) {
if (isUiInvocationNeeded()) {
- Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+ Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
getProviderDataAndInitiateUi();
} else {
respondToClientWithErrorAndFinish(CreateCredentialException.TYPE_NO_CREATE_OPTIONS,
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 9320dd247380..06b96eb46ac1 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -796,7 +796,7 @@ public final class CredentialManagerService
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CREDENTIAL,
DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER,
- false);
+ true);
} finally {
Binder.restoreCallingIdentity(origId);
}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 87509067f993..0dee7a44375d 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -33,7 +33,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.ResultReceiver;
import android.service.credentials.CredentialProviderInfoFactory;
-import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
@@ -124,7 +123,6 @@ public class CredentialManagerUi {
public CredentialManagerUi(Context context, int userId,
CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) {
- Log.i(TAG, "In CredentialManagerUi constructor");
mContext = context;
mUserId = userId;
mCallbacks = callbacks;
@@ -151,8 +149,6 @@ public class CredentialManagerUi {
*/
public PendingIntent createPendingIntent(
RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
- Log.i(TAG, "In createPendingIntent");
-
List<CredentialProviderInfo> allProviders =
CredentialProviderInfoFactory.getCredentialProviderServices(
mContext,
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c44e665ba699..3dba4a929859 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -30,7 +30,7 @@ import android.credentials.ui.RequestInfo;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
-import android.util.Log;
+import android.util.Slog;
import com.android.server.credentials.metrics.ProviderStatusForMetrics;
@@ -77,7 +77,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
.createNewSession(mContext, mUserId, providerInfo,
this, remoteCredentialService);
if (providerGetSession != null) {
- Log.i(TAG, "In startProviderSession - provider session created and being added");
+ Slog.d(TAG, "In startProviderSession - provider session created and "
+ + "being added for: " + providerInfo.getComponentName());
mProviders.put(providerGetSession.getComponentName().flattenToString(),
providerGetSession);
}
@@ -88,11 +89,13 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
+ cancelExistingPendingIntent();
try {
- mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent(
+ mPendingIntent = mCredentialManagerUi.createPendingIntent(
RequestInfo.newGetRequestInfo(
mRequestId, mClientRequest, mClientAppInfo.getPackageName()),
- providerDataList));
+ providerDataList);
+ mClientCallback.onPendingIntent(mPendingIntent);
} catch (RemoteException e) {
mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
@@ -116,7 +119,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
@Override
public void onFinalResponseReceived(ComponentName componentName,
@Nullable GetCredentialResponse response) {
- Log.i(TAG, "onFinalCredentialReceived from: " + componentName.flattenToString());
+ Slog.d(TAG, "onFinalResponseReceived from: " + componentName.flattenToString());
mRequestSessionMetric.collectUiResponseData(/*uiReturned=*/ true, System.nanoTime());
mRequestSessionMetric.collectChosenMetricViaCandidateTransfer(
mProviders.get(componentName.flattenToString())
@@ -160,7 +163,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
@Override
public void onProviderStatusChanged(ProviderSession.Status status,
ComponentName componentName, ProviderSession.CredentialsSource source) {
- Log.i(TAG, "in onStatusChanged with status: " + status + "and source: " + source);
+ Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: "
+ + status + ", and source: " + source);
// Auth entry was selected, and it did not have any underlying credentials
if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) {
@@ -173,7 +177,7 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest,
// or we need to respond with error. The only other case is the entry being
// selected after the UI has been invoked which has a separate code path.
if (isUiInvocationNeeded()) {
- Log.i(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
+ Slog.d(TAG, "in onProviderStatusChanged - isUiInvocationNeeded");
getProviderDataAndInitiateUi();
} else {
respondToClientWithErrorAndFinish(GetCredentialException.TYPE_NO_CREDENTIAL,
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index f274e65a20c3..9e7a87e74522 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -33,7 +33,6 @@ import android.os.CancellationSignal;
import android.os.RemoteException;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.PermissionUtils;
-import android.util.Log;
import android.util.Slog;
import java.util.ArrayList;
@@ -67,6 +66,9 @@ public class PrepareGetRequestSession extends GetRequestSession {
@Override
public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName,
ProviderSession.CredentialsSource source) {
+ Slog.d(TAG, "in onProviderStatusChanged with status: " + status + ", and "
+ + "source: " + source);
+
switch (source) {
case REMOTE_PROVIDER:
// Remote provider's status changed. We should check if all providers are done, and
@@ -123,7 +125,7 @@ public class PrepareGetRequestSession extends GetRequestSession {
hasPermission,
credentialTypes, hasAuthenticationResults, hasRemoteResults, uiIntent));
} catch (RemoteException e) {
- Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
+ Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
}
}
@@ -138,7 +140,7 @@ public class PrepareGetRequestSession extends GetRequestSession {
/*hasRemoteResults=*/ false,
/*pendingIntent=*/ null));
} catch (RemoteException e) {
- Log.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
+ Slog.e(TAG, "EXCEPTION while mPendingCallback.onResponse", e);
}
}
@@ -179,10 +181,8 @@ public class PrepareGetRequestSession extends GetRequestSession {
private PendingIntent getUiIntent() {
ArrayList<ProviderData> providerDataList = new ArrayList<>();
for (ProviderSession session : mProviders.values()) {
- Log.i(TAG, "preparing data for : " + session.getComponentName());
ProviderData providerData = session.prepareUiData();
if (providerData != null) {
- Log.i(TAG, "Provider data is not null");
providerDataList.add(providerData);
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
index 0c3d3f4ed6d2..9ec0ecd93b3c 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -26,7 +26,6 @@ import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.ClearCredentialStateRequest;
-import android.util.Log;
import android.util.Slog;
/**
@@ -81,7 +80,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS
@Override
public void onProviderResponseSuccess(@Nullable Void response) {
- Log.i(TAG, "in onProviderResponseSuccess");
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
mProviderResponseSet = true;
updateStatusAndInvokeCallback(Status.COMPLETE,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
@@ -105,7 +104,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
index 8b9255a9c56b..09433dbb0c52 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java
@@ -37,7 +37,6 @@ import android.service.credentials.CreateCredentialRequest;
import android.service.credentials.CreateEntry;
import android.service.credentials.CredentialProviderService;
import android.service.credentials.RemoteEntry;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
@@ -93,7 +92,8 @@ public final class ProviderCreateSession extends ProviderSession<
createRequestSession.mHybridService
);
}
- Log.i(TAG, "Unable to create provider session");
+ Slog.d(TAG, "Unable to create provider session for: "
+ + providerInfo.getComponentName());
return null;
}
@@ -122,7 +122,6 @@ public final class ProviderCreateSession extends ProviderSession<
return new CreateCredentialRequest(callingAppInfo, capability,
clientRequest.getCredentialData());
}
- Log.i(TAG, "Unable to create provider request - capabilities do not match");
return null;
}
@@ -146,7 +145,7 @@ public final class ProviderCreateSession extends ProviderSession<
@Override
public void onProviderResponseSuccess(
@Nullable BeginCreateCredentialResponse response) {
- Log.i(TAG, "in onProviderResponseSuccess");
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -169,7 +168,7 @@ public final class ProviderCreateSession extends ProviderSession<
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@@ -180,7 +179,6 @@ public final class ProviderCreateSession extends ProviderSession<
}
private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) {
- Log.i(TAG, "onSetInitialRemoteResponse with save entries");
mProviderResponse = response;
mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(),
response.getRemoteCreateEntry());
@@ -199,14 +197,12 @@ public final class ProviderCreateSession extends ProviderSession<
@Nullable
protected CreateCredentialProviderData prepareUiData()
throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData not in uiInvokingStatus");
+ Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
- Log.i(TAG, "In prepareUiData save entries not null");
return mProviderResponseDataHandler.toCreateCredentialProviderData();
}
return null;
@@ -218,7 +214,7 @@ public final class ProviderCreateSession extends ProviderSession<
switch (entryType) {
case SAVE_ENTRY_KEY:
if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) {
- Log.i(TAG, "Unexpected save entry key");
+ Slog.w(TAG, "Unexpected save entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -226,14 +222,14 @@ public final class ProviderCreateSession extends ProviderSession<
break;
case REMOTE_ENTRY_KEY:
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) {
- Log.i(TAG, "Unexpected remote entry key");
+ Slog.w(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
return;
}
onRemoteEntrySelected(providerPendingIntentResponse);
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -268,7 +264,7 @@ public final class ProviderCreateSession extends ProviderSession<
if (credentialResponse != null) {
mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse);
} else {
- Log.i(TAG, "onSaveEntrySelected - no response or error found in pending "
+ Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending "
+ "intent response");
invokeCallbackOnInternalInvalidState();
}
@@ -284,14 +280,14 @@ public final class ProviderCreateSession extends ProviderSession<
private CreateCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Log.i(TAG, "pendingIntentResponse is null");
+ Slog.w(TAG, "pendingIntentResponse is null");
return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS);
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
CreateCredentialException exception = PendingIntentResultHandler
.extractCreateCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Log.i(TAG, "Pending intent contains provider exception");
+ Slog.d(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -343,7 +339,7 @@ public final class ProviderCreateSession extends ProviderSession<
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
+ "checks.");
return;
}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 8d3d06469944..0c2b5633d501 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -115,7 +115,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
getRequestSession.mHybridService
);
}
- Log.i(TAG, "Unable to create provider session");
+ Slog.d(TAG, "Unable to create provider session for: "
+ + providerInfo.getComponentName());
return null;
}
@@ -146,17 +147,15 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
android.credentials.GetCredentialRequest clientRequest,
CredentialProviderInfo info
) {
+ Slog.d(TAG, "Filtering request options for: " + info.getComponentName());
List<CredentialOption> filteredOptions = new ArrayList<>();
for (CredentialOption option : clientRequest.getCredentialOptions()) {
if (providerCapabilities.contains(option.getType())
&& isProviderAllowed(option, info.getComponentName())
&& checkSystemProviderRequirement(option, info.isSystemProvider())) {
- Log.i(TAG, "In createProviderRequest - capability found : "
- + option.getType());
+ Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering"
+ + "conditions");
filteredOptions.add(option);
- } else {
- Log.i(TAG, "In createProviderRequest - capability not "
- + "found, or provider not allowed : " + option.getType());
}
}
if (!filteredOptions.isEmpty()) {
@@ -165,15 +164,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
.setCredentialOptions(
filteredOptions).build();
}
- Log.i(TAG, "In createProviderRequest - returning null");
+ Slog.d(TAG, "No options filtered");
return null;
}
private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) {
if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains(
componentName)) {
- Log.d(TAG, "Provider allow list specified but does not contain this provider: "
- + componentName.flattenToString());
+ Slog.d(TAG, "Provider allow list specified but does not contain this provider");
return false;
}
return true;
@@ -182,7 +180,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
private static boolean checkSystemProviderRequirement(CredentialOption option,
boolean isSystemProvider) {
if (option.isSystemProviderRequired() && !isSystemProvider) {
- Log.d(TAG, "System provider required, but this service is not a system provider");
+ Slog.d(TAG, "System provider required, but this service is not a system provider");
return false;
}
return true;
@@ -210,6 +208,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
/** Called when the provider response has been updated by an external source. */
@Override // Callback from the remote provider
public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) {
+ Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName);
onSetInitialRemoteResponse(response);
}
@@ -231,7 +230,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
updateStatusAndInvokeCallback(Status.SERVICE_DEAD,
/*source=*/ CredentialsSource.REMOTE_PROVIDER);
} else {
- Slog.i(TAG, "Component names different in onProviderServiceDied - "
+ Slog.w(TAG, "Component names different in onProviderServiceDied - "
+ "this should not happen");
}
}
@@ -244,13 +243,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@Override // Selection call from the request provider
protected void onUiEntrySelected(String entryType, String entryKey,
ProviderPendingIntentResponse providerPendingIntentResponse) {
- Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey);
+ Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: "
+ + entryKey);
switch (entryType) {
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mProviderResponseDataHandler
.getCredentialEntry(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Unexpected credential entry key");
+ Slog.w(TAG, "Unexpected credential entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -259,7 +259,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
case ACTION_ENTRY_KEY:
Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey);
if (actionEntry == null) {
- Log.i(TAG, "Unexpected action entry key");
+ Slog.w(TAG, "Unexpected action entry key");
invokeCallbackOnInternalInvalidState();
return;
}
@@ -269,21 +269,21 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
Action authenticationEntry = mProviderResponseDataHandler
.getAuthenticationAction(entryKey);
if (authenticationEntry == null) {
- Log.i(TAG, "Unexpected authenticationEntry key");
+ Slog.w(TAG, "Unexpected authenticationEntry key");
invokeCallbackOnInternalInvalidState();
return;
}
boolean additionalContentReceived =
onAuthenticationEntrySelected(providerPendingIntentResponse);
if (additionalContentReceived) {
- Log.i(TAG, "Additional content received - removing authentication entry");
+ Slog.d(TAG, "Additional content received - removing authentication entry");
mProviderResponseDataHandler.removeAuthenticationAction(entryKey);
if (!mProviderResponseDataHandler.isEmptyResponse()) {
updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED,
/*source=*/ CredentialsSource.AUTH_ENTRY);
}
} else {
- Log.i(TAG, "Additional content not received");
+ Slog.d(TAG, "Additional content not received from authentication entry");
mProviderResponseDataHandler
.updateAuthEntryWithNoCredentialsReceived(entryKey);
updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY,
@@ -294,12 +294,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) {
onRemoteEntrySelected(providerPendingIntentResponse);
} else {
- Log.i(TAG, "Unexpected remote entry key");
+ Slog.d(TAG, "Unexpected remote entry key");
invokeCallbackOnInternalInvalidState();
}
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
invokeCallbackOnInternalInvalidState();
}
}
@@ -320,26 +320,24 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
@Override // Call from request session to data to be shown on the UI
@Nullable
protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
- + mComponentName.flattenToString());
+ Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) {
return mProviderResponseDataHandler.toGetCredentialProviderData();
}
- Log.i(TAG, "In prepareUiData response null");
+ Slog.d(TAG, "In prepareUiData response null");
return null;
}
- private Intent setUpFillInIntent(@NonNull String id) {
+ private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) {
// TODO: Determine if we should skip this entry if entry id is not set, or is set
// but does not resolve to a valid option. For now, not skipping it because
// it may be possible that the provider adds their own extras and expects to receive
// those and complete the flow.
if (mBeginGetOptionToCredentialOptionMap.get(id) == null) {
- Log.i(TAG, "Id from Credential Entry does not resolve to a valid option");
+ Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option");
return new Intent();
}
return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST,
@@ -382,7 +380,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
getCredentialResponse);
return;
}
- Log.i(TAG, "Pending intent response contains no credential, or error");
+ Slog.d(TAG, "Pending intent response contains no credential, or error "
+ + "for a credential entry");
invokeCallbackOnInternalInvalidState();
}
@@ -390,14 +389,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
private GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- Log.i(TAG, "pendingIntentResponse is null");
return null;
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- Log.i(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
@@ -463,7 +460,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
/** Returns true if either an exception or a response is found. */
private void onActionEntrySelected(ProviderPendingIntentResponse
providerPendingIntentResponse) {
- Log.i(TAG, "onActionEntrySelected");
+ Slog.d(TAG, "onActionEntrySelected");
onCredentialEntrySelected(providerPendingIntentResponse);
}
@@ -559,7 +556,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
String id = generateUniqueId();
Entry entry = new Entry(CREDENTIAL_ENTRY_KEY,
id, credentialEntry.getSlice(),
- setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId()));
+ setUpFillInIntentWithFinalRequest(credentialEntry
+ .getBeginGetCredentialOptionId()));
mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry));
mCredentialEntryTypes.add(credentialEntry.getType());
}
@@ -574,9 +572,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
public void addAuthenticationAction(Action authenticationAction,
@AuthenticationEntry.Status int status) {
- Log.i(TAG, "In addAuthenticationAction");
String id = generateUniqueId();
- Log.i(TAG, "In addAuthenticationAction, id : " + id);
AuthenticationEntry entry = new AuthenticationEntry(
AUTHENTICATION_ACTION_ENTRY_KEY,
id, authenticationAction.getSlice(),
@@ -591,7 +587,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) {
if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it does not meet the restriction"
+ Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction"
+ " checks.");
return;
}
@@ -715,7 +711,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential
== AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT)
.findFirst();
if (previousMostRecentAuthEntry.isEmpty()) {
- Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found");
return;
}
String id = previousMostRecentAuthEntry.get().getKey();
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 24292ef2cdab..c10f5640c466 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -33,7 +33,7 @@ import android.os.ICancellationSignal;
import android.service.credentials.CallingAppInfo;
import android.service.credentials.CredentialEntry;
import android.service.credentials.CredentialProviderService;
-import android.telecom.Log;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -145,14 +145,12 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption
private List<Entry> prepareUiCredentialEntries(
@NonNull List<CredentialEntry> credentialEntries) {
- Log.i(TAG, "in prepareUiProviderDataWithCredentials");
List<Entry> credentialUiEntries = new ArrayList<>();
// Populate the credential entries
for (CredentialEntry credentialEntry : credentialEntries) {
String entryId = generateUniqueId();
mUiCredentialEntries.put(entryId, credentialEntry);
- Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
credentialEntry.getSlice(),
setUpFillInIntent()));
@@ -172,15 +170,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption
@Override
protected ProviderData prepareUiData() {
- Log.i(TAG, "In prepareUiData");
if (!ProviderSession.isUiInvokingStatus(getStatus())) {
- Log.i(TAG, "In prepareUiData - provider does not want to show UI: "
- + mComponentName.flattenToString());
+ Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString());
return null;
}
if (mProviderResponse == null) {
- Log.i(TAG, "In prepareUiData response null");
- throw new IllegalStateException("Response must be in completion mode");
+ Slog.w(TAG, "In prepareUiData but response is null. This is strange.");
+ return null;
}
return new GetCredentialProviderData.Builder(
mComponentName.flattenToString()).setActionChips(null)
@@ -200,13 +196,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption
case CREDENTIAL_ENTRY_KEY:
CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey);
if (credentialEntry == null) {
- Log.i(TAG, "Unexpected credential entry key");
+ Slog.w(TAG, "Unexpected credential entry key");
return;
}
onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse);
break;
default:
- Log.i(TAG, "Unsupported entry type selected");
+ Slog.w(TAG, "Unsupported entry type selected");
}
}
@@ -233,10 +229,8 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption
}
return;
}
-
- Log.i(TAG, "Pending intent response contains no credential, or error");
}
- Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result");
+ Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result");
}
@Override
@@ -279,14 +273,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption
protected GetCredentialException maybeGetPendingIntentException(
ProviderPendingIntentResponse pendingIntentResponse) {
if (pendingIntentResponse == null) {
- android.util.Log.i(TAG, "pendingIntentResponse is null");
return null;
}
if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) {
GetCredentialException exception = PendingIntentResultHandler
.extractGetCredentialException(pendingIntentResponse.getResultData());
if (exception != null) {
- android.util.Log.i(TAG, "Pending intent contains provider exception");
+ Slog.d(TAG, "Pending intent contains provider exception");
return exception;
}
} else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) {
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index d165756b3811..d02a8c1ee510 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -29,7 +29,6 @@ import android.credentials.ui.ProviderData;
import android.credentials.ui.ProviderPendingIntentResponse;
import android.os.ICancellationSignal;
import android.os.RemoteException;
-import android.util.Log;
import android.util.Slog;
import com.android.server.credentials.metrics.ProviderSessionMetric;
@@ -253,7 +252,7 @@ public abstract class ProviderSession<T, R>
@Nullable ComponentName expectedRemoteEntryProviderService) {
// Check if the service is the one set by the OEM. If not silently reject this entry
if (!mComponentName.equals(expectedRemoteEntryProviderService)) {
- Log.i(TAG, "Remote entry being dropped as it is not from the service "
+ Slog.w(TAG, "Remote entry being dropped as it is not from the service "
+ "configured by the OEM.");
return false;
}
@@ -270,15 +269,12 @@ public abstract class ProviderSession<T, R>
return true;
}
} catch (SecurityException e) {
- Log.i(TAG, "Error getting info for "
- + mComponentName.flattenToString() + ": " + e.getMessage());
+ Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
return false;
} catch (PackageManager.NameNotFoundException e) {
- Log.i(TAG, "Error getting info for "
- + mComponentName.flattenToString() + ": " + e.getMessage());
+ Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e);
return false;
}
- Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail");
return false;
}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index c1e9bc6a5c3c..0ad73c945284 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -40,7 +40,6 @@ import android.service.credentials.IBeginGetCredentialCallback;
import android.service.credentials.IClearCredentialStateCallback;
import android.service.credentials.ICredentialProviderService;
import android.text.format.DateUtils;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
@@ -122,7 +121,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
*/
public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request,
ProviderCallbacks<BeginGetCredentialResponse> callback) {
- Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef =
new AtomicReference<>();
@@ -142,7 +140,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
@Override
public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
String errorMsg = message == null ? "" : String.valueOf(
message);
getCredentials.completeExceptionally(
@@ -182,7 +179,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
*/
public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request,
ProviderCallbacks<BeginCreateCredentialResponse> callback) {
- Log.i(TAG, "In onCreateCredential in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
new AtomicReference<>();
@@ -197,14 +193,11 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
request, new IBeginCreateCredentialCallback.Stub() {
@Override
public void onSuccess(BeginCreateCredentialResponse response) {
- Log.i(TAG, "In onSuccess onBeginCreateCredential "
- + "in RemoteCredentialService");
createCredentialFuture.complete(response);
}
@Override
public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
String errorMsg = message == null ? "" : String.valueOf(
message);
createCredentialFuture.completeExceptionally(
@@ -244,7 +237,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
*/
public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
ProviderCallbacks<Void> callback) {
- Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
@@ -258,14 +250,11 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
request, new IClearCredentialStateCallback.Stub() {
@Override
public void onSuccess() {
- Log.i(TAG, "In onSuccess onClearCredentialState "
- + "in RemoteCredentialService");
clearCredentialFuture.complete(null);
}
@Override
public void onFailure(String errorType, CharSequence message) {
- Log.i(TAG, "In onFailure in RemoteCredentialService");
String errorMsg = message == null ? "" :
String.valueOf(message);
clearCredentialFuture.completeExceptionally(
@@ -300,35 +289,29 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr
AtomicReference<ICancellationSignal> cancellationSink,
ProviderCallbacks<T> callback) {
if (error == null) {
- Log.i(TAG, "In RemoteCredentialService execute error is null");
callback.onProviderResponseSuccess(result);
} else {
if (error instanceof TimeoutException) {
- Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+ Slog.d(TAG, "Remote provider response timed tuo for: " + mComponentName);
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_TIMEOUT,
null);
} else if (error instanceof CancellationException) {
- Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+ Slog.d(TAG, "Cancellation exception for remote provider: " + mComponentName);
dispatchCancellationSignal(cancellationSink.get());
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_TASK_CANCELED,
null);
} else if (error instanceof GetCredentialException) {
- Log.i(TAG, "In RemoteCredentialService execute error is provider get"
- + "error");
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
(GetCredentialException) error);
} else if (error instanceof CreateCredentialException) {
- Log.i(TAG, "In RemoteCredentialService execute error is provider create "
- + "error");
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_PROVIDER_FAILURE,
(CreateCredentialException) error);
} else {
- Log.i(TAG, "In RemoteCredentialService execute error is unknown");
callback.onProviderResponseFailure(
CredentialProviderErrors.ERROR_UNKNOWN,
(Exception) error);
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index e98c5241ae00..15a30e427688 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -18,6 +18,7 @@ package com.android.server.credentials;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -32,7 +33,6 @@ import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.credentials.CallingAppInfo;
-import android.util.Log;
import android.util.Slog;
import com.android.internal.R;
@@ -91,6 +91,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
private final Set<ComponentName> mEnabledProviders;
+ protected PendingIntent mPendingIntent;
+
@NonNull
protected RequestSessionStatus mRequestSessionStatus =
RequestSessionStatus.IN_PROGRESS;
@@ -179,7 +181,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
@Override // from CredentialManagerUiCallbacks
public void onUiSelection(UserSelectionDialogResult selection) {
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
+ Slog.w(TAG, "Request has already been completed. This is strange.");
return;
}
if (isSessionCancelled()) {
@@ -187,13 +189,11 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
return;
}
String providerId = selection.getProviderId();
- Log.i(TAG, "onUiSelection, providerId: " + providerId);
ProviderSession providerSession = mProviders.get(providerId);
if (providerSession == null) {
- Log.i(TAG, "providerSession not found in onUiSelection");
+ Slog.w(TAG, "providerSession not found in onUiSelection. This is strange.");
return;
}
- Log.i(TAG, "Provider session found");
mRequestSessionMetric.collectMetricPerBrowsingSelect(selection,
providerSession.mProviderSessionMetric.getCandidatePhasePerProviderMetric());
providerSession.onUiEntrySelected(selection.getEntryKey(),
@@ -205,11 +205,23 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
if (propagateCancellation) {
mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession);
}
+ cancelExistingPendingIntent();
mRequestSessionStatus = RequestSessionStatus.COMPLETE;
mProviders.clear();
clearRequestSessionLocked();
}
+ void cancelExistingPendingIntent() {
+ if (mPendingIntent != null) {
+ try {
+ mPendingIntent.cancel();
+ mPendingIntent = null;
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to cancel existing pending intent", e);
+ }
+ }
+ }
+
private void clearRequestSessionLocked() {
synchronized (mLock) {
mSessionCallback.onFinishRequestSession(mUserId, mRequestId);
@@ -247,15 +259,13 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
void getProviderDataAndInitiateUi() {
ArrayList<ProviderData> providerDataList = getProviderDataForUi();
if (!providerDataList.isEmpty()) {
- Log.i(TAG, "provider list not empty about to initiate ui");
launchUiWithProviderData(providerDataList);
}
}
@NonNull
protected ArrayList<ProviderData> getProviderDataForUi() {
- Log.i(TAG, "In getProviderDataAndInitiateUi");
- Log.i(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
+ Slog.d(TAG, "In getProviderDataAndInitiateUi providers size: " + mProviders.size());
ArrayList<ProviderData> providerDataList = new ArrayList<>();
mRequestSessionMetric.logCandidatePhaseMetrics(mProviders);
@@ -265,10 +275,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
}
for (ProviderSession session : mProviders.values()) {
- Log.i(TAG, "preparing data for : " + session.getComponentName());
ProviderData providerData = session.prepareUiData();
if (providerData != null) {
- Log.i(TAG, "Provider data is not null");
providerDataList.add(providerData);
}
}
@@ -284,7 +292,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(/*has_exception=*/ false,
ProviderStatusForMetrics.FINAL_SUCCESS);
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
+ Slog.w(TAG, "Request has already been completed. This is strange.");
return;
}
if (isSessionCancelled()) {
@@ -300,7 +308,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
} catch (RemoteException e) {
mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
/*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
- Log.i(TAG, "Issue while responding to client with a response : " + e.getMessage());
+ Slog.e(TAG, "Issue while responding to client with a response : " + e);
mRequestSessionMetric.logApiCalledAtFinish(
/*apiStatus=*/ ApiStatus.FAILURE.getMetricCode());
}
@@ -317,7 +325,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
mRequestSessionMetric.collectFinalPhaseProviderMetricStatus(
/*has_exception=*/ true, ProviderStatusForMetrics.FINAL_FAILURE);
if (mRequestSessionStatus == RequestSessionStatus.COMPLETE) {
- Log.i(TAG, "Request has already been completed. This is strange.");
+ Slog.w(TAG, "Request has already been completed. This is strange.");
return;
}
if (isSessionCancelled()) {
@@ -330,7 +338,7 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential
try {
invokeClientCallbackError(errorType, errorMsg);
} catch (RemoteException e) {
- Log.i(TAG, "Issue while responding to client with error : " + e.getMessage());
+ Slog.e(TAG, "Issue while responding to client with error : " + e);
}
boolean isUserCanceled = errorType.contains(MetricUtilities.USER_CANCELED_SUBSTRING);
mRequestSessionMetric.logFailureOrUserCancel(isUserCanceled);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
index 474df98cb1e6..950ec77f5ba8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BooleanPolicySerializer.java
@@ -32,22 +32,25 @@ import java.util.Objects;
final class BooleanPolicySerializer extends PolicySerializer<Boolean> {
+ private static final String ATTR_VALUE = "value";
+
+ private static final String TAG = "BooleanPolicySerializer";
+
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
- @NonNull Boolean value)
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull Boolean value)
throws IOException {
Objects.requireNonNull(value);
- serializer.attributeBoolean(/* namespace= */ null, attributeName, value);
+ serializer.attributeBoolean(/* namespace= */ null, ATTR_VALUE, value);
}
@Nullable
@Override
- BooleanPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ BooleanPolicyValue readFromXml(TypedXmlPullParser parser) {
try {
return new BooleanPolicyValue(
- parser.getAttributeBoolean(/* namespace= */ null, attributeName));
+ parser.getAttributeBoolean(/* namespace= */ null, ATTR_VALUE));
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing Boolean policy value", e);
+ Log.e(TAG, "Error parsing Boolean policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
index c79aac722bd7..ee73f8afabd2 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java
@@ -53,6 +53,10 @@ import java.util.Objects;
// rather than in its own files.
final class BundlePolicySerializer extends PolicySerializer<Bundle> {
+ private static final String TAG = "BundlePolicySerializer";
+
+ private static final String ATTR_FILE_NAME = "file-name";
+
private static final String RESTRICTIONS_FILE_PREFIX = "AppRestrictions_";
private static final String XML_SUFFIX = ".xml";
@@ -72,7 +76,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
@Override
void saveToXml(@NonNull PolicyKey policyKey, TypedXmlSerializer serializer,
- String attributeName, @NonNull Bundle value) throws IOException {
+ @NonNull Bundle value) throws IOException {
Objects.requireNonNull(value);
Objects.requireNonNull(policyKey);
if (!(policyKey instanceof PackagePolicyKey)) {
@@ -82,13 +86,13 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
String packageName = ((PackagePolicyKey) policyKey).getPackageName();
String fileName = packageToRestrictionsFileName(packageName, value);
writeApplicationRestrictionsLAr(fileName, value);
- serializer.attribute(/* namespace= */ null, attributeName, fileName);
+ serializer.attribute(/* namespace= */ null, ATTR_FILE_NAME, fileName);
}
@Nullable
@Override
- BundlePolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
- String fileName = parser.getAttributeValue(/* namespace= */ null, attributeName);
+ BundlePolicyValue readFromXml(TypedXmlPullParser parser) {
+ String fileName = parser.getAttributeValue(/* namespace= */ null, ATTR_FILE_NAME);
return new BundlePolicyValue(readApplicationRestrictions(fileName));
}
@@ -119,7 +123,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
final TypedXmlPullParser parser = Xml.resolvePullParser(fis);
XmlUtils.nextElement(parser);
if (parser.getEventType() != XmlPullParser.START_TAG) {
- Slog.e(DevicePolicyEngine.TAG, "Unable to read restrictions file "
+ Slog.e(TAG, "Unable to read restrictions file "
+ restrictionsFile.getBaseFile());
return restrictions;
}
@@ -127,7 +131,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
readEntry(restrictions, values, parser);
}
} catch (IOException | XmlPullParserException e) {
- Slog.w(DevicePolicyEngine.TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
+ Slog.w(TAG, "Error parsing " + restrictionsFile.getBaseFile(), e);
} finally {
IoUtils.closeQuietly(fis);
}
@@ -209,7 +213,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> {
restrictionsFile.finishWrite(fos);
} catch (Exception e) {
restrictionsFile.failWrite(fos);
- Slog.e(DevicePolicyEngine.TAG, "Error writing application restrictions list", e);
+ Slog.e(TAG, "Error writing application restrictions list", e);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
index d1c6bcb8b48c..6303a1a8b860 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ComponentNamePolicySerializer.java
@@ -30,30 +30,31 @@ import java.io.IOException;
import java.util.Objects;
final class ComponentNamePolicySerializer extends PolicySerializer<ComponentName> {
- private static final String ATTR_PACKAGE_NAME = ":package-name";
- private static final String ATTR_CLASS_NAME = ":class-name";
+
+ private static final String TAG = "ComponentNamePolicySerializer";
+
+ private static final String ATTR_PACKAGE_NAME = "package-name";
+ private static final String ATTR_CLASS_NAME = "class-name";
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull ComponentName value) throws IOException {
Objects.requireNonNull(value);
serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGE_NAME, value.getPackageName());
+ /* namespace= */ null, ATTR_PACKAGE_NAME, value.getPackageName());
serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_CLASS_NAME, value.getClassName());
+ /* namespace= */ null, ATTR_CLASS_NAME, value.getClassName());
}
@Nullable
@Override
- ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+ ComponentNamePolicyValue readFromXml(TypedXmlPullParser parser) {
String packageName = parser.getAttributeValue(
- /* namespace= */ null, attributeNamePrefix + ATTR_PACKAGE_NAME);
+ /* namespace= */ null, ATTR_PACKAGE_NAME);
String className = parser.getAttributeValue(
- /* namespace= */ null, attributeNamePrefix + ATTR_CLASS_NAME);
+ /* namespace= */ null, ATTR_CLASS_NAME);
if (packageName == null || className == null) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing ComponentName policy.");
+ Log.e(TAG, "Error parsing ComponentName policy.");
return null;
}
return new ComponentNamePolicyValue(new ComponentName(packageName, className));
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index d4f4b72fbb2b..3d5686dfe66e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -27,6 +27,7 @@ import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PAREN
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.BroadcastOptions;
import android.app.admin.DevicePolicyIdentifiers;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyState;
@@ -353,6 +354,7 @@ final class DevicePolicyEngine {
policyDefinition,
userId);
}
+ sendDevicePolicyChangedToSystem(userId);
}
/**
@@ -478,6 +480,8 @@ final class DevicePolicyEngine {
enforcingAdmin,
policyDefinition,
UserHandle.USER_ALL);
+
+ sendDevicePolicyChangedToSystem(UserHandle.USER_ALL);
}
/**
@@ -699,7 +703,7 @@ final class DevicePolicyEngine {
if (policyDefinition.isGlobalOnlyPolicy()) {
throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only"
- + "policy.");
+ + " policy.");
}
if (!mLocalPolicies.contains(userId)) {
@@ -724,7 +728,7 @@ final class DevicePolicyEngine {
private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) {
if (policyDefinition.isLocalOnlyPolicy()) {
throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only"
- + "policy.");
+ + " policy.");
}
if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) {
@@ -761,6 +765,20 @@ final class DevicePolicyEngine {
policyValue == null ? null : policyValue.getValue(), mContext, userId);
}
+ private void sendDevicePolicyChangedToSystem(int userId) {
+ Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ Bundle options = new BroadcastOptions()
+ .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+ .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+ .toBundle();
+ Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+ intent,
+ new UserHandle(userId),
+ /* receiverPermissions= */ null,
+ options));
+ }
+
private <V> void sendPolicyResultToAdmin(
EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) {
Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT);
@@ -892,7 +910,7 @@ final class DevicePolicyEngine {
mDeviceAdminServiceController.stopServicesForUser(
userId, actionForLog);
} else {
- for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) {
+ for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) {
// DPCs are handled separately in DPMS, no need to reestablish the connection here.
if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
continue;
@@ -903,6 +921,51 @@ final class DevicePolicyEngine {
}
}
+ /**
+ * Handles internal state related to a user getting started.
+ */
+ void handleStartUser(int userId) {
+ updateDeviceAdminsServicesForUser(
+ userId, /* enable= */ true, /* actionForLog= */ "start-user");
+ }
+
+ /**
+ * Handles internal state related to a user getting started.
+ */
+ void handleUnlockUser(int userId) {
+ updateDeviceAdminsServicesForUser(
+ userId, /* enable= */ true, /* actionForLog= */ "unlock-user");
+ }
+
+ /**
+ * Handles internal state related to a user getting stopped.
+ */
+ void handleStopUser(int userId) {
+ updateDeviceAdminsServicesForUser(
+ userId, /* enable= */ false, /* actionForLog= */ "stop-user");
+ }
+
+ /**
+ * Handles internal state related to packages getting updated.
+ */
+ void handlePackageChanged(@Nullable String updatedPackage, int userId) {
+ if (updatedPackage == null) {
+ return;
+ }
+ updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
+ }
+
+ /**
+ * Handles internal state related to a user getting removed.
+ */
+ void handleUserRemoved(int userId) {
+ removeLocalPoliciesForUser(userId);
+ removePoliciesForAdminsOnUser(userId);
+ }
+
+ /**
+ * Handles internal state related to a user getting created.
+ */
void handleUserCreated(UserInfo user) {
enforcePoliciesOnInheritableProfilesIfApplicable(user);
}
@@ -945,40 +1008,6 @@ final class DevicePolicyEngine {
}
/**
- * Handles internal state related to a user getting started.
- */
- void handleStartUser(int userId) {
- updateDeviceAdminsServicesForUser(
- userId, /* enable= */ true, /* actionForLog= */ "start-user");
- }
-
- /**
- * Handles internal state related to a user getting started.
- */
- void handleUnlockUser(int userId) {
- updateDeviceAdminsServicesForUser(
- userId, /* enable= */ true, /* actionForLog= */ "unlock-user");
- }
-
- /**
- * Handles internal state related to a user getting stopped.
- */
- void handleStopUser(int userId) {
- updateDeviceAdminsServicesForUser(
- userId, /* enable= */ false, /* actionForLog= */ "stop-user");
- }
-
- /**
- * Handles internal state related to packages getting updated.
- */
- void handlePackageChanged(@Nullable String updatedPackage, int userId) {
- if (updatedPackage == null) {
- return;
- }
- updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId);
- }
-
- /**
* Returns all current enforced policies set on the device, and the individual values set by
* each admin. Global policies are returned under {@link UserHandle#ALL}.
*/
@@ -1006,6 +1035,63 @@ final class DevicePolicyEngine {
return new DevicePolicyState(policies);
}
+
+ /**
+ * Removes all local and global policies set by that admin.
+ */
+ void removePoliciesForAdmin(EnforcingAdmin admin) {
+ Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet());
+ for (PolicyKey policy : globalPolicies) {
+ PolicyState<?> policyState = mGlobalPolicies.get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ removeGlobalPolicy(policyState.getPolicyDefinition(), admin);
+ }
+ }
+
+ for (int i = 0; i < mLocalPolicies.size(); i++) {
+ Set<PolicyKey> localPolicies = new HashSet<>(
+ mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(
+ mLocalPolicies.keyAt(i)).get(policy);
+ if (policyState.getPoliciesSetByAdmins().containsKey(admin)) {
+ removeLocalPolicy(
+ policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i));
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes all local policies for the provided {@code userId}.
+ */
+ private void removeLocalPoliciesForUser(int userId) {
+ Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet());
+ for (PolicyKey policy : localPolicies) {
+ PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy);
+ Set<EnforcingAdmin> admins = new HashSet<>(
+ policyState.getPoliciesSetByAdmins().keySet());
+ for (EnforcingAdmin admin : admins) {
+ removeLocalPolicy(
+ policyState.getPolicyDefinition(), admin, userId);
+ }
+ }
+
+ mLocalPolicies.remove(userId);
+ }
+
+ /**
+ * Removes all local and global policies for admins installed in the provided
+ * {@code userId}.
+ */
+ private void removePoliciesForAdminsOnUser(int userId) {
+ Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId);
+
+ for (EnforcingAdmin admin : admins) {
+ removePoliciesForAdmin(admin);
+ }
+ }
+
/**
* Reestablishes the service that handles
* {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package
@@ -1013,7 +1099,7 @@ final class DevicePolicyEngine {
*/
private void updateDeviceAdminServiceOnPackageChanged(
@NonNull String updatedPackage, int userId) {
- for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) {
+ for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) {
// DPCs are handled separately in DPMS, no need to reestablish the connection here.
if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) {
continue;
@@ -1102,7 +1188,7 @@ final class DevicePolicyEngine {
}
@NonNull
- private Set<EnforcingAdmin> getEnforcingAdminsForUser(int userId) {
+ private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) {
return mEnforcingAdmins.contains(userId)
? mEnforcingAdmins.get(userId) : Collections.emptySet();
}
@@ -1141,12 +1227,6 @@ final class DevicePolicyEngine {
}
}
- // TODO: we need to listen for user removal and package removal and update out internal policy
- // map and enforcing admins for this is be accurate.
- boolean hasActivePolicies() {
- return mEnforcingAdmins.size() > 0;
- }
-
private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition,
@NonNull EnforcingAdmin enforcingAdmin) {
if (!policyDefinition.getPolicyKey().getIdentifier().equals(
@@ -1181,7 +1261,8 @@ final class DevicePolicyEngine {
private static final String DEVICE_POLICIES_XML = "device_policy_state.xml";
private static final String TAG_LOCAL_POLICY_ENTRY = "local-policy-entry";
private static final String TAG_GLOBAL_POLICY_ENTRY = "global-policy-entry";
- private static final String TAG_ADMINS_POLICY_ENTRY = "admins-policy-entry";
+ private static final String TAG_POLICY_STATE_ENTRY = "policy-state-entry";
+ private static final String TAG_POLICY_KEY_ENTRY = "policy-key-entry";
private static final String TAG_ENFORCING_ADMINS_ENTRY = "enforcing-admins-entry";
private static final String ATTR_USER_ID = "user-id";
@@ -1236,11 +1317,14 @@ final class DevicePolicyEngine {
serializer.startTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
serializer.attributeInt(/* namespace= */ null, ATTR_USER_ID, userId);
+
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
policy.getKey().saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
- serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
policy.getValue().saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
serializer.endTag(/* namespace= */ null, TAG_LOCAL_POLICY_ENTRY);
}
@@ -1253,11 +1337,13 @@ final class DevicePolicyEngine {
for (Map.Entry<PolicyKey, PolicyState<?>> policy : mGlobalPolicies.entrySet()) {
serializer.startTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
policy.getKey().saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_KEY_ENTRY);
- serializer.startTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
policy.getValue().saveToXml(serializer);
- serializer.endTag(/* namespace= */ null, TAG_ADMINS_POLICY_ENTRY);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_STATE_ENTRY);
serializer.endTag(/* namespace= */ null, TAG_GLOBAL_POLICY_ENTRY);
}
@@ -1323,28 +1409,56 @@ final class DevicePolicyEngine {
private void readLocalPoliciesInner(TypedXmlPullParser parser)
throws XmlPullParserException, IOException {
int userId = parser.getAttributeInt(/* namespace= */ null, ATTR_USER_ID);
- PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
- if (!mLocalPolicies.contains(userId)) {
- mLocalPolicies.put(userId, new HashMap<>());
+ PolicyKey policyKey = null;
+ PolicyState<?> policyState = null;
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ switch (tag) {
+ case TAG_POLICY_KEY_ENTRY:
+ policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ break;
+ case TAG_POLICY_STATE_ENTRY:
+ policyState = PolicyState.readFromXml(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag for local policy entry" + tag);
+ }
}
- PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
- if (adminsPolicy != null) {
- mLocalPolicies.get(userId).put(policyKey, adminsPolicy);
+
+ if (policyKey != null && policyState != null) {
+ if (!mLocalPolicies.contains(userId)) {
+ mLocalPolicies.put(userId, new HashMap<>());
+ }
+ mLocalPolicies.get(userId).put(policyKey, policyState);
} else {
- Log.e(TAG,
- "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
+ Log.e(TAG, "Error parsing local policy");
}
}
private void readGlobalPoliciesInner(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
- PolicyKey policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
- PolicyState<?> adminsPolicy = parseAdminsPolicy(parser);
- if (adminsPolicy != null) {
- mGlobalPolicies.put(policyKey, adminsPolicy);
+ PolicyKey policyKey = null;
+ PolicyState<?> policyState = null;
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ String tag = parser.getName();
+ switch (tag) {
+ case TAG_POLICY_KEY_ENTRY:
+ policyKey = PolicyDefinition.readPolicyKeyFromXml(parser);
+ break;
+ case TAG_POLICY_STATE_ENTRY:
+ policyState = PolicyState.readFromXml(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag for local policy entry" + tag);
+ }
+ }
+
+ if (policyKey != null && policyState != null) {
+ mGlobalPolicies.put(policyKey, policyState);
} else {
- Log.e(TAG,
- "Error parsing file, " + policyKey + "doesn't have an " + "AdminsPolicy.");
+ Log.e(TAG, "Error parsing global policy");
}
}
@@ -1356,20 +1470,5 @@ final class DevicePolicyEngine {
}
mEnforcingAdmins.get(admin.getUserId()).add(admin);
}
-
- @Nullable
- private PolicyState<?> parseAdminsPolicy(TypedXmlPullParser parser)
- throws XmlPullParserException, IOException {
- int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- String tag = parser.getName();
- if (tag.equals(TAG_ADMINS_POLICY_ENTRY)) {
- return PolicyState.readFromXml(parser);
- }
- Log.e(TAG, "Unknown tag " + tag);
- }
- Log.e(TAG, "Error parsing file, AdminsPolicy not found");
- return null;
- }
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index d55099f604a2..c2766c200d2c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -19,6 +19,7 @@ package com.android.server.devicepolicy;
import static android.Manifest.permission.BIND_DEVICE_ADMIN;
import static android.Manifest.permission.LOCK_DEVICE;
import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
+import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL;
@@ -59,6 +60,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTI
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT;
@@ -441,7 +443,6 @@ import android.util.AtomicFile;
import android.util.DebugUtils;
import android.util.IndentingPrintWriter;
import android.util.IntArray;
-import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -855,7 +856,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG =
"enable_device_policy_engine";
- private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = false;
+ private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true;
// TODO(b/265683382) remove the flag after rollout.
private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
@@ -1177,6 +1178,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Resume logging if all remaining users are affiliated.
maybeResumeDeviceWideLoggingLocked();
}
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.handleUserRemoved(userHandle);
+ }
}
} else if (Intent.ACTION_USER_STARTED.equals(action)) {
sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STARTED, userHandle);
@@ -3663,6 +3667,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
for (Integer userId : deletedUsers) {
removeUserData(userId);
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.handleUserRemoved(userId);
+ }
}
}
@@ -4152,6 +4159,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
mInjector.binderWithCleanCallingIdentity(() ->
removeActiveAdminLocked(adminReceiver, userHandle));
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.removePoliciesForAdmin(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ adminReceiver, userHandle, admin));
+ }
}
}
@@ -7767,12 +7779,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// Explicit behaviour
if (factoryReset) {
// TODO(b/254031494) Replace with new factory reset permission checks
- boolean hasPermission = isDeviceOwnerUserId(userId)
- || (isOrganizationOwnedDeviceWithManagedProfile()
- && calledOnParentInstance);
- Preconditions.checkState(hasPermission,
- "Admin %s does not have permission to factory reset the device.",
- userId);
+ if (!isPermissionCheckFlagEnabled()) {
+ boolean hasPermission = isDeviceOwnerUserId(userId)
+ || (isOrganizationOwnedDeviceWithManagedProfile()
+ && calledOnParentInstance);
+ Preconditions.checkCallAuthorization(hasPermission,
+ "Admin %s does not have permission to factory reset the device.",
+ userId);
+ }
wipeDevice = true;
} else {
Preconditions.checkCallAuthorization(!isSystemUser,
@@ -9989,6 +10003,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
pushUserControlDisabledPackagesLocked(userId);
setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT);
+
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.removePoliciesForAdmin(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(), userId, admin));
+ }
}
private void clearApplicationRestrictions(int userId) {
@@ -10136,6 +10156,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
toggleBackupServiceActive(userId, true);
applyProfileRestrictionsIfDeviceOwnerLocked();
setNetworkLoggingActiveInternal(false);
+
+ if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) {
+ mDevicePolicyEngine.removePoliciesForAdmin(
+ EnforcingAdmin.createEnterpriseEnforcingAdmin(
+ admin.info.getComponent(), userId, admin));
+ }
}
@Override
@@ -11391,7 +11417,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
for (PolicyKey key : keys) {
if (!(key instanceof IntentFilterPolicyKey)) {
throw new IllegalStateException("PolicyKey for PERSISTENT_PREFERRED_ACTIVITY is not"
- + "of type PersistentPreferredActivityPolicyKey");
+ + "of type IntentFilterPolicyKey");
}
IntentFilterPolicyKey parsedKey =
(IntentFilterPolicyKey) key;
@@ -13131,19 +13157,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
int userId = caller.getUserId();
- if (!UserRestrictionsUtils.isValidRestriction(key)) {
- return;
- }
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
if (isPolicyEngineForFinanceFlagEnabled()) {
- int affectedUserId = parent ? getProfileParentId(userId) : userId;
- EnforcingAdmin admin = enforcePermissionForUserRestriction(
- who,
- key,
- caller.getPackageName(),
- affectedUserId);
- if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+ if (!isDeviceOwner(caller) && !isProfileOwner(caller)) {
+ if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) {
+ throw new IllegalStateException("Calling package is not targeting Android U.");
+ }
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ throw new IllegalArgumentException("Invalid restriction key: " + key);
+ }
+ int affectedUserId = parent ? getProfileParentId(userId) : userId;
+ EnforcingAdmin admin = enforcePermissionForUserRestriction(
+ who,
+ key,
+ caller.getPackageName(),
+ affectedUserId);
PolicyDefinition<Boolean> policyDefinition =
PolicyDefinition.getPolicyDefinitionForUserRestriction(key);
if (enabledFromThisOwner) {
@@ -13155,7 +13184,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false);
}
if (!policyDefinition.isGlobalOnlyPolicy()) {
- setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId);
+ setLocalUserRestrictionInternal(admin, key, /* enabled= */ false,
+ userId);
int parentUserId = getProfileParentId(userId);
if (parentUserId != userId) {
@@ -13165,49 +13195,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
} else {
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ return;
+ }
+ Objects.requireNonNull(who, "ComponentName is null");
+ EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
+ checkAdminCanSetRestriction(caller, parent, key);
setBackwardCompatibleUserRestriction(
caller, admin, key, enabledFromThisOwner, parent);
}
} else {
- Objects.requireNonNull(who, "ComponentName is null");
- if (parent) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller));
- } else {
- Preconditions.checkCallAuthorization(
- isDeviceOwner(caller) || isProfileOwner(caller));
- }
- synchronized (getLockObject()) {
- if (isDefaultDeviceOwner(caller)) {
- if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
- throw new SecurityException("Device owner cannot set user restriction "
- + key);
- }
- Preconditions.checkArgument(!parent,
- "Cannot use the parent instance in Device Owner mode");
- } else if (isFinancedDeviceOwner(caller)) {
- if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
- throw new SecurityException("Cannot set user restriction " + key
- + " when managing a financed device");
- }
- Preconditions.checkArgument(!parent,
- "Cannot use the parent instance in Financed Device Owner"
- + " mode");
- } else {
- boolean profileOwnerCanChangeOnItself = !parent
- && UserRestrictionsUtils.canProfileOwnerChange(
- key, userId == getMainUserId());
- boolean orgOwnedProfileOwnerCanChangeGlobally = parent
- && isProfileOwnerOfOrganizationOwnedDevice(caller)
- && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
- key);
-
- if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
- throw new SecurityException("Profile owner cannot set user restriction "
- + key);
- }
- }
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ return;
}
+ Objects.requireNonNull(who, "ComponentName is null");
+ checkAdminCanSetRestriction(caller, parent, key);
synchronized (getLockObject()) {
final ActiveAdmin activeAdmin = getParentOfAdminIfRequired(
getProfileOwnerOrDeviceOwnerLocked(userId), parent);
@@ -13224,6 +13226,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
logUserRestrictionCall(key, enabledFromThisOwner, parent, caller);
}
+ private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) {
+ if (parent) {
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller));
+ } else {
+ Preconditions.checkCallAuthorization(
+ isDeviceOwner(caller) || isProfileOwner(caller));
+ }
+ synchronized (getLockObject()) {
+ if (isDefaultDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) {
+ throw new SecurityException("Device owner cannot set user restriction "
+ + key);
+ }
+ Preconditions.checkArgument(!parent,
+ "Cannot use the parent instance in Device Owner mode");
+ } else if (isFinancedDeviceOwner(caller)) {
+ if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) {
+ throw new SecurityException("Cannot set user restriction " + key
+ + " when managing a financed device");
+ }
+ Preconditions.checkArgument(!parent,
+ "Cannot use the parent instance in Financed Device Owner"
+ + " mode");
+ } else {
+ boolean profileOwnerCanChangeOnItself = !parent
+ && UserRestrictionsUtils.canProfileOwnerChange(
+ key, caller.getUserId() == getMainUserId());
+ boolean orgOwnedProfileOwnerCanChangeGlobally = parent
+ && isProfileOwnerOfOrganizationOwnedDevice(caller)
+ && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(
+ key);
+
+ if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) {
+ throw new SecurityException("Profile owner cannot set user restriction "
+ + key);
+ }
+ }
+ }
+ }
private void setBackwardCompatibleUserRestriction(
CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled,
boolean parent) {
@@ -13245,6 +13287,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
? getProfileParentId(caller.getUserId()) : caller.getUserId();
setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId);
}
+ } else {
+ throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key
+ + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE");
}
}
}
@@ -13252,33 +13297,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setUserRestrictionGlobally(String callerPackage, String key) {
final CallerIdentity caller = getCallerIdentity(callerPackage);
- if (!UserRestrictionsUtils.isValidRestriction(key)) {
- return;
- }
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION);
if (!isPolicyEngineForFinanceFlagEnabled()) {
throw new IllegalStateException("Feature flag is not enabled.");
}
-
+ if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ throw new IllegalStateException("Admins are not allowed to call this API.");
+ }
if (!mInjector.isChangeEnabled(
ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
throw new IllegalStateException("Calling package is not targeting Android U.");
}
+ if (!UserRestrictionsUtils.isValidRestriction(key)) {
+ throw new IllegalArgumentException("Invalid restriction key: " + key);
+ }
EnforcingAdmin admin = enforcePermissionForUserRestriction(
/* who= */ null,
key,
caller.getPackageName(),
- caller.getUserId()
+ UserHandle.USER_ALL
);
setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true);
logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller);
}
-
private void setLocalUserRestrictionInternal(
EnforcingAdmin admin, String key, boolean enabled, int userId) {
PolicyDefinition<Boolean> policyDefinition =
@@ -13296,7 +13342,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
userId);
}
}
-
private void setGlobalUserRestrictionInternal(
EnforcingAdmin admin, String key, boolean enabled) {
PolicyDefinition<Boolean> policyDefinition =
@@ -13416,14 +13461,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int targetUserId = parent
? getProfileParentId(caller.getUserId()) : caller.getUserId();
EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage);
- Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
- // Add global restrictions set by the admin as well if admin is not targeting Android U.
- if (!mInjector.isChangeEnabled(
- ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+ if (isDeviceOwner(caller) || isProfileOwner(caller)) {
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isFinancedDeviceOwner(caller)
+ || isProfileOwner(caller)
+ || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller)));
+
+ Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId);
+ // Add global restrictions set by the admin as well.
restrictions.putAll(
getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL));
+ return restrictions;
+ } else {
+ if (!mInjector.isChangeEnabled(
+ ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) {
+ throw new IllegalStateException("Calling package is not targeting Android U.");
+ }
+ return getUserRestrictionsFromPolicyEngine(admin, targetUserId);
}
- return restrictions;
} else {
Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
@@ -13439,164 +13495,162 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
// Map of user restriction to permission.
- private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
+ private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>();
{
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+ UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER);
+ UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE);
+ UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+ UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+ UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+ UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS);
+ UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS);
+ UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+ UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+ UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER);
+ UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+ UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT);
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING);
+ UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS);
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+ UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA);
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT);
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE);
+ UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+ UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES);
+ UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+ UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN);
+ UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET);
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES);
+ UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION);
+ UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS);
+ UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+ UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT);
+ UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN);
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY);
+ UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS);
+ UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION);
+ UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE);
+ UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME);
+ UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+ UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY);
+ UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+ UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA);
+ UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA);
+ UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH);
+ UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH);
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS);
+ UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL);
+ UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL);
+ UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY);
+ UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE);
+ UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT);
+ UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI);
+ UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS);
+ UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES);
+ UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES);
+ UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK);
+ UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO,
- MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION);
+ UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES});
// Restrictions not allowed to be set by admins.
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_RECORD_AUDIO, null);
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_WALLPAPER, null);
- USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null);
}
private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who,
String userRestriction, String callerPackageName, int userId) {
- String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
- if (permission != null) {
- return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName,
- userId);
- }
+ String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction);
+ if (permissions.length > 0) {
+ try {
+ return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName,
+ userId);
+ } catch (SecurityException e) {
+ throw new SecurityException("Caller does not hold the required permission for this "
+ + "user restriction: " + userRestriction + ".\n" + e.getMessage());
+ }
+ }
throw new SecurityException("Admins are not permitted to set User Restriction: "
+ userRestriction);
}
@@ -14017,9 +14071,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
if (isPolicyEngineForFinanceFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+ EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin(
who,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ new String[]{
+ MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL
+ },
caller.getPackageName(),
caller.getUserId());
mDevicePolicyEngine.setLocalPolicy(
@@ -16055,6 +16112,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else {
long ident = mInjector.binderClearCallingIdentity();
try {
+ // TODO(b/277908283): check in the policy engine instead of calling user manager.
List<UserManager.EnforcingUser> sources = mUserManager
.getUserRestrictionSources(restriction, UserHandle.of(userId));
if (sources == null) {
@@ -16086,27 +16144,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final UserManager.EnforcingUser enforcingUser = sources.get(0);
final int sourceType = enforcingUser.getUserRestrictionSource();
- final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier();
- if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) {
- // Restriction was enforced by PO
- final ComponentName profileOwner = mOwners.getProfileOwnerComponent(
- enforcingUserId);
- if (profileOwner != null) {
- result = new Bundle();
- result.putInt(Intent.EXTRA_USER_ID, enforcingUserId);
- result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
- profileOwner);
- return result;
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
- // Restriction was enforced by DO
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null) {
+ if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER
+ || sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER ) {
+ ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userId);
+ if (admin != null) {
result = new Bundle();
- result.putInt(Intent.EXTRA_USER_ID, deviceOwner.first);
+ result.putInt(Intent.EXTRA_USER_ID, admin.getUserHandle().getIdentifier());
result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
- deviceOwner.second);
+ admin.info.getComponent());
return result;
}
} else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
@@ -16626,10 +16671,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA);
SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO);
SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
- SENSOR_PERMISSIONS.add(
- Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE);
- SENSOR_PERMISSIONS.add(
- Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND);
}
private boolean canGrantPermission(CallerIdentity caller, String permission,
@@ -22422,6 +22463,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
});
}
+ // Permission that will need to be created in V.
+ private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL =
+ "manage_device_policy_block_uninstall";
+ private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE =
+ "manage_device_policy_camera_toggle";
+ private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE =
+ "manage_device_policy_microphone_toggle";
+
// DPC types
private static final int DEFAULT_DEVICE_OWNER = 0;
private static final int FINANCED_DEVICE_OWNER = 1;
@@ -22512,53 +22561,53 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
);
/**
- * All the permisisons granted to a profile owner.
+ * All the permissions granted to a profile owner.
*/
private static final List<String> PROFILE_OWNER_PERMISSIONS =
List.of(
- MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
- MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
- MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
- MANAGE_DEVICE_POLICY_AUTOFILL,
- MANAGE_DEVICE_POLICY_CALLS,
- MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
- MANAGE_DEVICE_POLICY_DISPLAY,
- MANAGE_DEVICE_POLICY_FACTORY_RESET,
- MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
- MANAGE_DEVICE_POLICY_KEYGUARD,
- MANAGE_DEVICE_POLICY_LOCALE,
- MANAGE_DEVICE_POLICY_LOCATION,
- MANAGE_DEVICE_POLICY_LOCK,
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- MANAGE_DEVICE_POLICY_PACKAGE_STATE,
- MANAGE_DEVICE_POLICY_PRINTING,
- MANAGE_DEVICE_POLICY_PROFILES,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- MANAGE_DEVICE_POLICY_RESET_PASSWORD,
- MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
- MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
- MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
- MANAGE_DEVICE_POLICY_TIME,
- MANAGE_DEVICE_POLICY_VPN,
- MANAGE_DEVICE_POLICY_WIPE_DATA
+ MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL,
+ MANAGE_DEVICE_POLICY_APPS_CONTROL,
+ MANAGE_DEVICE_POLICY_APP_RESTRICTIONS,
+ MANAGE_DEVICE_POLICY_AUDIO_OUTPUT,
+ MANAGE_DEVICE_POLICY_AUTOFILL,
+ MANAGE_DEVICE_POLICY_BLUETOOTH,
+ MANAGE_DEVICE_POLICY_CALLS,
+ MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
+ MANAGE_DEVICE_POLICY_DISPLAY,
+ MANAGE_DEVICE_POLICY_FACTORY_RESET,
+ MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES,
+ MANAGE_DEVICE_POLICY_KEYGUARD,
+ MANAGE_DEVICE_POLICY_LOCALE,
+ MANAGE_DEVICE_POLICY_LOCATION,
+ MANAGE_DEVICE_POLICY_LOCK,
+ MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
+ MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
+ MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
+ MANAGE_DEVICE_POLICY_PACKAGE_STATE,
+ MANAGE_DEVICE_POLICY_PRINTING,
+ MANAGE_DEVICE_POLICY_PROFILES,
+ MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
+ MANAGE_DEVICE_POLICY_RESET_PASSWORD,
+ MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
+ MANAGE_DEVICE_POLICY_SCREEN_CAPTURE,
+ MANAGE_DEVICE_POLICY_SCREEN_CONTENT,
+ MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
+ MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
+ MANAGE_DEVICE_POLICY_TIME,
+ MANAGE_DEVICE_POLICY_VPN,
+ MANAGE_DEVICE_POLICY_WIPE_DATA
);
/**
- * All the additional permissions granted to an organisation owned profile owner.
- */
+ * All the additional permissions granted to an organisation owned profile owner.
+ */
private static final List<String>
ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS =
- List.of(
+ List.of(
MANAGE_DEVICE_POLICY_ACROSS_USERS,
MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
MANAGE_DEVICE_POLICY_APPS_CONTROL,
- MANAGE_DEVICE_POLICY_BLUETOOTH,
MANAGE_DEVICE_POLICY_CAMERA,
MANAGE_DEVICE_POLICY_CERTIFICATES,
MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
@@ -22579,13 +22628,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_WIFI,
SET_TIME,
SET_TIME_ZONE
- );
+ );
private static final List<String> ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS =
List.of(
MANAGE_DEVICE_POLICY_AIRPLANE_MODE,
- MANAGE_DEVICE_POLICY_BLUETOOTH,
MANAGE_DEVICE_POLICY_CAMERA,
MANAGE_DEVICE_POLICY_DISPLAY,
MANAGE_DEVICE_POLICY_FUN,
@@ -22645,7 +22693,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
{
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT);
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS);
- DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL);
+ DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL);
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING);
DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS);
}
@@ -22724,6 +22772,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES,
@@ -22740,6 +22792,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES,
@@ -22770,13 +22824,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user. Only one permission provided in the list needs to be granted to pass this
+ * check.
+ * The given permissions will be checked along with their associated cross-user permissions if
+ * they exists and the target user is different to the calling user.
+ * Returns an {@link EnforcingAdmin} for the caller.
+ *
+ * @param admin the component name of the admin.
+ * @param callerPackageName The package name of the calling application.
+ * @param permissions an array of permission names to be checked.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin,
+ String[] permissions, String callerPackageName, int targetUserId) {
+ enforcePermissions(permissions, callerPackageName, targetUserId);
+ return getEnforcingAdminForCaller(admin, callerPackageName);
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
* specific user.
* The given permission will be checked along with its associated cross-user permission if it
* exists and the target user is different to the calling user.
* Returns an {@link EnforcingAdmin} for the caller.
*
* @param admin the component name of the admin.
- * @param callerPackageName The package name of the calling application.
+ * @param callerPackageName The package name of the calling application.
* @param permission The name of the permission being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
@@ -22834,6 +22909,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
new HashMap<>();
/**
+ * Checks if the calling process has been granted permission to apply a device policy.
+ *
+ * @param callerPackageName The package name of the calling application.
+ * @param permission The name of the permission being checked.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermission(String permission, String callerPackageName)
+ throws SecurityException {
+ if (!hasPermission(permission, callerPackageName)) {
+ throw new SecurityException("Caller does not have the required permissions for "
+ + "this user. Permission required: "
+ + permission
+ + ".");
+ }
+ }
+
+
+ /**
* Checks if the calling process has been granted permission to apply a device policy on a
* specific user.
* The given permission will be checked along with its associated cross-user permission if it
@@ -22847,17 +22941,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
*/
private void enforcePermission(String permission, String callerPackageName, int targetUserId)
throws SecurityException {
- if (!hasPermission(permission, callerPackageName, targetUserId)) {
- // TODO(b/276920002): Split the error messages so that the cross-user permission
- // is only mentioned when it is needed.
+ enforcePermission(permission, callerPackageName);
+ if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) {
+ enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName);
+ }
+ }
+
+ /**
+ * Checks if the calling process has been granted permission to apply a device policy on a
+ * specific user. Only one of the given permissions will be required to be held to pass this
+ * check.
+ * The given permissions will be checked along with their associated cross-user permissions if
+ * they exist and the target user is different to the calling user.
+ *
+ * @param permissions An array of the names of the permissions being checked.
+ * @param callerPackageName The package name of the calling application.
+ * @param targetUserId The userId of the user which the caller needs permission to act on.
+ * @throws SecurityException if the caller has not been granted the given permission,
+ * the associated cross-user permission if the caller's user is different to the target user.
+ */
+ private void enforcePermissions(String[] permissions, String callerPackageName,
+ int targetUserId) throws SecurityException {
+ String heldPermission = "";
+ for (String permission : permissions) {
+ if (hasPermission(permission, callerPackageName)) {
+ heldPermission = permission;
+ break;
+ }
+ }
+ if (heldPermission.isEmpty()) {
throw new SecurityException("Caller does not have the required permissions for "
- + "this user. Permissions required: {"
- + permission
- + ", "
- + CROSS_USER_PERMISSIONS.get(permission)
- + "(if calling cross-user)"
- + "}");
+ + "this user. One of the following permission required: "
+ + Arrays.toString(permissions));
}
+ enforcePermission(heldPermission, callerPackageName, targetUserId);
}
/**
@@ -22867,25 +22984,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* exists and the target user is different to the calling user.
*
* @param callerPackageName The package name of the calling application.
+ * @param adminPolicy The admin policy that should grant holders permission.
* @param permission The name of the permission being checked.
* @param targetUserId The userId of the user which the caller needs permission to act on.
* @throws SecurityException if the caller has not been granted the given permission,
* the associated cross-user permission if the caller's user is different to the target user.
*/
private void enforcePermission(String permission, int adminPolicy,
- String callerPackageName, int targetUserId)
- throws SecurityException {
- if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) {
- // TODO(b/276920002): Split the error messages so that the cross-user permission
- // is only mentioned when it is needed.
- throw new SecurityException("Caller does not have the required permissions for "
- + "this user. Permissions required: {"
- + permission
- + ", "
- + CROSS_USER_PERMISSIONS.get(permission)
- + "(if calling cross-user)"
- + "}");
+ String callerPackageName, int targetUserId) throws SecurityException {
+ if (hasAdminPolicy(adminPolicy, callerPackageName)) {
+ return;
}
+ enforcePermission(permission, callerPackageName, targetUserId);
}
/**
@@ -22909,6 +23019,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforcePermission(permission, callerPackageName, targetUserId);
}
+ private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) {
+ CallerIdentity caller = getCallerIdentity(callerPackageName);
+ ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
+ return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
+ }
+
/**
* Return whether the calling process has been granted permission to apply a device policy on
* a specific user.
@@ -22921,24 +23037,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
CallerIdentity caller = getCallerIdentity(callerPackageName);
boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName());
boolean hasPermissionOnTargetUser = true;
- if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) {
- hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission),
- caller.getPackageName());
+ if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) {
+ hasPermissionOnTargetUser = hasPermissionOnTargetUser
+ && hasPermission(CROSS_USER_PERMISSIONS.get(permission),
+ caller.getPackageName());
}
return hasPermissionOnOwnUser && hasPermissionOnTargetUser;
}
- private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName,
- int adminPolicy, int targetUserId) {
- CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (hasPermission(permission, caller.getPackageName(), targetUserId)) {
- return true;
- }
- ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller);
- return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy);
- }
-
/**
* Return whether the calling process has been granted the given permission.
*
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
index bff6d3288dca..45a2d2a7bda1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/IntegerPolicySerializer.java
@@ -32,21 +32,25 @@ import java.util.Objects;
final class IntegerPolicySerializer extends PolicySerializer<Integer> {
+ private static final String TAG = "IntegerPolicySerializer";
+
+ private static final String ATTR_VALUE = "value";
+
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull Integer value) throws IOException {
Objects.requireNonNull(value);
- serializer.attributeInt(/* namespace= */ null, attributeName, value);
+ serializer.attributeInt(/* namespace= */ null, ATTR_VALUE, value);
}
@Nullable
@Override
- IntegerPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ IntegerPolicyValue readFromXml(TypedXmlPullParser parser) {
try {
return new IntegerPolicyValue(
- parser.getAttributeInt(/* namespace= */ null, attributeName));
+ parser.getAttributeInt(/* namespace= */ null, ATTR_VALUE));
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing Integer policy value", e);
+ Log.e(TAG, "Error parsing Integer policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
index 3265b61a3543..0f6f3c5a5921 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LockTaskPolicySerializer.java
@@ -32,12 +32,14 @@ import java.util.Set;
final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
- private static final String ATTR_PACKAGES = ":packages";
+ private static final String TAG = "LockTaskPolicySerializer";
+
+ private static final String ATTR_PACKAGES = "packages";
private static final String ATTR_PACKAGES_SEPARATOR = ";";
- private static final String ATTR_FLAGS = ":flags";
+ private static final String ATTR_FLAGS = "flags";
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull LockTaskPolicy value) throws IOException {
Objects.requireNonNull(value);
if (value.getPackages() == null || value.getPackages().isEmpty()) {
@@ -46,31 +48,31 @@ final class LockTaskPolicySerializer extends PolicySerializer<LockTaskPolicy> {
}
serializer.attribute(
/* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGES,
+ ATTR_PACKAGES,
String.join(ATTR_PACKAGES_SEPARATOR, value.getPackages()));
serializer.attributeInt(
/* namespace= */ null,
- attributeNamePrefix + ATTR_FLAGS,
+ ATTR_FLAGS,
value.getFlags());
}
@Override
- LockTaskPolicy readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
+ LockTaskPolicy readFromXml(TypedXmlPullParser parser) {
String packagesStr = parser.getAttributeValue(
/* namespace= */ null,
- attributeNamePrefix + ATTR_PACKAGES);
+ ATTR_PACKAGES);
if (packagesStr == null) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value.");
+ Log.e(TAG, "Error parsing LockTask policy value.");
return null;
}
Set<String> packages = Set.of(packagesStr.split(ATTR_PACKAGES_SEPARATOR));
try {
int flags = parser.getAttributeInt(
/* namespace= */ null,
- attributeNamePrefix + ATTR_FLAGS);
+ ATTR_FLAGS);
return new LockTaskPolicy(packages, flags);
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing LockTask policy value", e);
+ Log.e(TAG, "Error parsing LockTask policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
index f77d051cab86..522c4b5e84be 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/LongPolicySerializer.java
@@ -32,21 +32,25 @@ import java.util.Objects;
final class LongPolicySerializer extends PolicySerializer<Long> {
+ private static final String TAG = "LongPolicySerializer";
+
+ private static final String ATTR_VALUE = "value";
+
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeName,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull Long value) throws IOException {
Objects.requireNonNull(value);
- serializer.attributeLong(/* namespace= */ null, attributeName, value);
+ serializer.attributeLong(/* namespace= */ null, ATTR_VALUE, value);
}
@Nullable
@Override
- LongPolicyValue readFromXml(TypedXmlPullParser parser, String attributeName) {
+ LongPolicyValue readFromXml(TypedXmlPullParser parser) {
try {
return new LongPolicyValue(
- parser.getAttributeLong(/* namespace= */ null, attributeName));
+ parser.getAttributeLong(/* namespace= */ null, ATTR_VALUE));
} catch (XmlPullParserException e) {
- Log.e(DevicePolicyEngine.TAG, "Error parsing Long policy value", e);
+ Log.e(TAG, "Error parsing Long policy value", e);
return null;
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index a15aa53be96c..8c2468af1146 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -284,6 +284,7 @@ final class PolicyDefinition<V> {
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
+ // TODO(b/277218360): Revisit policies that should be marked as global-only.
static {
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY,
@@ -312,8 +313,9 @@ final class PolicyDefinition<V> {
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0);
+ // TODO: set as global only once we get rid of the mapping
USER_RESTRICTION_FLAGS.put(
- UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(
@@ -333,8 +335,10 @@ final class PolicyDefinition<V> {
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0);
+ // This effectively always applies globally, but it can be set on the profile
+ // parent, check the javadocs on the restriction for more info.
USER_RESTRICTION_FLAGS.put(
- UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ UserManager.DISALLOW_USB_FILE_TRANSFER, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0);
@@ -344,8 +348,10 @@ final class PolicyDefinition<V> {
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0);
+ // This effectively always applies globally, but it can be set on the profile
+ // parent, check the javadocs on the restriction for more info.
USER_RESTRICTION_FLAGS.put(
- UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ UserManager.DISALLOW_NETWORK_RESET, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0);
@@ -376,8 +382,7 @@ final class PolicyDefinition<V> {
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0);
- // TODO: double check flags
- USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY);
+ USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0);
@@ -390,6 +395,7 @@ final class PolicyDefinition<V> {
USER_RESTRICTION_FLAGS.put(
UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY);
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0);
+ // TODO: According the UserRestrictionsUtils, this is global only, need to confirm.
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0);
// TODO: check if its global only
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0);
@@ -531,7 +537,6 @@ final class PolicyDefinition<V> {
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
- // TODO: here and elsewhere, add tags to ensure attributes aren't overridden by duplication.
mPolicyKey.saveToXml(serializer);
}
@@ -554,14 +559,14 @@ final class PolicyDefinition<V> {
return genericPolicyDefinition.mPolicyKey.readFromXml(parser);
}
- void savePolicyValueToXml(TypedXmlSerializer serializer, String attributeName, V value)
+ void savePolicyValueToXml(TypedXmlSerializer serializer, V value)
throws IOException {
- mPolicySerializer.saveToXml(mPolicyKey, serializer, attributeName, value);
+ mPolicySerializer.saveToXml(mPolicyKey, serializer, value);
}
@Nullable
- PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser, String attributeName) {
- return mPolicySerializer.readFromXml(parser, attributeName);
+ PolicyValue<V> readPolicyValueFromXml(TypedXmlPullParser parser) {
+ return mPolicySerializer.readFromXml(parser);
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
index 0ef431f6ff90..5af2fa285483 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicySerializer.java
@@ -26,8 +26,7 @@ import com.android.modules.utils.TypedXmlSerializer;
import java.io.IOException;
abstract class PolicySerializer<V> {
- abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
- String attributeName, @NonNull V value)
+ abstract void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, @NonNull V value)
throws IOException;
- abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser, String attributeName);
+ abstract PolicyValue<V> readFromXml(TypedXmlPullParser parser);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 3a792d82d2ba..741f209a90c3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -35,11 +35,14 @@ import java.util.Objects;
* Class containing all values set for a certain policy by different admins.
*/
final class PolicyState<V> {
+
+ private static final String TAG = "PolicyState";
private static final String TAG_ADMIN_POLICY_ENTRY = "admin-policy-entry";
- private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
- private static final String ATTR_POLICY_VALUE = "policy-value";
- private static final String ATTR_RESOLVED_POLICY = "resolved-policy";
+ private static final String TAG_POLICY_DEFINITION_ENTRY = "policy-definition-entry";
+ private static final String TAG_RESOLVED_VALUE_ENTRY = "resolved-value-entry";
+ private static final String TAG_ENFORCING_ADMIN_ENTRY = "enforcing-admin-entry";
+ private static final String TAG_POLICY_VALUE_ENTRY = "policy-value-entry";
private final PolicyDefinition<V> mPolicyDefinition;
private final LinkedHashMap<EnforcingAdmin, PolicyValue<V>> mPoliciesSetByAdmins =
new LinkedHashMap<>();
@@ -193,18 +196,24 @@ final class PolicyState<V> {
}
void saveToXml(TypedXmlSerializer serializer) throws IOException {
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
mPolicyDefinition.saveToXml(serializer);
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_DEFINITION_ENTRY);
if (mCurrentResolvedPolicy != null) {
+ serializer.startTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_RESOLVED_POLICY, mCurrentResolvedPolicy.getValue());
+ serializer, mCurrentResolvedPolicy.getValue());
+ serializer.endTag(/* namespace= */ null, TAG_RESOLVED_VALUE_ENTRY);
}
for (EnforcingAdmin admin : mPoliciesSetByAdmins.keySet()) {
serializer.startTag(/* namespace= */ null, TAG_ADMIN_POLICY_ENTRY);
+ serializer.startTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
mPolicyDefinition.savePolicyValueToXml(
- serializer, ATTR_POLICY_VALUE, mPoliciesSetByAdmins.get(admin).getValue());
+ serializer, mPoliciesSetByAdmins.get(admin).getValue());
+ serializer.endTag(/* namespace= */ null, TAG_POLICY_VALUE_ENTRY);
serializer.startTag(/* namespace= */ null, TAG_ENFORCING_ADMIN_ENTRY);
admin.saveToXml(serializer);
@@ -217,32 +226,57 @@ final class PolicyState<V> {
static <V> PolicyState<V> readFromXml(TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
- PolicyDefinition<V> policyDefinition = PolicyDefinition.readFromXml(parser);
+ PolicyDefinition<V> policyDefinition = null;
- PolicyValue<V> currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(
- parser, ATTR_RESOLVED_POLICY);
+ PolicyValue<V> currentResolvedPolicy = null;
LinkedHashMap<EnforcingAdmin, PolicyValue<V>> policiesSetByAdmins = new LinkedHashMap<>();
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
String tag = parser.getName();
- if (TAG_ADMIN_POLICY_ENTRY.equals(tag)) {
- PolicyValue<V> value = policyDefinition.readPolicyValueFromXml(
- parser, ATTR_POLICY_VALUE);
- EnforcingAdmin admin;
- int adminPolicyDepth = parser.getDepth();
- if (XmlUtils.nextElementWithin(parser, adminPolicyDepth)
- && parser.getName().equals(TAG_ENFORCING_ADMIN_ENTRY)) {
- admin = EnforcingAdmin.readFromXml(parser);
- policiesSetByAdmins.put(admin, value);
- }
- } else {
- Log.e(DevicePolicyEngine.TAG, "Unknown tag: " + tag);
+ switch (tag) {
+ case TAG_ADMIN_POLICY_ENTRY:
+ PolicyValue<V> value = null;
+ EnforcingAdmin admin = null;
+ int adminPolicyDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, adminPolicyDepth)) {
+ String adminPolicyTag = parser.getName();
+ switch (adminPolicyTag) {
+ case TAG_ENFORCING_ADMIN_ENTRY:
+ admin = EnforcingAdmin.readFromXml(parser);
+ break;
+ case TAG_POLICY_VALUE_ENTRY:
+ value = policyDefinition.readPolicyValueFromXml(parser);
+ break;
+ }
+ }
+ if (admin != null && value != null) {
+ policiesSetByAdmins.put(admin, value);
+ } else {
+ Log.e(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY");
+ }
+ break;
+ case TAG_POLICY_DEFINITION_ENTRY:
+ policyDefinition = PolicyDefinition.readFromXml(parser);
+ break;
+
+ case TAG_RESOLVED_VALUE_ENTRY:
+ currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
+ break;
+ default:
+ Log.e(TAG, "Unknown tag: " + tag);
}
}
- return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
+ if (policyDefinition != null) {
+ return new PolicyState<V>(policyDefinition, policiesSetByAdmins, currentResolvedPolicy);
+ } else {
+ Log.e("PolicyState", "Error parsing policyState");
+ return null;
+ }
}
+
+
PolicyDefinition<V> getPolicyDefinition() {
return mPolicyDefinition;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
index dc6592d73116..24d0521471f7 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/StringSetPolicySerializer.java
@@ -36,21 +36,17 @@ final class StringSetPolicySerializer extends PolicySerializer<Set<String>> {
private static final String ATTR_VALUES_SEPARATOR = ";";
@Override
- void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer, String attributeNamePrefix,
+ void saveToXml(PolicyKey policyKey, TypedXmlSerializer serializer,
@NonNull Set<String> value) throws IOException {
Objects.requireNonNull(value);
serializer.attribute(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_VALUES,
- String.join(ATTR_VALUES_SEPARATOR, value));
+ /* namespace= */ null, ATTR_VALUES, String.join(ATTR_VALUES_SEPARATOR, value));
}
@Nullable
@Override
- PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser, String attributeNamePrefix) {
- String valuesStr = parser.getAttributeValue(
- /* namespace= */ null,
- attributeNamePrefix + ATTR_VALUES);
+ PolicyValue<Set<String>> readFromXml(TypedXmlPullParser parser) {
+ String valuesStr = parser.getAttributeValue(/* namespace= */ null, ATTR_VALUES);
if (valuesStr == null) {
Log.e(DevicePolicyEngine.TAG, "Error parsing StringSet policy value.");
return null;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 492d477fe23a..b1d613109e09 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@ import com.android.server.compat.PlatformCompatNative;
import com.android.server.connectivity.PacProxyService;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.coverage.CoverageService;
+import com.android.server.cpu.CpuMonitorService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.devicestate.DeviceStateManagerService;
import com.android.server.display.DisplayManagerService;
@@ -1405,6 +1406,15 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(RemoteProvisioningService.class);
t.traceEnd();
+ // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable
+ // builds once the Android JobScheduler starts using this service.
+ if (Build.IS_DEBUGGABLE || Build.IS_ENG) {
+ // Service for CPU monitor.
+ t.traceBegin("CpuMonitorService");
+ mSystemServiceManager.startService(CpuMonitorService.class);
+ t.traceEnd();
+ }
+
t.traceEnd(); // startCoreServices
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 1a7517098d18..7b771aff0055 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -567,36 +567,36 @@ public class RescuePartyTest {
// Ensure that no action is taken for cases where the failure reason is unknown
assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
- PackageHealthObserverImpact.USER_IMPACT_NONE);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
// Ensure the correct user impact is returned for each mitigation count.
assertEquals(observer.onHealthCheckFailed(null,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
- PackageHealthObserverImpact.USER_IMPACT_LOW);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
assertEquals(observer.onHealthCheckFailed(null,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
- PackageHealthObserverImpact.USER_IMPACT_LOW);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
assertEquals(observer.onHealthCheckFailed(null,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
assertEquals(observer.onHealthCheckFailed(null,
PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
}
@Test
public void testBootLoopLevels() {
RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
- assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE);
- assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW);
- assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW);
- assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH);
- assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH);
- assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
+ assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+ assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
+ assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
+ assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 318067ee8681..3fb7fb4e6aea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -74,16 +74,17 @@ import android.os.BundleMerger;
import android.os.DropBoxManager;
import android.os.HandlerThread;
import android.os.SystemClock;
+import android.os.TestLooperManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ExtendedMockitoRule;
-import com.android.server.am.BroadcastQueueTest.SyncBarrier;
import org.junit.After;
import org.junit.Before;
@@ -96,6 +97,7 @@ import java.io.Writer;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
@SmallTest
public final class BroadcastQueueModernImplTest {
@@ -111,6 +113,7 @@ public final class BroadcastQueueModernImplTest {
@Mock BroadcastProcessQueue mQueue4;
HandlerThread mHandlerThread;
+ TestLooperManager mLooper;
BroadcastConstants mConstants;
BroadcastQueueModernImpl mImpl;
@@ -127,6 +130,10 @@ public final class BroadcastQueueModernImplTest {
mHandlerThread = new HandlerThread(getClass().getSimpleName());
mHandlerThread.start();
+ // Pause all event processing until a test chooses to resume
+ mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+ .acquireLooperManager(mHandlerThread.getLooper()));
+
mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mConstants.DELAY_URGENT_MILLIS = -120_000;
mConstants.DELAY_NORMAL_MILLIS = 10_000;
@@ -167,6 +174,17 @@ public final class BroadcastQueueModernImplTest {
mHandlerThread.quit();
}
+ /**
+ * Un-pause our handler to process pending events, wait for our queue to go
+ * idle, and then re-pause the handler.
+ */
+ private void waitForIdle() throws Exception {
+ mLooper.release();
+ mImpl.waitForIdle(LOG_WRITER_INFO);
+ mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+ .acquireLooperManager(mHandlerThread.getLooper()));
+ }
+
private static void assertOrphan(BroadcastProcessQueue queue) {
assertNull(queue.runnableAtNext);
assertNull(queue.runnableAtPrev);
@@ -237,9 +255,23 @@ public final class BroadcastQueueModernImplTest {
}
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+ BroadcastRecord record, int recordIndex) {
+ enqueueOrReplaceBroadcast(queue, record, recordIndex, false, 42_000_000L);
+ }
+
+ private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
BroadcastRecord record, int recordIndex, long enqueueTime) {
- queue.enqueueOrReplaceBroadcast(record, recordIndex, false);
+ enqueueOrReplaceBroadcast(queue, record, recordIndex, false, enqueueTime);
+ }
+
+ private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+ BroadcastRecord record, int recordIndex, boolean wouldBeSkipped, long enqueueTime) {
+ queue.enqueueOrReplaceBroadcast(record, recordIndex, wouldBeSkipped, (r, i) -> {
+ throw new UnsupportedOperationException();
+ });
record.enqueueTime = enqueueTime;
+ record.enqueueRealTime = enqueueTime;
+ record.enqueueClockTime = enqueueTime;
}
@Test
@@ -370,7 +402,7 @@ public final class BroadcastQueueModernImplTest {
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
List.of(makeMockRegisteredReceiver()), false);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
queue.setProcessAndUidCached(null, false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -397,7 +429,7 @@ public final class BroadcastQueueModernImplTest {
.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options,
List.of(makeMockRegisteredReceiver()), false);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
queue.setProcessAndUidCached(null, false);
final long notCachedRunnableAt = queue.getRunnableAt();
@@ -406,7 +438,7 @@ public final class BroadcastQueueModernImplTest {
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertTrue(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason());
- assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
}
/**
@@ -421,12 +453,12 @@ public final class BroadcastQueueModernImplTest {
// enqueue a bg-priority broadcast then a fg-priority one
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
- queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, timezoneRecord, 0);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
// verify that:
// (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -434,13 +466,13 @@ public final class BroadcastQueueModernImplTest {
queue.setProcessAndUidCached(null, false);
assertTrue(queue.isRunnable());
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
- assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
queue.setProcessAndUidCached(null, true);
assertTrue(queue.isRunnable());
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueClockTime);
- assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
assertEquals(queue.peekNextBroadcastRecord(), airplaneRecord);
}
@@ -457,13 +489,14 @@ public final class BroadcastQueueModernImplTest {
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 1);
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
// Bumping past barrier makes us now runnable
- airplaneRecord.terminalCount++;
+ airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED,
+ "testRunnableAt_Ordered");
queue.invalidateRunnableAt();
assertTrue(queue.isRunnable());
assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -480,7 +513,7 @@ public final class BroadcastQueueModernImplTest {
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane,
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, airplaneRecord, 0);
mConstants.MAX_PENDING_BROADCASTS = 128;
queue.invalidateRunnableAt();
@@ -506,11 +539,11 @@ public final class BroadcastQueueModernImplTest {
new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED),
List.of(makeMockRegisteredReceiver()));
- queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, lazyRecord, 0);
assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason);
- queue.enqueueOrReplaceBroadcast(testRecord, 0, false);
+ enqueueOrReplaceBroadcast(queue, testRecord, 0);
assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime);
assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason);
}
@@ -572,22 +605,22 @@ public final class BroadcastQueueModernImplTest {
BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
- queue.enqueueOrReplaceBroadcast(
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false);
- queue.enqueueOrReplaceBroadcast(
- makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false);
- queue.enqueueOrReplaceBroadcast(
+ .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+ enqueueOrReplaceBroadcast(queue,
+ makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+ enqueueOrReplaceBroadcast(queue,
makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false);
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
queue.makeActiveNextPending();
assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
@@ -821,9 +854,6 @@ public final class BroadcastQueueModernImplTest {
optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio",
String.valueOf(AudioManager.STREAM_ALARM));
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
optionsMusicVolumeChanged));
@@ -890,9 +920,6 @@ public final class BroadcastQueueModernImplTest {
String.valueOf(TEST_UID2));
optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger);
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid,
optionsPackageChangedForUid));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2,
@@ -940,9 +967,6 @@ public final class BroadcastQueueModernImplTest {
BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged);
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged,
optionsMusicVolumeChanged));
@@ -989,9 +1013,6 @@ public final class BroadcastQueueModernImplTest {
final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast3 = createDropboxBroadcast(
"TAG_A", now + 2000, 7);
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast1.first,
dropboxEntryBroadcast1.second));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast2.first,
@@ -1021,9 +1042,6 @@ public final class BroadcastQueueModernImplTest {
.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
.setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing");
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
closeSystemDialogs1, optionsCloseSystemDialog1));
mImpl.enqueueBroadcastLocked(makeBroadcastRecord(
@@ -1073,9 +1091,6 @@ public final class BroadcastQueueModernImplTest {
final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT);
userPresent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
-
final BroadcastRecord userPresentRecord1 = makeBroadcastRecord(userPresent);
final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent);
@@ -1114,8 +1129,6 @@ public final class BroadcastQueueModernImplTest {
makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)
));
- // Halt all processing so that we get a consistent view
- mHandlerThread.getLooper().getQueue().postSyncBarrier();
mImpl.enqueueBroadcastLocked(record1);
mImpl.enqueueBroadcastLocked(record2);
@@ -1137,12 +1150,9 @@ public final class BroadcastQueueModernImplTest {
final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic();
optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT);
- // Halt all processing so that we get a consistent view
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
- mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
- }
- mImpl.waitForIdle(LOG_WRITER_INFO);
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick));
+ waitForIdle();
// Verify that there is only one delivery event reported since one of the broadcasts
// should have been skipped.
@@ -1154,6 +1164,41 @@ public final class BroadcastQueueModernImplTest {
times(1));
}
+ @Test
+ public void testGetPreferredSchedulingGroup() throws Exception {
+ final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+ PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+ List.of(makeMockRegisteredReceiver())), 0);
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+
+ // Make the foreground broadcast as active.
+ queue.makeActiveNextPending();
+ assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+
+ queue.makeActiveIdle();
+ assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(airplane,
+ List.of(makeMockRegisteredReceiver())), 0);
+
+ // Make the background broadcast as active.
+ queue.makeActiveNextPending();
+ assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked());
+
+ enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick,
+ List.of(makeMockRegisteredReceiver())), 0);
+ // Even though the active broadcast is not a foreground one, scheduling group will be
+ // DEFAULT since there is a foreground broadcast waiting to be delivered.
+ assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked());
+ }
+
private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index b6bc02a41c21..7be1d7cde27f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO;
@@ -39,7 +41,6 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -76,6 +77,7 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerExemptionManager;
import android.os.SystemClock;
+import android.os.TestLooperManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
@@ -145,6 +147,7 @@ public class BroadcastQueueTest {
private Context mContext;
private HandlerThread mHandlerThread;
+ private TestLooperManager mLooper;
private AtomicInteger mNextPid;
@Mock
@@ -205,6 +208,11 @@ public class BroadcastQueueTest {
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
+
+ // Pause all event processing until a test chooses to resume
+ mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+ .acquireLooperManager(mHandlerThread.getLooper()));
+
mNextPid = new AtomicInteger(100);
LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
@@ -359,25 +367,6 @@ public class BroadcastQueueTest {
}
}
- /**
- * Helper that leverages try-with-resources to pause dispatch of
- * {@link #mHandlerThread} until released.
- */
- static class SyncBarrier implements AutoCloseable {
- private final int mToken;
- private HandlerThread mThread;
-
- SyncBarrier(HandlerThread thread) {
- mThread = thread;
- mToken = mThread.getLooper().getQueue().postSyncBarrier();
- }
-
- @Override
- public void close() throws Exception {
- mThread.getLooper().getQueue().removeSyncBarrier(mToken);
- }
- }
-
private enum ProcessStartBehavior {
/** Process starts successfully */
SUCCESS,
@@ -463,7 +452,7 @@ public class BroadcastQueueTest {
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
- + Arrays.toString(invocation.getArguments()));
+ + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
assertHealth();
final Intent intent = invocation.getArgument(0);
final Bundle extras = invocation.getArgument(5);
@@ -485,7 +474,7 @@ public class BroadcastQueueTest {
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for "
- + Arrays.toString(invocation.getArguments()));
+ + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName);
assertHealth();
final Intent intent = invocation.getArgument(1);
final Bundle extras = invocation.getArgument(4);
@@ -670,8 +659,15 @@ public class BroadcastQueueTest {
}
}
+ /**
+ * Un-pause our handler to process pending events, wait for our queue to go
+ * idle, and then re-pause the handler.
+ */
private void waitForIdle() throws Exception {
+ mLooper.release();
mQueue.waitForIdle(LOG_WRITER_INFO);
+ mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+ .acquireLooperManager(mHandlerThread.getLooper()));
}
private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception {
@@ -961,7 +957,7 @@ public class BroadcastQueueTest {
} else {
// Confirm that app was thawed
verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily(
- eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER));
+ eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER));
// Confirm that we added package to process
verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName),
@@ -1155,23 +1151,21 @@ public class BroadcastQueueTest {
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
- List.of(makeRegisteredReceiver(receiverApp),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
-
- synchronized (mAms) {
- mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN),
- UserHandle.USER_SYSTEM);
-
- // Also try clearing out other unrelated things that should leave
- // the final receiver intact
- mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null,
- UserHandle.USER_SYSTEM);
- mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
- }
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+ List.of(makeRegisteredReceiver(receiverApp),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_RED),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE)))));
+
+ synchronized (mAms) {
+ mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN),
+ UserHandle.USER_SYSTEM);
+
+ // Also try clearing out other unrelated things that should leave
+ // the final receiver intact
+ mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null,
+ UserHandle.USER_SYSTEM);
+ mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
// To maximize test coverage, dump current state; we're not worried
// about the actual output, just that we don't crash
@@ -1199,21 +1193,19 @@ public class BroadcastQueueTest {
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
final Intent timeZone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>(
- List.of(makeRegisteredReceiver(callerApp),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
- makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
- makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
- enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>(
- List.of(makeRegisteredReceiver(callerApp),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
- makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
- makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
-
- synchronized (mAms) {
- mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
- }
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>(
+ List.of(makeRegisteredReceiver(callerApp),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
+ enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>(
+ List.of(makeRegisteredReceiver(callerApp),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST)))));
+
+ synchronized (mAms) {
+ mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST);
}
waitForIdle();
@@ -1239,15 +1231,12 @@ public class BroadcastQueueTest {
final ProcessRecord oldApp = makeActiveProcessRecord(PACKAGE_GREEN);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
- List.of(makeRegisteredReceiver(oldApp),
- makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
-
- synchronized (mAms) {
- oldApp.killLocked(TAG, 42, false);
- mQueue.onApplicationCleanupLocked(oldApp);
- }
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+ List.of(makeRegisteredReceiver(oldApp),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
+ synchronized (mAms) {
+ oldApp.killLocked(TAG, 42, false);
+ mQueue.onApplicationCleanupLocked(oldApp);
}
waitForIdle();
@@ -1404,7 +1393,7 @@ public class BroadcastQueueTest {
// Finally, verify that we thawed the final receiver
verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp),
- eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER));
+ eq(OOM_ADJ_REASON_FINISH_RECEIVER));
}
/**
@@ -1620,17 +1609,14 @@ public class BroadcastQueueTest {
final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
- List.of(makeRegisteredReceiver(receiverBlueApp, 10),
- makeRegisteredReceiver(receiverGreenApp, 10),
- makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
- makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
- makeRegisteredReceiver(receiverYellowApp, -10))));
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
- List.of(makeRegisteredReceiver(receiverBlueApp))));
- }
-
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp, 10),
+ makeRegisteredReceiver(receiverGreenApp, 10),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeRegisteredReceiver(receiverYellowApp, -10))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverBlueApp))));
waitForIdle();
// Ignore the final foreground broadcast
@@ -1744,17 +1730,15 @@ public class BroadcastQueueTest {
final IIntentReceiver resultToFirst = mock(IIntentReceiver.class);
final IIntentReceiver resultToSecond = mock(IIntentReceiver.class);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
- makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
- resultToFirst, null));
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
- enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
- resultToSecond, null));
- }
+ enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+ resultToFirst, null));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED))));
+ enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)),
+ resultToSecond, null));
waitForIdle();
final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
@@ -1835,14 +1819,12 @@ public class BroadcastQueueTest {
timeTickFirst.putExtra(Intent.EXTRA_INDEX, "third");
timeTickThird.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- enqueueBroadcast(makeBroadcastRecord(timeTickFirst, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
- enqueueBroadcast(makeBroadcastRecord(timeTickSecond, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
- enqueueBroadcast(makeBroadcastRecord(timeTickThird, callerApp,
- List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
- }
+ enqueueBroadcast(makeBroadcastRecord(timeTickFirst, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
+ enqueueBroadcast(makeBroadcastRecord(timeTickSecond, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
+ enqueueBroadcast(makeBroadcastRecord(timeTickThird, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE))));
waitForIdle();
final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE,
@@ -1877,26 +1859,26 @@ public class BroadcastQueueTest {
assertTrue(mQueue.isIdleLocked());
assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
- enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
- List.of(makeRegisteredReceiver(receiverApp))));
- afterFirst = SystemClock.uptimeMillis();
-
- assertFalse(mQueue.isIdleLocked());
- assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
- assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
-
- final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
- List.of(makeRegisteredReceiver(receiverApp))));
- afterSecond = SystemClock.uptimeMillis() + 10;
-
- assertFalse(mQueue.isIdleLocked());
- assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
- assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
- assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
- }
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp))));
+ afterFirst = SystemClock.uptimeMillis();
+
+ assertFalse(mQueue.isIdleLocked());
+ assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+ assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp))));
+ afterSecond = SystemClock.uptimeMillis() + 10;
+
+ assertFalse(mQueue.isIdleLocked());
+ assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst));
+ assertFalse(mQueue.isBeyondBarrierLocked(afterFirst));
+ assertFalse(mQueue.isBeyondBarrierLocked(afterSecond));
+
+ mLooper.release();
mQueue.waitForBarrier(LOG_WRITER_INFO);
assertTrue(mQueue.isBeyondBarrierLocked(afterFirst));
@@ -1980,6 +1962,46 @@ public class BroadcastQueueTest {
}
/**
+ * Confirm how many times a pathological broadcast pattern results in OOM
+ * adjusts; watches for performance regressions.
+ */
+ @Test
+ public void testOomAdjust_TriggerCount() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ // Send 8 broadcasts, 4 receivers in the first process,
+ // and 2 alternating in each of the remaining processes
+ synchronized (mAms) {
+ for (int i = 0; i < 8; i++) {
+ final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW),
+ makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+ }
+ }
+ waitForIdle();
+
+ final int expectedTimes;
+ switch (mImpl) {
+ // Original stack requested for every single receiver; yikes
+ case DEFAULT: expectedTimes = 64; break;
+ // Modern stack requests once each time we promote a process to
+ // running; we promote "green" twice, and "blue" and "yellow" once
+ case MODERN: expectedTimes = 4; break;
+ default: throw new UnsupportedOperationException();
+ }
+
+ verify(mAms, times(expectedTimes))
+ .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER));
+ }
+
+ /**
* Verify that expected events are triggered when a broadcast is finished.
*/
@Test
@@ -2007,25 +2029,23 @@ public class BroadcastQueueTest {
final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
- try (SyncBarrier b = new SyncBarrier(mHandlerThread)) {
- final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp);
- final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp);
- final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW);
- final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE);
- enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
- List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver)));
-
- doAnswer(invocation -> {
- final BroadcastRecord r = invocation.getArgument(0);
- final Object o = invocation.getArgument(1);
- if (airplane.getAction().equals(r.intent.getAction())
- && (isReceiverEquals(o, greenReceiver)
- || isReceiverEquals(o, orangeReceiver))) {
- return "test skipped receiver";
- }
- return null;
- }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any());
- }
+ final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp);
+ final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp);
+ final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW);
+ final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver)));
+
+ doAnswer(invocation -> {
+ final BroadcastRecord r = invocation.getArgument(0);
+ final Object o = invocation.getArgument(1);
+ if (airplane.getAction().equals(r.intent.getAction())
+ && (isReceiverEquals(o, greenReceiver)
+ || isReceiverEquals(o, orangeReceiver))) {
+ return "test skipped receiver";
+ }
+ return null;
+ }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any());
waitForIdle();
// Verify that only blue and yellow receiver apps received the broadcast.
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 2b6f2174d49b..08952eab071f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,7 +24,12 @@ import static android.content.Intent.ACTION_TIME_CHANGED;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING;
+import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED;
+import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount;
import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive;
import static com.android.server.am.BroadcastRecord.calculateUrgent;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
@@ -58,7 +63,6 @@ import androidx.test.filters.SmallTest;
import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -79,6 +83,7 @@ import java.util.function.BiFunction;
@SmallTest
@RunWith(MockitoJUnitRunner.class)
public class BroadcastRecordTest {
+ private static final String TAG = "BroadcastRecordTest";
private static final int USER0 = UserHandle.USER_SYSTEM;
private static final int USER1 = USER0 + 1;
@@ -120,13 +125,13 @@ public class BroadcastRecordTest {
assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
assertArrayEquals(new int[] {-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
}
@@ -142,12 +147,12 @@ public class BroadcastRecordTest {
createResolveInfo(PACKAGE3, getAppId(3), 10))));
assertArrayEquals(new int[] {-1,-1,-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 0),
createResolveInfo(PACKAGE2, getAppId(2), 0),
createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
assertArrayEquals(new int[] {-1,-1,-1},
- calculateBlockedUntilTerminalCount(List.of(
+ calculateBlockedUntilBeyondCount(List.of(
createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 10),
createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
@@ -156,26 +161,176 @@ public class BroadcastRecordTest {
@Test
public void testIsPrioritized_Yes() {
assertTrue(isPrioritized(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ createResolveInfo(PACKAGE3, getAppId(3), -10))));
assertTrue(isPrioritized(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0),
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10))));
+ createResolveInfo(PACKAGE3, getAppId(3), 0))));
assertArrayEquals(new int[] {0,1,2},
- calculateBlockedUntilTerminalCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), -10),
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 10),
createResolveInfo(PACKAGE2, getAppId(2), 0),
- createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), -10)), false));
assertArrayEquals(new int[] {0,0,2,3,3},
- calculateBlockedUntilTerminalCount(List.of(
- createResolveInfo(PACKAGE1, getAppId(1), 0),
- createResolveInfo(PACKAGE2, getAppId(2), 0),
+ calculateBlockedUntilBeyondCount(List.of(
+ createResolveInfo(PACKAGE1, getAppId(1), 20),
+ createResolveInfo(PACKAGE2, getAppId(2), 20),
createResolveInfo(PACKAGE3, getAppId(3), 10),
- createResolveInfo(PACKAGE3, getAppId(3), 20),
- createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
+ createResolveInfo(PACKAGE3, getAppId(3), 0),
+ createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+ }
+
+ @Test
+ public void testSetDeliveryState_Single() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0)));
+ assertEquals(DELIVERY_PENDING, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+ assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+ // Identical state change has no effect
+ r.setDeliveryState(0, DELIVERY_DEFERRED, TAG);
+ assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 0, 1, 1);
+
+ // Moving to terminal state updates counters
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+ // Trying to change terminal state has no effect
+ r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG);
+ assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0));
+ assertBlocked(r, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+ }
+
+ @Test
+ public void testSetDeliveryState_Unordered() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0)));
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ // Even though we finish a middle item in the tranche, we're not
+ // "beyond" it because there is still unfinished work before it
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 1, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 0, 3);
+ }
+
+ @Test
+ public void testSetDeliveryState_Ordered() {
+ final BroadcastRecord r = createOrderedBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0)));
+ assertBlocked(r, false, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, true);
+ assertTerminalDeferredBeyond(r, 1, 0, 1);
+
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 0, 2);
+
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 0, 3);
+ }
+
+ @Test
+ public void testSetDeliveryState_DeferUntilActive() {
+ final BroadcastRecord r = createBroadcastRecord(
+ new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(10),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(0),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10),
+ createResolveInfoWithPriority(-10)));
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 0, 0);
+
+ r.setDeliveryState(0, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(1, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(2, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(3, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(4, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(5, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(6, DELIVERY_DEFERRED, TAG);
+ r.setDeliveryState(7, DELIVERY_PENDING, TAG);
+ r.setDeliveryState(8, DELIVERY_DEFERRED, TAG);
+
+ // Verify deferred counts ratchet up, but we're not "beyond" the first
+ // still-pending receiver
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 0, 6, 0);
+
+ // We're still not "beyond" the first still-pending receiver, even when
+ // we finish a receiver later in the first tranche
+ r.setDeliveryState(2, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, true, true, true, true, true, true);
+ assertTerminalDeferredBeyond(r, 1, 6, 0);
+
+ // Completing that last item in first tranche means we now unblock the
+ // second tranche, and since it's entirely deferred, the third traunche
+ // is unblocked too
+ r.setDeliveryState(0, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 6, 7);
+
+ // Moving a deferred item in an earlier tranche back to being pending
+ // doesn't change the fact that we've already moved beyond it
+ r.setDeliveryState(1, DELIVERY_PENDING, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 2, 5, 7);
+ r.setDeliveryState(1, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 3, 5, 7);
+
+ // Completing middle pending item is enough to fast-forward to end
+ r.setDeliveryState(7, DELIVERY_DELIVERED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 4, 5, 9);
+
+ // Moving everyone else directly into a finished state updates all the
+ // terminal counters
+ r.setDeliveryState(3, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(4, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(5, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(6, DELIVERY_SKIPPED, TAG);
+ r.setDeliveryState(8, DELIVERY_SKIPPED, TAG);
+ assertBlocked(r, false, false, false, false, false, false, false, false, false);
+ assertTerminalDeferredBeyond(r, 9, 0, 9);
}
@Test
@@ -688,6 +843,10 @@ public class BroadcastRecordTest {
: errorMsg.insert(0, "Contains unexpected receiver: ").toString();
}
+ private static ResolveInfo createResolveInfoWithPriority(int priority) {
+ return createResolveInfo(PACKAGE1, getAppId(1), priority);
+ }
+
private static ResolveInfo createResolveInfo(String packageName, int uid) {
return createResolveInfo(packageName, uid, 0);
}
@@ -738,21 +897,40 @@ public class BroadcastRecordTest {
return excludedList;
}
+ private BroadcastRecord createBroadcastRecord(Intent intent,
+ List<ResolveInfo> receivers) {
+ return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+ null /* options */, false);
+ }
+
+ private BroadcastRecord createOrderedBroadcastRecord(Intent intent,
+ List<ResolveInfo> receivers) {
+ return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */,
+ null /* options */, true);
+ }
+
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent) {
return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
- null /* options */);
+ null /* options */, false);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent, BroadcastOptions options) {
return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */,
- options);
+ options, false);
}
private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
BroadcastOptions options) {
+ return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver,
+ options, false);
+ }
+
+ private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId,
+ Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+ BroadcastOptions options, boolean ordered) {
return new BroadcastRecord(
mQueue /* queue */,
intent,
@@ -774,7 +952,7 @@ public class BroadcastRecordTest {
0 /* resultCode */,
null /* resultData */,
null /* resultExtras */,
- false /* serialized */,
+ ordered /* serialized */,
false /* sticky */,
false /* initialSticky */,
userId,
@@ -789,6 +967,20 @@ public class BroadcastRecordTest {
private static boolean isPrioritized(List<Object> receivers) {
return BroadcastRecord.isPrioritized(
- calculateBlockedUntilTerminalCount(receivers, false), false);
+ calculateBlockedUntilBeyondCount(receivers, false), false);
+ }
+
+ private static void assertBlocked(BroadcastRecord r, boolean... blocked) {
+ assertEquals(r.receivers.size(), blocked.length);
+ for (int i = 0; i < blocked.length; i++) {
+ assertEquals("blocked " + i, blocked[i], r.isBlocked(i));
+ }
+ }
+
+ private static void assertTerminalDeferredBeyond(BroadcastRecord r,
+ int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) {
+ assertEquals("terminal", expectedTerminalCount, r.terminalCount);
+ assertEquals("deferred", expectedDeferredCount, r.deferredCount);
+ assertEquals("beyond", expectedBeyondCount, r.beyondCount);
}
}
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 485ce33dfb7d..cda5456723fb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -38,11 +38,12 @@ import static android.app.ActivityManager.PROCESS_STATE_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_TOP;
import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY;
+import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY;
import static com.android.server.am.ProcessList.BACKUP_APP_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ;
@@ -254,12 +255,13 @@ public class MockingOomAdjusterTests {
* - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int).
* - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int).
*/
+ @SuppressWarnings("GuardedBy")
private void updateOomAdj(ProcessRecord... apps) {
if (apps.length == 1) {
- sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE);
} else {
setProcessesToLru(apps);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
sService.mProcessList.getLruProcessesLOSP().clear();
}
}
@@ -658,7 +660,7 @@ public class MockingOomAdjusterTests {
ServiceRecord s = bindService(app, system,
null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class));
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND,
PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT);
@@ -1226,7 +1228,7 @@ public class MockingOomAdjusterTests {
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
}
@@ -1243,7 +1245,7 @@ public class MockingOomAdjusterTests {
mock(IBinder.class));
client.mState.setMaxAdj(PERSISTENT_PROC_ADJ);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE);
doReturn(false).when(wpc).isHeavyWeightProcess();
assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj());
@@ -1497,7 +1499,7 @@ public class MockingOomAdjusterTests {
client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState());
assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState());
@@ -1919,7 +1921,7 @@ public class MockingOomAdjusterTests {
doReturn(client2).when(sService).getTopApp();
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE);
assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
}
@@ -2029,7 +2031,7 @@ public class MockingOomAdjusterTests {
setServiceMap(s3, MOCKAPP5_UID, cn3);
setServiceMap(c2s, MOCKAPP3_UID, cn4);
app2UidRecord.setIdle(false);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ,
SCHED_GROUP_DEFAULT);
@@ -2055,7 +2057,7 @@ public class MockingOomAdjusterTests {
anyInt(), anyBoolean(), anyBoolean(), anyBoolean());
doNothing().when(sService.mServices)
.scheduleServiceTimeoutLocked(any(ProcessRecord.class));
- sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE);
assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState());
assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState());
@@ -2427,7 +2429,7 @@ public class MockingOomAdjusterTests {
app2.mState.setHasShownUi(false);
sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services");
@@ -2436,7 +2438,7 @@ public class MockingOomAdjusterTests {
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
app.mState.setHasShownUi(false);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
@@ -2445,7 +2447,7 @@ public class MockingOomAdjusterTests {
app.mState.setAdjType(null);
app.mState.setSetAdj(UNKNOWN_ADJ);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2463,7 +2465,7 @@ public class MockingOomAdjusterTests {
s.lastActivity = now;
app.mServices.startService(s);
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2474,7 +2476,7 @@ public class MockingOomAdjusterTests {
app.mState.setSetAdj(UNKNOWN_ADJ);
app.mState.setHasShownUi(false);
s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1;
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
@@ -2482,7 +2484,7 @@ public class MockingOomAdjusterTests {
doReturn(userOther).when(sService.mUserController).getCurrentUserId();
sService.mOomAdjuster.handleUserSwitchedLocked();
- sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE);
+ sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE);
assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services");
assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services");
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
index 201da359aeb6..9f685b479d47 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java
@@ -104,4 +104,24 @@ public class BackupAndRestoreFeatureFlagsTest {
assertThat(BackupAndRestoreFeatureFlags.getFullBackupUtilsRouteBufferSizeBytes())
.isEqualTo(5678);
}
+
+ @Test
+ public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_notSet_returnsDefault() {
+ assertThat(
+ BackupAndRestoreFeatureFlags
+ .getUnifiedRestoreContinueAfterTransportFailureInKvRestore())
+ .isEqualTo(true);
+ }
+
+ @Test
+ public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_set_returnsSetValue() {
+ DeviceConfig.setProperty(/*namespace=*/ "backup_and_restore",
+ /*name=*/ "unified_restore_continue_after_transport_failure_in_kv_restore",
+ /*value=*/ "false", /*makeDefault=*/ false);
+
+ assertThat(
+ BackupAndRestoreFeatureFlags
+ .getUnifiedRestoreContinueAfterTransportFailureInKvRestore())
+ .isEqualTo(false);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
index 017c93975286..c84797febcfd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java
@@ -25,20 +25,33 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupTransport;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.Message;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.modules.utils.testing.TestableDeviceConfig;
import com.android.server.backup.UserBackupManagerService;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.transport.BackupTransportClient;
+import com.android.server.backup.transport.TransportConnection;
+import com.android.server.backup.transport.TransportNotAvailableException;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -62,9 +75,14 @@ public class PerformUnifiedRestoreTaskTest {
private static final String SYSTEM_PACKAGE_NAME = "android";
private static final String NON_SYSTEM_PACKAGE_NAME = "package";
- @Mock private BackupDataInput mBackupDataInput;
- @Mock private BackupDataOutput mBackupDataOutput;
- @Mock private UserBackupManagerService mBackupManagerService;
+ @Mock
+ private BackupDataInput mBackupDataInput;
+ @Mock
+ private BackupDataOutput mBackupDataOutput;
+ @Mock
+ private UserBackupManagerService mBackupManagerService;
+ @Mock
+ private TransportConnection mTransportConnection;
private Set<String> mExcludedkeys = new HashSet<>();
private Map<String, String> mBackupData = new HashMap<>();
@@ -74,12 +92,20 @@ public class PerformUnifiedRestoreTaskTest {
private Set<String> mBackupDataDump;
private PerformUnifiedRestoreTask mRestoreTask;
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ private Context mContext;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
populateTestData();
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
mBackupDataSource = new ArrayDeque<>(mBackupData.keySet());
when(mBackupDataInput.readNextHeader()).then(new Answer<Boolean>() {
@Override
@@ -106,7 +132,7 @@ public class PerformUnifiedRestoreTaskTest {
}
});
- mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService);
+ mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection);
}
private void populateTestData() {
@@ -179,4 +205,64 @@ public class PerformUnifiedRestoreTaskTest {
assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME));
}
+
+ @Test
+ public void testFailedKeyValueRestore_continueAfterFeatureEnabled_nextStateIsRunningQueue()
+ throws TransportNotAvailableException, RemoteException {
+ DeviceConfig.setProperty(
+ "backup_and_restore",
+ "unified_restore_continue_after_transport_failure_in_kv_restore",
+ "true",
+ false);
+
+ setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR);
+
+ mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE);
+ mRestoreTask.setStateDirForTesting(mContext.getCacheDir());
+
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "test.package.name";
+ mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L);
+ assertTrue(
+ mRestoreTask.getCurrentUnifiedRestoreStateForTesting()
+ == UnifiedRestoreState.RUNNING_QUEUE);
+ }
+
+ @Test
+ public void testFailedKeyValueRestore_continueAfterFeatureDisabled_nextStateIsFinal()
+ throws RemoteException, TransportNotAvailableException {
+ DeviceConfig.setProperty(
+ "backup_and_restore",
+ "unified_restore_continue_after_transport_failure_in_kv_restore",
+ "false",
+ false);
+
+ setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR);
+
+ mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE);
+ mRestoreTask.setStateDirForTesting(mContext.getCacheDir());
+
+ PackageInfo testPackageInfo = new PackageInfo();
+ testPackageInfo.packageName = "test.package.name";
+ mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L);
+ assertTrue(
+ mRestoreTask.getCurrentUnifiedRestoreStateForTesting()
+ == UnifiedRestoreState.FINAL);
+ }
+
+ private void setupForRestoreKeyValueState(int transportStatus)
+ throws RemoteException, TransportNotAvailableException {
+ // Mock BackupHandler to do nothing when executeNextState() is called
+ BackupHandler backupHandler = Mockito.mock(BackupHandler.class);
+ when(backupHandler.obtainMessage(anyInt(), any())).thenReturn(new Message());
+ when(backupHandler.sendMessage(any())).thenReturn(true);
+
+ // Return cache directory for any bookkeeping or maintaining persistent state.
+ when(mBackupManagerService.getDataDir()).thenReturn(mContext.getCacheDir());
+ when(mBackupManagerService.getBackupHandler()).thenReturn(backupHandler);
+
+ BackupTransportClient transport = Mockito.mock(BackupTransportClient.class);
+ when(transport.getRestoreData(any())).thenReturn(transportStatus);
+ when(mTransportConnection.connectOrThrow(any())).thenReturn(transport);
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 45fefe477f8a..ca857f121624 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -38,9 +38,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
@@ -51,18 +49,18 @@ import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
+import android.testing.TestableContext;
import android.util.FloatProperty;
import android.view.Display;
import android.view.DisplayInfo;
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -74,12 +72,12 @@ import com.android.server.testutils.OffsettableClock;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -97,11 +95,9 @@ public final class DisplayPowerController2Test {
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
- private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
- private Context mContextSpy;
private DisplayPowerControllerHolder mHolder;
private Sensor mProxSensor;
@@ -118,40 +114,44 @@ public final class DisplayPowerController2Test {
@Mock
private PowerManager mPowerManagerMock;
@Mock
- private Resources mResourcesMock;
- @Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(BatteryStatsService.class)
+ .build();
+
@Before
public void setUp() throws Exception {
- mSession = ExtendedMockito.mockitoSession()
- .initMocks(this)
- .strictness(Strictness.LENIENT)
- .spyStatic(SystemProperties.class)
- .spyStatic(LocalServices.class)
- .spyStatic(BatteryStatsService.class)
- .spyStatic(Settings.System.class)
- .startMocking();
- mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
mHandler = new Handler(mTestLooper.getLooper());
+
+ // Put the system into manual brightness by default, just to minimize unexpected events and
+ // have a consistent starting state
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+ addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mCdsiMock);
- when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
- when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
SystemProperties.set(anyString(), any()));
- doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
- mCdsiMock).when(() -> LocalServices.getService(
- ColorDisplayService.ColorDisplayServiceInternal.class));
doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
- doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
- Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
setUpSensors();
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -159,8 +159,8 @@ public final class DisplayPowerController2Test {
@After
public void tearDown() {
- mSession.finishMocking();
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
}
@Test
@@ -421,11 +421,9 @@ public final class DisplayPowerController2Test {
@Test
public void testDisplayBrightnessFollowers_AutomaticBrightness() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
final float brightness = 0.4f;
final float nits = 300;
final float ambientLux = 3000;
@@ -436,7 +434,7 @@ public final class DisplayPowerController2Test {
when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux);
when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
- DisplayPowerController followerDpc = mock(DisplayPowerController.class);
+ DisplayPowerController2 followerDpc = mock(DisplayPowerController2.class);
mHolder.dpc.addDisplayBrightnessFollower(followerDpc);
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -542,11 +540,9 @@ public final class DisplayPowerController2Test {
@Test
public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -577,17 +573,14 @@ public final class DisplayPowerController2Test {
@Test
public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- when(mResourcesMock.getBoolean(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
- .thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
@@ -615,12 +608,7 @@ public final class DisplayPowerController2Test {
@Test
public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
-
+ // Tests are set up with manual brightness by default, so no need to set it here.
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -632,11 +620,9 @@ public final class DisplayPowerController2Test {
@Test
public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -690,9 +676,9 @@ public final class DisplayPowerController2Test {
public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
float brightness = 0.3f;
float nits = 500;
- when(mResourcesMock.getBoolean(
- com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
- .thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+ true);
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
@@ -753,7 +739,7 @@ public final class DisplayPowerController2Test {
any(HysteresisLevels.class),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
- eq(mContextSpy),
+ eq(mContext),
any(HighBrightnessModeController.class),
any(BrightnessThrottler.class),
isNull(),
@@ -862,7 +848,7 @@ public final class DisplayPowerController2Test {
setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
final DisplayPowerController2 dpc = new DisplayPowerController2(
- mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+ mContext, injector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, display,
mBrightnessTrackerMock, brightnessSetting, () -> {},
hbmMetadata, /* bootCompleted= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index d9133a441aff..0b97c5cb6ca1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -38,9 +38,7 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.ContextWrapper;
import android.content.res.Resources;
import android.hardware.Sensor;
import android.hardware.SensorEventListener;
@@ -51,18 +49,18 @@ import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
+import android.testing.TestableContext;
import android.util.FloatProperty;
import android.view.Display;
import android.view.DisplayInfo;
-import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService;
import com.android.server.display.RampAnimator.DualRampAnimator;
@@ -75,12 +73,12 @@ import com.android.server.testutils.OffsettableClock;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.mockito.stubbing.Answer;
@@ -98,11 +96,9 @@ public final class DisplayPowerControllerTest {
private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789";
private static final float PROX_SENSOR_MAX_RANGE = 5;
- private MockitoSession mSession;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private Handler mHandler;
- private Context mContextSpy;
private DisplayPowerControllerHolder mHolder;
private Sensor mProxSensor;
@@ -119,41 +115,45 @@ public final class DisplayPowerControllerTest {
@Mock
private PowerManager mPowerManagerMock;
@Mock
- private Resources mResourcesMock;
- @Mock
private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock;
@Captor
private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor;
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getContext());
+
+ @Rule
+ public final ExtendedMockitoRule mExtendedMockitoRule =
+ new ExtendedMockitoRule.Builder(this)
+ .setStrictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .spyStatic(BatteryStatsService.class)
+ .build();
+
@Before
public void setUp() throws Exception {
- mSession = ExtendedMockito.mockitoSession()
- .initMocks(this)
- .strictness(Strictness.LENIENT)
- .spyStatic(SystemProperties.class)
- .spyStatic(LocalServices.class)
- .spyStatic(BatteryStatsService.class)
- .spyStatic(Settings.System.class)
- .startMocking();
- mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
mHandler = new Handler(mTestLooper.getLooper());
+ // Put the system into manual brightness by default, just to minimize unexpected events and
+ // have a consistent starting state
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+
addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock);
+ addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class,
+ mCdsiMock);
- when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock);
- when(mContextSpy.getResources()).thenReturn(mResourcesMock);
+ mContext.addMockSystemService(PowerManager.class, mPowerManagerMock);
doAnswer((Answer<Void>) invocationOnMock -> null).when(() ->
SystemProperties.set(anyString(), any()));
- doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock ->
- mCdsiMock).when(() -> LocalServices.getService(
- ColorDisplayService.ColorDisplayServiceInternal.class));
doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService);
- doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() ->
- Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt()));
setUpSensors();
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
@@ -161,8 +161,8 @@ public final class DisplayPowerControllerTest {
@After
public void tearDown() {
- mSession.finishMocking();
LocalServices.removeServiceForTest(WindowManagerPolicy.class);
+ LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class);
}
@Test
@@ -425,11 +425,9 @@ public final class DisplayPowerControllerTest {
@Test
public void testDisplayBrightnessFollowers_AutomaticBrightness() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
final float brightness = 0.4f;
final float nits = 300;
final float ambientLux = 3000;
@@ -547,11 +545,9 @@ public final class DisplayPowerControllerTest {
@Test
public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
@@ -582,17 +578,14 @@ public final class DisplayPowerControllerTest {
@Test
public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_DOZE;
- when(mResourcesMock.getBoolean(
- com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing))
- .thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true);
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
@@ -620,12 +613,7 @@ public final class DisplayPowerControllerTest {
@Test
public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
-
+ // Tests are set up with manual brightness by default, so no need to set it here.
DisplayPowerRequest dpr = new DisplayPowerRequest();
dpr.policy = DisplayPowerRequest.POLICY_OFF;
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
@@ -637,11 +625,10 @@ public final class DisplayPowerControllerTest {
@Test
public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() {
- doAnswer((Answer<Integer>) invocationOnMock ->
- Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC)
- .when(() -> Settings.System.getIntForUser(any(ContentResolver.class),
- eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(),
- eq(UserHandle.USER_CURRENT)));
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false);
DisplayPowerRequest dpr = new DisplayPowerRequest();
@@ -695,9 +682,10 @@ public final class DisplayPowerControllerTest {
public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
float brightness = 0.3f;
float nits = 500;
- when(mResourcesMock.getBoolean(
- com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
- .thenReturn(true);
+ mContext.getOrCreateTestableResources().addOverride(
+ com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay,
+ true);
+
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
@@ -758,7 +746,7 @@ public final class DisplayPowerControllerTest {
any(HysteresisLevels.class),
any(HysteresisLevels.class),
any(HysteresisLevels.class),
- eq(mContextSpy),
+ eq(mContext),
any(HighBrightnessModeController.class),
any(BrightnessThrottler.class),
isNull(),
@@ -866,7 +854,7 @@ public final class DisplayPowerControllerTest {
setUpDisplay(displayId, uniqueId, display, device, config, isEnabled);
final DisplayPowerController dpc = new DisplayPowerController(
- mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler,
+ mContext, injector, mDisplayPowerCallbacksMock, mHandler,
mSensorManagerMock, mDisplayBlankerMock, display,
mBrightnessTrackerMock, brightnessSetting, () -> {},
hbmMetadata, /* bootCompleted= */ false);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index 02fdfadb2d8a..754f409b3966 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -276,9 +276,9 @@ public class ThermalStatusRestrictionTest {
assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning));
assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong));
assertFalse(mThermalStatusRestriction.isJobRestricted(ui));
- assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried));
assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning));
- assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
+ assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong));
mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
new file mode 100644
index 000000000000..e1fa8f527261
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java
@@ -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.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssAntennaInfoProviderTest {
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IBinder mBinder;
+ private GnssNative mGnssNative;
+
+ private GnssAntennaInfoProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssAntennaInfoProvider(mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testOnHalStarted() {
+ verify(mGnssNative, times(1)).startAntennaInfoListening();
+ }
+
+ @Test
+ public void testOnHalRestarted() {
+ mTestProvider.onHalRestarted();
+ verify(mGnssNative, times(2)).startAntennaInfoListening();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
index fd9dfe869d52..bf96b1dec4ac 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java
@@ -74,7 +74,6 @@ public class GnssMeasurementsProviderTest {
private @Mock Context mContext;
private @Mock LocationManagerInternal mInternal;
private @Mock GnssConfiguration mMockConfiguration;
- private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks;
private @Mock IGnssMeasurementsListener mListener1;
private @Mock IGnssMeasurementsListener mListener2;
private @Mock IBinder mBinder1;
@@ -98,7 +97,6 @@ public class GnssMeasurementsProviderTest {
Injector injector = new TestInjector(mContext);
mGnssNative = spy(Objects.requireNonNull(
GnssNative.create(injector, mMockConfiguration)));
- mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks);
mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative);
mGnssNative.register();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
new file mode 100644
index 000000000000..64aa4b3fa2ff
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java
@@ -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.server.location.gnss;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNavigationMessageListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+public class GnssNavigationMessageProviderTest {
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssNavigationMessageListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssNavigationMessageProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startNavigationMessageCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopNavigationMessageCollection();
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
new file mode 100644
index 000000000000..49e5e69933f9
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssNmeaListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssNmeaProviderTest {
+
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssNmeaListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssNmeaProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssNmeaProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startNmeaMessageCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopNmeaMessageCollection();
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
new file mode 100644
index 000000000000..ce2aec7f8d5d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.location.gnss;
+
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.location.IGnssStatusListener;
+import android.location.LocationManager;
+import android.location.LocationManagerInternal;
+import android.location.util.identity.CallerIdentity;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.LocalServices;
+import com.android.server.location.gnss.hal.FakeGnssHal;
+import com.android.server.location.gnss.hal.GnssNative;
+import com.android.server.location.injector.FakeUserInfoHelper;
+import com.android.server.location.injector.Injector;
+import com.android.server.location.injector.TestInjector;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Objects;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GnssStatusProviderTest {
+ private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID;
+ private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000,
+ "mypackage", "attribution", "listener");
+ private @Mock Context mContext;
+ private @Mock LocationManagerInternal mInternal;
+ private @Mock GnssConfiguration mMockConfiguration;
+ private @Mock IGnssStatusListener mListener;
+ private @Mock IBinder mBinder;
+
+ private GnssNative mGnssNative;
+
+ private GnssStatusProvider mTestProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(mBinder).when(mListener).asBinder();
+ doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER),
+ anyInt());
+ LocalServices.addService(LocationManagerInternal.class, mInternal);
+ FakeGnssHal fakeGnssHal = new FakeGnssHal();
+ GnssNative.setGnssHalForTest(fakeGnssHal);
+ Injector injector = new TestInjector(mContext);
+ mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration)));
+ mTestProvider = new GnssStatusProvider(injector, mGnssNative);
+ mGnssNative.register();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(LocationManagerInternal.class);
+ }
+
+ @Test
+ public void testAddListener() {
+ // add a request
+ mTestProvider.addListener(IDENTITY, mListener);
+ verify(mGnssNative, times(1)).startSvStatusCollection();
+
+ // remove a request
+ mTestProvider.removeListener(mListener);
+ verify(mGnssNative, times(1)).stopSvStatusCollection();
+ }
+
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
index b7ab6f80167e..2d962acfe665 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java
@@ -562,6 +562,26 @@ public final class FakeGnssHal extends GnssNative.GnssHal {
}
@Override
+ protected boolean startSvStatusCollection() {
+ return true;
+ }
+
+ @Override
+ protected boolean stopSvStatusCollection() {
+ return true;
+ }
+
+ @Override
+ public boolean startNmeaMessageCollection() {
+ return true;
+ }
+
+ @Override
+ public boolean stopNmeaMessageCollection() {
+ return true;
+ }
+
+ @Override
protected int getBatchSize() {
return mBatchSize;
}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
index 0be678af12dc..541b07782b29 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java
@@ -16,31 +16,65 @@
package com.android.server.rollback;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
import android.util.Log;
import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog;
import com.android.server.SystemConfig;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import org.xmlpull.v1.XmlPullParser;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
+import java.util.List;
import java.util.Scanner;
+
@RunWith(AndroidJUnit4.class)
public class RollbackPackageHealthObserverTest {
+ @Mock
+ private Context mMockContext;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageWatchdog mMockPackageWatchdog;
+ @Mock
+ RollbackManager mRollbackManager;
+ @Mock
+ RollbackInfo mRollbackInfo;
+ @Mock
+ PackageRollbackInfo mPackageRollbackInfo;
+
+ private MockitoSession mSession;
+ private static final String APP_A = "com.package.a";
+ private static final long VERSION_CODE = 1L;
private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
private SystemConfig mSysConfig;
@@ -50,17 +84,74 @@ public class RollbackPackageHealthObserverTest {
@Before
public void setup() {
mSysConfig = new SystemConfigTestClass();
+
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(PackageWatchdog.class)
+ .startMocking();
+
+ // Mock PackageWatchdog
+ doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
+ .when(() -> PackageWatchdog.getInstance(mMockContext));
+
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mSession.finishMocking();
}
/**
- * Subclass of SystemConfig without running the constructor.
- */
+ * Subclass of SystemConfig without running the constructor.
+ */
private class SystemConfigTestClass extends SystemConfig {
SystemConfigTestClass() {
- super(false);
+ super(false);
}
}
+ @Test
+ public void testHealthCheckLevels() {
+ RollbackPackageHealthObserver observer =
+ spy(new RollbackPackageHealthObserver(mMockContext));
+ VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
+
+
+ when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
+
+ // Crashes with no rollbacks available
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+
+ // Make the rollbacks available
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
+ when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
+ when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage);
+
+ // native crash
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+ observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
+ // non-native crash
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
+ observer.onHealthCheckFailed(testFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
+ // Second non-native crash again
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
+ observer.onHealthCheckFailed(testFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 2));
+ // Subsequent crashes when rollbacks have completed
+ when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
+ assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
+ observer.onHealthCheckFailed(testFailedPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH, 3));
+ }
+
/**
* Test that isAutomaticRollbackDenied works correctly when packages that are not
* denied are sent.
@@ -77,7 +168,7 @@ public class RollbackPackageHealthObserverTest {
readPermissions(folder, /* Grant all permission flags */ ~0);
assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.test.package", 1))).isEqualTo(false);
+ new VersionedPackage("com.test.package", 1))).isEqualTo(false);
}
/**
@@ -96,7 +187,7 @@ public class RollbackPackageHealthObserverTest {
readPermissions(folder, /* Grant all permission flags */ ~0);
assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
+ new VersionedPackage("com.android.vending", 1))).isEqualTo(true);
}
/**
@@ -109,7 +200,7 @@ public class RollbackPackageHealthObserverTest {
readPermissions(folder, /* Grant all permission flags */ ~0);
assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig,
- new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
+ new VersionedPackage("com.android.vending", 1))).isEqualTo(false);
}
/**
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml
index 1d2a7d37d613..7e2529aa0090 100644
--- a/services/tests/servicestests/res/xml/irq_device_map_3.xml
+++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml
@@ -23,4 +23,7 @@
<device name="test.wifi.device">
<subsystem>Wifi</subsystem>
</device>
+ <device name="test.sound_trigger.device">
+ <subsystem>Sound_trigger</subsystem>
+ </device>
</irq-device-map> \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 6216c66aa54f..4b86dd048cd1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -152,8 +152,7 @@ public class AuthServiceTest {
verify(mBiometricService, never()).registerAuthenticator(
anyInt(),
- anyInt(),
- anyInt(),
+ any(),
any());
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 4cdca268fc4b..26a3ae110525 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -43,14 +43,17 @@ import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustManager;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricManager.Authenticators;
-import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricSensorReceiver;
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
@@ -82,6 +85,7 @@ public class AuthSessionTest {
private static final long TEST_REQUEST_ID = 22;
@Mock private Context mContext;
+ @Mock private Resources mResources;
@Mock private BiometricContext mBiometricContext;
@Mock private ITrustManager mTrustManager;
@Mock private DevicePolicyManager mDevicePolicyManager;
@@ -103,6 +107,7 @@ public class AuthSessionTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class));
when(mBiometricContext.updateContext(any(), anyBoolean()))
.thenAnswer(invocation -> invocation.getArgument(0));
@@ -341,6 +346,33 @@ public class AuthSessionTest {
testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null));
}
+ @Test
+ public void testCallbackOnAcquired() throws RemoteException {
+ final String acquiredStr = "test_acquired_info_callback";
+ final String acquiredStrVendor = "test_acquired_info_callback_vendor";
+ setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR);
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 0 /* operationId */,
+ 0 /* userId */);
+
+ when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial))
+ .thenReturn(acquiredStr);
+ session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0);
+ verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr));
+ verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr));
+
+ when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor))
+ .thenReturn(new String[]{acquiredStrVendor});
+ session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0);
+ verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor));
+ verify(mClientReceiver).onAcquired(
+ eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor));
+ }
+
// TODO (b/208484275) : Enable these tests
// @Test
// public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception {
@@ -458,9 +490,16 @@ public class AuthSessionTest {
IBiometricAuthenticator fingerprintAuthenticator = mock(IBiometricAuthenticator.class);
when(fingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(fingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- mSensors.add(new BiometricSensor(mContext, id,
+
+ final FingerprintSensorPropertiesInternal props = new FingerprintSensorPropertiesInternal(
+ id, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of() /* componentInfo */, type,
+ false /* resetLockoutRequiresHardwareAuthToken */);
+ mFingerprintSensorProps.add(props);
+
+ mSensors.add(new BiometricSensor(mContext,
TYPE_FINGERPRINT /* modality */,
- Authenticators.BIOMETRIC_STRONG /* strength */,
+ props,
fingerprintAuthenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
@@ -473,21 +512,6 @@ public class AuthSessionTest {
}
});
- final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
- componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
- "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */,
- "00000001" /* serialNumber */, "" /* softwareVersion */));
- componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */,
- "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
- "vendor/version/revision" /* softwareVersion */));
-
- mFingerprintSensorProps.add(new FingerprintSensorPropertiesInternal(id,
- SensorProperties.STRENGTH_STRONG,
- 5 /* maxEnrollmentsPerUser */,
- componentInfo,
- type,
- false /* resetLockoutRequiresHardwareAuthToken */));
-
when(mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
}
@@ -495,9 +519,13 @@ public class AuthSessionTest {
IBiometricAuthenticator authenticator) throws RemoteException {
when(authenticator.isHardwareDetected(any())).thenReturn(true);
when(authenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- mSensors.add(new BiometricSensor(mContext, id,
+ mSensors.add(new BiometricSensor(mContext,
TYPE_FACE /* modality */,
- Authenticators.BIOMETRIC_STRONG /* strength */,
+ new FaceSensorPropertiesInternal(id,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of() /* componentInfo */, FaceSensorProperties.TYPE_UNKNOWN,
+ true /* supportsFace Detection */, true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */),
authenticator) {
@Override
boolean confirmationAlwaysRequired(int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index 168642e3533f..b51a8c4e1b6c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS;
import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI;
@@ -66,7 +67,11 @@ import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -93,6 +98,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
import java.util.Random;
@Presubmit
@@ -114,6 +120,7 @@ public class BiometricServiceTest {
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
+ private FingerprintSensorPropertiesInternal mFingerprintProps;
private BiometricService mBiometricService;
@@ -193,6 +200,11 @@ public class BiometricServiceTest {
};
when(mInjector.getConfiguration(any())).thenReturn(config);
+
+ mFingerprintProps = new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+ STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */);
}
@Test
@@ -328,8 +340,7 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */,
- TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -401,8 +412,7 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- mBiometricService.mImpl.registerAuthenticator(0 /* id */,
- TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
mFingerprintAuthenticator);
invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
@@ -1334,9 +1344,13 @@ public class BiometricServiceTest {
for (int i = 0; i < testCases.length; i++) {
final BiometricSensor sensor =
- new BiometricSensor(mContext, 0 /* id */,
+ new BiometricSensor(mContext,
TYPE_FINGERPRINT,
- testCases[i][0],
+ new FingerprintSensorPropertiesInternal(i /* id */,
+ Utils.authenticatorStrengthToPropertyStrength(testCases[i][0]),
+ 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */),
mock(IBiometricAuthenticator.class)) {
@Override
boolean confirmationAlwaysRequired(int userId) {
@@ -1364,8 +1378,7 @@ public class BiometricServiceTest {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(0 /* testId */,
- TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
mFingerprintAuthenticator);
verify(mBiometricService.mBiometricStrengthController).updateStrengths();
@@ -1376,15 +1389,14 @@ public class BiometricServiceTest {
mBiometricService = new BiometricService(mContext, mInjector);
mBiometricService.onStart();
- final int testId = 0;
-
when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true);
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(testId /* id */,
- TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+
+ final int testId = SENSOR_ID_FINGERPRINT;
+ mBiometricService.mImpl.registerAuthenticator(TYPE_FINGERPRINT, mFingerprintProps,
mFingerprintAuthenticator);
// Downgrade the authenticator
@@ -1484,11 +1496,9 @@ public class BiometricServiceTest {
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
- 0 /* id */, 2 /* modality */, 15 /* strength */,
- mFingerprintAuthenticator);
+ 2 /* modality */, mFingerprintProps, mFingerprintAuthenticator);
mBiometricService.mImpl.registerAuthenticator(
- 0 /* id */, 2 /* modality */, 15 /* strength */,
- mFingerprintAuthenticator);
+ 2 /* modality */, mFingerprintProps, mFingerprintAuthenticator);
}
@Test(expected = IllegalArgumentException.class)
@@ -1498,9 +1508,7 @@ public class BiometricServiceTest {
mBiometricService.onStart();
mBiometricService.mImpl.registerAuthenticator(
- 0 /* id */, 2 /* modality */,
- Authenticators.BIOMETRIC_STRONG /* strength */,
- null /* authenticator */);
+ 2 /* modality */, mFingerprintProps, null /* authenticator */);
}
@Test
@@ -1511,8 +1519,13 @@ public class BiometricServiceTest {
for (String s : mInjector.getConfiguration(null)) {
SensorConfig config = new SensorConfig(s);
- mBiometricService.mImpl.registerAuthenticator(config.id, config.modality,
- config.strength, mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(config.modality,
+ new FingerprintSensorPropertiesInternal(config.id,
+ Utils.authenticatorStrengthToPropertyStrength(config.strength),
+ 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */),
+ mFingerprintAuthenticator);
}
}
@@ -1609,7 +1622,12 @@ public class BiometricServiceTest {
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LockoutTracker.LOCKOUT_NONE);
- mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality, strength,
+ mBiometricService.mImpl.registerAuthenticator(modality,
+ new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+ Utils.authenticatorStrengthToPropertyStrength(strength),
+ 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */),
mFingerprintAuthenticator);
}
@@ -1618,7 +1636,13 @@ public class BiometricServiceTest {
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
when(mFaceAuthenticator.getLockoutModeForUser(anyInt()))
.thenReturn(LockoutTracker.LOCKOUT_NONE);
- mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength,
+ mBiometricService.mImpl.registerAuthenticator(modality,
+ new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+ Utils.authenticatorStrengthToPropertyStrength(strength),
+ 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FaceSensorProperties.TYPE_UNKNOWN, true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */),
mFaceAuthenticator);
}
}
@@ -1641,15 +1665,27 @@ public class BiometricServiceTest {
when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any()))
.thenReturn(true);
when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FINGERPRINT, modality,
- strength, mFingerprintAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(modality,
+ new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+ Utils.authenticatorStrengthToPropertyStrength(strength),
+ 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UNKNOWN,
+ false /* resetLockoutRequiresHardwareAuthToken */),
+ mFingerprintAuthenticator);
}
if ((modality & BiometricAuthenticator.TYPE_FACE) != 0) {
when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
when(mFaceAuthenticator.isHardwareDetected(any())).thenReturn(true);
- mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality,
- strength, mFaceAuthenticator);
+ mBiometricService.mImpl.registerAuthenticator(modality,
+ new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+ Utils.authenticatorStrengthToPropertyStrength(strength),
+ 5 /* maxEnrollmentsPerUser */, List.of() /* componentInfo */,
+ FaceSensorProperties.TYPE_UNKNOWN,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */),
+ mFaceAuthenticator);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
index ee5ab92065ee..f7539bd27c9d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/InvalidationTrackerTest.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
+import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -27,9 +30,13 @@ import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricManager.Authenticators;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IInvalidationCallback;
+import android.hardware.biometrics.SensorPropertiesInternal;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
@@ -42,6 +49,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
@Presubmit
@SmallTest
@@ -59,26 +67,54 @@ public class InvalidationTrackerTest {
public void testCallbackReceived_whenAllStrongSensorsInvalidated() throws Exception {
final IBiometricAuthenticator authenticator1 = mock(IBiometricAuthenticator.class);
when(authenticator1.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor1 = new TestSensor(mContext, 0 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ final TestSensor sensor1 = new TestSensor(mContext,
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ new FingerprintSensorPropertiesInternal(0 /* id */,
+ STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ false /* resetLockoutRequiresHardwareAuthToken */),
authenticator1);
final IBiometricAuthenticator authenticator2 = mock(IBiometricAuthenticator.class);
when(authenticator2.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor2 = new TestSensor(mContext, 1 /* id */,
- BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG,
+ final TestSensor sensor2 = new TestSensor(mContext,
+ BiometricAuthenticator.TYPE_FINGERPRINT,
+ new FingerprintSensorPropertiesInternal(1 /* id */,
+ STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ List.of() /* componentInfo */,
+ FingerprintSensorProperties.TYPE_REAR,
+ false /* resetLockoutRequiresHardwareAuthToken */),
authenticator2);
final IBiometricAuthenticator authenticator3 = mock(IBiometricAuthenticator.class);
when(authenticator3.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor3 = new TestSensor(mContext, 2 /* id */,
- BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG,
+ final TestSensor sensor3 = new TestSensor(mContext,
+ BiometricAuthenticator.TYPE_FACE,
+ new FaceSensorPropertiesInternal(2 /* id */,
+ STRENGTH_STRONG,
+ 5 /* maxEnrollmentsPerUser */,
+ List.of() /* componentInfo */,
+ FaceSensorProperties.TYPE_RGB,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */),
authenticator3);
final IBiometricAuthenticator authenticator4 = mock(IBiometricAuthenticator.class);
when(authenticator4.hasEnrolledTemplates(anyInt(), any())).thenReturn(true);
- final TestSensor sensor4 = new TestSensor(mContext, 3 /* id */,
- BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_WEAK,
+ final TestSensor sensor4 = new TestSensor(mContext,
+ BiometricAuthenticator.TYPE_FACE,
+ new FaceSensorPropertiesInternal(3 /* id */,
+ STRENGTH_WEAK,
+ 5 /* maxEnrollmentsPerUser */,
+ List.of() /* componentInfo */,
+ FaceSensorProperties.TYPE_IR,
+ true /* supportsFace Detection */,
+ true /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresHardwareAuthToken */),
authenticator4);
final ArrayList<BiometricSensor> sensors = new ArrayList<>();
@@ -113,9 +149,9 @@ public class InvalidationTrackerTest {
private static class TestSensor extends BiometricSensor {
- TestSensor(@NonNull Context context, int id, int modality, int strength,
+ TestSensor(@NonNull Context context, int modality, @NonNull SensorPropertiesInternal props,
@NonNull IBiometricAuthenticator impl) {
- super(context, id, modality, strength, impl);
+ super(context, modality, props, impl);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
index 903ed9082481..d3f04dfcfa17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/FaceServiceRegistryTest.java
@@ -17,8 +17,6 @@
package com.android.server.biometrics.sensors.face;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
@@ -70,9 +68,7 @@ public class FaceServiceRegistryTest {
@Mock
private ServiceProvider mProvider2;
@Captor
- private ArgumentCaptor<Integer> mIdCaptor;
- @Captor
- private ArgumentCaptor<Integer> mStrengthCaptor;
+ private ArgumentCaptor<FaceSensorPropertiesInternal> mPropsCaptor;
private FaceSensorPropertiesInternal mProvider1Props;
private FaceSensorPropertiesInternal mProvider2Props;
@@ -82,13 +78,13 @@ public class FaceServiceRegistryTest {
public void setup() {
mProvider1Props = new FaceSensorPropertiesInternal(SENSOR_ID_1,
STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
- List.of(), FaceSensorProperties.TYPE_RGB,
+ List.of() /* componentInfo */, FaceSensorProperties.TYPE_RGB,
true /* supportsFace Detection */,
true /* supportsSelfIllumination */,
false /* resetLockoutRequiresHardwareAuthToken */);
mProvider2Props = new FaceSensorPropertiesInternal(SENSOR_ID_2,
STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of(), FaceSensorProperties.TYPE_IR,
+ List.of() /* componentInfo */, FaceSensorProperties.TYPE_IR,
true /* supportsFace Detection */,
true /* supportsSelfIllumination */,
false /* resetLockoutRequiresHardwareAuthToken */);
@@ -107,10 +103,9 @@ public class FaceServiceRegistryTest {
assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
verify(mBiometricService, times(2)).registerAuthenticator(
- mIdCaptor.capture(), eq(TYPE_FACE), mStrengthCaptor.capture(), any());
- assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
- assertThat(mStrengthCaptor.getAllValues())
- .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+ eq(TYPE_FACE), mPropsCaptor.capture(), any());
+ assertThat(mPropsCaptor.getAllValues())
+ .containsExactly(mProvider1Props, mProvider2Props);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
index 7468901a16af..d5d06d3b4eb8 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java
@@ -16,6 +16,8 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT;
+import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -148,6 +150,28 @@ public class FaceAuthenticationClientTest {
}
@Test
+ public void testLockoutEndsOperation() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onLockoutPermanent();
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(FACE_ERROR_LOCKOUT_PERMANENT), anyInt());
+ verify(mCallback).onClientFinished(client, false);
+ }
+
+ @Test
+ public void testTemporaryLockoutEndsOperation() throws RemoteException {
+ final FaceAuthenticationClient client = createClient(2);
+ client.start(mCallback);
+ client.onLockoutTimed(1000);
+
+ verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(),
+ eq(FACE_ERROR_LOCKOUT), anyInt());
+ verify(mCallback).onClientFinished(client, false);
+ }
+
+ @Test
public void notifyHalWhenContextChanges() throws RemoteException {
final FaceAuthenticationClient client = createClient();
client.start(mCallback);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
index 13c3f64fec93..6e09069e654b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceRegistryTest.java
@@ -17,8 +17,6 @@
package com.android.server.biometrics.sensors.fingerprint;
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_STRONG;
-import static android.hardware.biometrics.BiometricManager.Authenticators.BIOMETRIC_WEAK;
import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
import static android.hardware.biometrics.SensorProperties.STRENGTH_WEAK;
@@ -70,9 +68,7 @@ public class FingerprintServiceRegistryTest {
@Mock
private ServiceProvider mProvider2;
@Captor
- private ArgumentCaptor<Integer> mIdCaptor;
- @Captor
- private ArgumentCaptor<Integer> mStrengthCaptor;
+ private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor;
private FingerprintSensorPropertiesInternal mProvider1Props;
private FingerprintSensorPropertiesInternal mProvider2Props;
@@ -82,11 +78,11 @@ public class FingerprintServiceRegistryTest {
public void setup() {
mProvider1Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_1,
STRENGTH_WEAK, 5 /* maxEnrollmentsPerUser */,
- List.of(), FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
+ List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
mProvider2Props = new FingerprintSensorPropertiesInternal(SENSOR_ID_2,
STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
- List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+ List.of() /* componentInfo */, FingerprintSensorProperties.TYPE_UNKNOWN,
false /* resetLockoutRequiresHardwareAuthToken */);
when(mProvider1.getSensorProperties()).thenReturn(List.of(mProvider1Props));
@@ -103,10 +99,9 @@ public class FingerprintServiceRegistryTest {
assertThat(mRegistry.getProviders()).containsExactly(mProvider1, mProvider2);
assertThat(mRegistry.getAllProperties()).containsExactly(mProvider1Props, mProvider2Props);
verify(mBiometricService, times(2)).registerAuthenticator(
- mIdCaptor.capture(), eq(TYPE_FINGERPRINT), mStrengthCaptor.capture(), any());
- assertThat(mIdCaptor.getAllValues()).containsExactly(SENSOR_ID_1, SENSOR_ID_2);
- assertThat(mStrengthCaptor.getAllValues())
- .containsExactly(BIOMETRIC_WEAK, BIOMETRIC_STRONG);
+ eq(TYPE_FINGERPRINT), mPropsCaptor.capture(), any());
+ assertThat(mPropsCaptor.getAllValues())
+ .containsExactly(mProvider1Props, mProvider2Props);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
index 25a700a5275f..1089c07e6787 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintServiceTest.java
@@ -110,15 +110,17 @@ public class FingerprintServiceTest {
private final FingerprintSensorPropertiesInternal mSensorPropsDefault =
new FingerprintSensorPropertiesInternal(ID_DEFAULT, STRENGTH_STRONG,
2 /* maxEnrollmentsPerUser */,
- List.of(),
+ List.of() /* componentInfo */,
TYPE_REAR,
false /* resetLockoutRequiresHardwareAuthToken */);
private final FingerprintSensorPropertiesInternal mSensorPropsVirtual =
new FingerprintSensorPropertiesInternal(ID_VIRTUAL, STRENGTH_STRONG,
2 /* maxEnrollmentsPerUser */,
- List.of(),
+ List.of() /* componentInfo */,
TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHardwareAuthToken */);
+ @Captor
+ private ArgumentCaptor<FingerprintSensorPropertiesInternal> mPropsCaptor;
private FingerprintService mService;
@Before
@@ -166,7 +168,8 @@ public class FingerprintServiceTest {
mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
- verify(mIBiometricService).registerAuthenticator(eq(ID_DEFAULT), anyInt(), anyInt(), any());
+ verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
+ assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsDefault);
}
@Test
@@ -178,7 +181,8 @@ public class FingerprintServiceTest {
mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
- verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
+ assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual);
}
@Test
@@ -188,7 +192,8 @@ public class FingerprintServiceTest {
mService.mServiceWrapper.registerAuthenticators(HIDL_AUTHENTICATORS);
waitForRegistration();
- verify(mIBiometricService).registerAuthenticator(eq(ID_VIRTUAL), anyInt(), anyInt(), any());
+ verify(mIBiometricService).registerAuthenticator(anyInt(), mPropsCaptor.capture(), any());
+ assertThat(mPropsCaptor.getAllValues()).containsExactly(mSensorPropsVirtual);
}
private void waitForRegistration() throws Exception {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 120ddf6af7de..b539a76b521c 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -86,6 +86,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
// Test setting default restrictions for managed profile.
@Test
+ @Ignore("b/277916462")
public void testMigration_managedProfileOwner() throws Exception {
// Create a managed profile user.
final File user10dir = getServices().addUser(10, 0,
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index dd81abeec5d3..16aadacb5af6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1368,6 +1368,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testClearDeviceOwner() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -1955,6 +1956,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUserRestriction_asDo() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2123,6 +2125,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUserRestriction_asPo() {
setAsProfileOwner(admin1);
@@ -2259,6 +2262,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
);
@Test
+ @Ignore("b/277916462")
public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
final int MANAGED_PROFILE_ADMIN_UID =
UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID);
@@ -2334,6 +2338,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testNoDefaultEnabledUserRestrictions() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2925,6 +2930,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testCreateAdminSupportIntent() throws Exception {
// Setup device owner.
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
@@ -4993,6 +4999,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testWipeDataManagedProfileOnOrganizationOwnedDevice() throws Exception {
setupProfileOwner();
configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE);
@@ -7023,6 +7030,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUserControlDisabledPackages_asDO() throws Exception {
final List<String> testPackages = new ArrayList<>();
testPackages.add("package_1");
@@ -7038,6 +7046,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUserControlDisabledPackages_asPO() {
final List<String> testPackages = new ArrayList<>();
testPackages.add("package_1");
@@ -7776,6 +7785,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction()
throws Exception {
setDeviceOwner();
@@ -7858,6 +7868,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUninstallBlocked_financeDo_success() throws Exception {
String packageName = "com.android.foo.package";
setDeviceOwner();
@@ -7871,6 +7882,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testSetUserControlDisabledPackages_financeDo_success() throws Exception {
List<String> packages = new ArrayList<>();
packages.add("com.android.foo.package");
@@ -7960,6 +7972,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testAddPersistentPreferredActivity_financeDo_success() throws Exception {
IntentFilter filter = new IntentFilter();
ComponentName target = new ComponentName(admin2.getPackageName(), "test.class");
@@ -7975,6 +7988,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
}
@Test
+ @Ignore("b/277916462")
public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception {
String packageName = admin2.getPackageName();
setDeviceOwner();
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 550204b99323..4cfbb9520d5f 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -105,6 +105,7 @@ public class LocaleManagerServiceTest {
mMockPackageManager = mock(PackageManager.class);
mMockPackageMonitor = mock(PackageMonitor.class);
+ doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
// For unit tests, set the default installer info
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
.getInstallSourceInfo(anyString());
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index da9de2562930..e20f1e7065d4 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -131,6 +131,7 @@ public class SystemAppUpdateTrackerTest {
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
doReturn(InstrumentationRegistry.getContext().getContentResolver())
.when(mMockContext).getContentResolver();
+ doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt());
mStoragefile = new AtomicFile(new File(
Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index a0fb3deeb131..21a11bc0e49e 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -763,7 +763,7 @@ public class PowerManagerServiceTest {
ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
dreamManagerStateListener.capture());
- dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
when(mBatteryManagerInternalMock.getPlugType())
.thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -793,7 +793,7 @@ public class PowerManagerServiceTest {
ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
dreamManagerStateListener.capture());
- dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
when(mBatteryManagerInternalMock.getPlugType())
.thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -823,7 +823,7 @@ public class PowerManagerServiceTest {
ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
dreamManagerStateListener.capture());
- dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true);
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true);
when(mBatteryManagerInternalMock.getPlugType())
.thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK);
@@ -831,7 +831,7 @@ public class PowerManagerServiceTest {
forceAwake(); // Needs to be awake first before it can dream.
forceDream();
- dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false);
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
setPluggedIn(false);
@@ -839,6 +839,34 @@ public class PowerManagerServiceTest {
}
@Test
+ public void testWakefulnessDream_shouldStopDreamingWhenUnplugging_whenDreamPrevents() {
+ // Make sure "unplug turns on screen" is configured to true.
+ when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen))
+ .thenReturn(true);
+
+ createService();
+ startSystem();
+
+ ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener =
+ ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class);
+ verify(mDreamManagerInternalMock).registerDreamManagerStateListener(
+ dreamManagerStateListener.capture());
+
+ when(mBatteryManagerInternalMock.getPlugType())
+ .thenReturn(BatteryManager.BATTERY_PLUGGED_AC);
+ setPluggedIn(true);
+
+ forceAwake(); // Needs to be awake first before it can dream.
+ forceDream();
+ dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false);
+ when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0);
+ setPluggedIn(false);
+
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ }
+
+
+ @Test
public void testWakefulnessDoze_goToSleep() {
createService();
// Start with AWAKE state
diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
index 2230ddd8a4a7..2bde51b4eb57 100644
--- a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java
@@ -103,21 +103,45 @@ public class ShutdownCheckPointsTest {
}
@Test
- public void testCallerProcessBinderEntry() throws RemoteException {
+ public void testCallerProcessBinderEntries() throws RemoteException {
List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
runningAppProcessInfos.add(
new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
mTestInjector.setCurrentTime(1000);
+ // Matching pid in getRunningAppProcesses
mInstance.recordCheckPointInternal(1, "reason1");
+ // Mising pid in getRunningAppProcesses
+ mInstance.recordCheckPointInternal(2, "reason2");
assertEquals(
"Shutdown request from BINDER for reason reason1 "
+ "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+ "com.android.server.power.ShutdownCheckPointsTest"
- + ".testCallerProcessBinderEntry\n"
- + "From process process_name (pid=1)\n\n",
+ + ".testCallerProcessBinderEntries\n"
+ + "From process process_name (pid=1)\n\n"
+ + "Shutdown request from BINDER for reason reason2 "
+ + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+ + "com.android.server.power.ShutdownCheckPointsTest"
+ + ".testCallerProcessBinderEntries\n"
+ + "From process ? (pid=2)\n\n",
+ dumpToString());
+ }
+
+ @Test
+ public void testNullCallerProcessBinderEntries() throws RemoteException {
+ when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
+
+ mTestInjector.setCurrentTime(1000);
+ mInstance.recordCheckPointInternal(1, "reason1");
+
+ assertEquals(
+ "Shutdown request from BINDER for reason reason1 "
+ + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
+ + "com.android.server.power.ShutdownCheckPointsTest"
+ + ".testNullCallerProcessBinderEntries\n"
+ + "From process ? (pid=1)\n\n",
dumpToString());
}
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index 7cf5bc88c213..dca67d6da7ad 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM;
+import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN;
import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI;
-import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
+import static com.android.server.power.stats.wakeups.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS;
import static com.google.common.truth.Truth.assertThat;
@@ -34,6 +35,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.frameworks.servicestests.R;
+import com.android.server.power.stats.wakeups.CpuWakeupStats.Wakeup;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,10 +48,11 @@ import java.util.concurrent.ThreadLocalRandom;
@RunWith(AndroidJUnit4.class)
public class CpuWakeupStatsTest {
private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
- private static final String KERNEL_REASON_WIFI_IRQ = "120 test.wifi.device";
+ private static final String KERNEL_REASON_WIFI_IRQ = "130 test.wifi.device";
+ private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device";
private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
- private static final String KERNEL_REASON_UNSUPPORTED = "-1 test.alarm.device";
+ private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device";
private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device";
private static final int TEST_UID_1 = 13239823;
@@ -140,6 +143,40 @@ public class CpuWakeupStatsTest {
}
@Test
+ public void soundTriggerIrqAttributionSolo() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ final long wakeupTime = 1247121;
+
+ populateDefaultProcStates(obj);
+
+ obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_SOUND_TRIGGER_IRQ);
+
+ // Outside the window, so should be ignored.
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+ wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1);
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER,
+ wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2);
+ // Should be attributed
+ obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, wakeupTime + 5, TEST_UID_3,
+ TEST_UID_5);
+
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution).isNotNull();
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey(
+ TEST_UID_1)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey(
+ TEST_UID_2)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey(
+ TEST_UID_4)).isLessThan(0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).get(TEST_UID_5)).isEqualTo(
+ TEST_PROC_STATE_5);
+ }
+
+ @Test
public void wifiIrqAttributionSolo() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
final long wakeupTime = 12423121;
@@ -268,22 +305,39 @@ public class CpuWakeupStatsTest {
}
@Test
- public void unsupportedWakeupIgnored() {
+ public void abnormalAlarmAttribution() {
final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
+ populateDefaultProcStates(obj);
long wakeupTime = 970934;
- obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
+ obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_ALARM_ABNORMAL);
- // Should be ignored as this type of wakeup is unsupported.
- assertThat(obj.mWakeupEvents.size()).isEqualTo(0);
+ assertThat(obj.mWakeupEvents.size()).isEqualTo(1);
+ assertThat(obj.mWakeupEvents.valueAt(0).mType).isEqualTo(Wakeup.TYPE_ABNORMAL);
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
- // Any nearby activity should not end up in the attribution map.
- assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
+ final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime);
+ assertThat(attribution.size()).isEqualTo(1);
+ assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue();
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_1)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_2)).isLessThan(
+ 0);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_3)).isEqualTo(
+ TEST_PROC_STATE_3);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).get(TEST_UID_4)).isEqualTo(
+ TEST_PROC_STATE_4);
+ assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_ALARM).indexOfKey(TEST_UID_5)).isLessThan(
+ 0);
+ }
+
+ @Test
+ public void abortIgnored() {
+ final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler);
- wakeupTime = 883124;
+ long wakeupTime = 883124;
obj.noteWakeupTimeAndReason(wakeupTime, 3, KERNEL_REASON_ABORT);
// Should be ignored as this type of wakeup is unsupported.
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
index 43d9e60c4a2b..47a8f49e7025 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.power.stats;
+package com.android.server.power.stats.wakeups;
import static com.google.common.truth.Truth.assertThat;
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index 82bc6f6c5263..06fc01738967 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -87,6 +87,7 @@ public class UiServiceTestCase {
Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser(
any(), any(), any(), any(), any());
Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
+ Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any(), anyInt());
Mockito.doNothing().when(mContext).unregisterReceiver(any());
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index ff6c9769b69f..516fb4aa40c6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -15,23 +15,31 @@
*/
package com.android.server.notification;
-import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
+import static android.app.Notification.FLAG_AUTO_CANCEL;
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
+import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_NO_CLEAR;
+import static android.app.Notification.FLAG_ONGOING_EVENT;
+
+import static com.android.server.notification.GroupHelper.BASE_FLAGS;
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import android.annotation.SuppressLint;
import android.app.Notification;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
import androidx.test.runner.AndroidJUnit4;
@@ -45,11 +53,10 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Map;
@SmallTest
+@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
@RunWith(AndroidJUnit4.class)
public class GroupHelperTest extends UiServiceTestCase {
private @Mock GroupHelper.Callback mCallback;
@@ -82,21 +89,104 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- public void testNoGroup_postingUnderLimit() throws Exception {
+ public void testGetAutogroupSummaryFlags_noChildren() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+
+ assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_oneOngoing() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", 0);
+ children.put("b", FLAG_ONGOING_EVENT);
+ children.put("c", FLAG_BUBBLE);
+
+ assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_oneOngoingNoClear() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", 0);
+ children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR);
+ children.put("c", FLAG_BUBBLE);
+
+ assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_oneOngoingBubble() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", 0);
+ children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE);
+ children.put("c", FLAG_BUBBLE);
+
+ assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_multipleOngoing() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", 0);
+ children.put("b", FLAG_ONGOING_EVENT);
+ children.put("c", FLAG_BUBBLE);
+ children.put("d", FLAG_ONGOING_EVENT);
+
+ assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_oneAutoCancel() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", 0);
+ children.put("b", FLAG_AUTO_CANCEL);
+ children.put("c", FLAG_BUBBLE);
+
+ assertEquals(BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_allAutoCancel() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", FLAG_AUTO_CANCEL);
+ children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+ children.put("c", FLAG_AUTO_CANCEL);
+ children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE);
+
+ assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() {
+ ArrayMap<String, Integer> children = new ArrayMap<>();
+ children.put("a", FLAG_AUTO_CANCEL);
+ children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE);
+ children.put("c", FLAG_AUTO_CANCEL);
+ children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT);
+
+ assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS,
+ mGroupHelper.getAutogroupSummaryFlags(children));
+ }
+
+ @Test
+ public void testNoGroup_postingUnderLimit() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
false);
}
- verify(mCallback, never()).addAutoGroupSummary(
- eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
- verify(mCallback, never()).addAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verifyZeroInteractions(mCallback);
}
@Test
- public void testNoGroup_multiPackage() throws Exception {
+ public void testNoGroup_multiPackage() {
final String pkg = "package";
final String pkg2 = "package2";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
@@ -105,31 +195,23 @@ public class GroupHelperTest extends UiServiceTestCase {
}
mGroupHelper.onNotificationPosted(
getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false);
- verify(mCallback, never()).addAutoGroupSummary(
- eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
- verify(mCallback, never()).addAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verifyZeroInteractions(mCallback);
}
@Test
- public void testNoGroup_multiUser() throws Exception {
+ public void testNoGroup_multiUser() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM),
false);
}
mGroupHelper.onNotificationPosted(
- getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false);
- verify(mCallback, never()).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), anyBoolean());
- verify(mCallback, never()).addAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false);
+ verifyZeroInteractions(mCallback);
}
@Test
- public void testNoGroup_someAreGrouped() throws Exception {
+ public void testNoGroup_someAreGrouped() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
mGroupHelper.onNotificationPosted(
@@ -137,233 +219,344 @@ public class GroupHelperTest extends UiServiceTestCase {
}
mGroupHelper.onNotificationPosted(
getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false);
- verify(mCallback, never()).addAutoGroupSummary(
- eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean());
- verify(mCallback, never()).addAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroup(anyString());
- verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verifyZeroInteractions(mCallback);
}
@Test
- public void testPostingOverLimit() throws Exception {
+ public void testAddSummary() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
mGroupHelper.onNotificationPosted(
getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
}
@Test
- public void testPostingOverLimit_addsOngoingFlag() throws Exception {
+ public void testAddSummary_oneChildOngoing_summaryOngoing() {
final String pkg = "package";
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
if (i == 0) {
- sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
}
mGroupHelper.onNotificationPosted(sbn, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true));
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
}
@Test
- public void testAutoGroupCount_addingNoGroupSBN() {
+ public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() {
final String pkg = "package";
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
}
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ }
- for (StatusBarNotification sbn: notifications) {
- sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ @Test
+ public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(sbn, false);
}
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ }
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, true);
+ @Test
+ public void testAddSummary_summaryAutoCancelNoClear() {
+ final String pkg = "package";
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_NO_CLEAR;
+ }
+ mGroupHelper.onNotificationPosted(sbn, false);
}
-
- verify(mCallback, times(AUTOGROUP_AT_COUNT + 1))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(
- userId, pkg), AUTOGROUP_AT_COUNT + 1);
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
}
@Test
- public void testAutoGroupCount_UpdateNotification() {
+ public void testAutoGrouped_allOngoing_updateChildNotOngoing() {
final String pkg = "package";
- ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
- }
- for (StatusBarNotification sbn: notifications) {
- sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ notifications.add(sbn);
}
for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, true);
+ mGroupHelper.onNotificationPosted(sbn, false);
}
- notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
- mGroupHelper.onNotificationUpdated(notifications.get(0));
+ // One notification is no longer ongoing
+ notifications.get(0).getNotification().flags &= ~FLAG_ONGOING_EVENT;
+ mGroupHelper.onNotificationPosted(notifications.get(0), true);
- verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
-
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(
- userId, pkg), AUTOGROUP_AT_COUNT);
+ // Summary should keep FLAG_ONGOING_EVENT if any child has it
+ verify(mCallback).updateAutogroupSummary(
+ anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
}
@Test
- public void testAutoGroupCount_UpdateNotificationAfterChanges() {
+ public void testAutoGrouped_singleOngoing_removeOngoingChild() {
final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ }
+ notifications.add(sbn);
}
for (StatusBarNotification sbn: notifications) {
- sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ mGroupHelper.onNotificationPosted(sbn, false);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, true);
- }
+ // remove ongoing
+ mGroupHelper.onNotificationRemoved(notifications.get(0));
- notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT;
+ // Summary is no longer ongoing
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ }
- mGroupHelper.onNotificationUpdated(notifications.get(0));
+ @Test
+ public void testAutoGrouped_noOngoing_updateOngoingChild() {
+ final String pkg = "package";
- notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ notifications.add(sbn);
+ }
- mGroupHelper.onNotificationUpdated(notifications.get(0));
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
- verify(mCallback, times(AUTOGROUP_AT_COUNT + 3))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
+ // update to ongoing
+ notifications.get(0).getNotification().flags |= FLAG_ONGOING_EVENT;
+ mGroupHelper.onNotificationPosted(notifications.get(0), true);
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(
- userId, pkg), AUTOGROUP_AT_COUNT + 1);
+ // Summary is now ongoing
+ verify(mCallback).updateAutogroupSummary(
+ anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
}
@Test
- public void testAutoGroupCount_RemoveNotification() {
+ public void testAutoGrouped_noOngoing_addOngoingChild() {
final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ notifications.add(sbn);
}
for (StatusBarNotification sbn: notifications) {
- sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ mGroupHelper.onNotificationPosted(sbn, false);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, true);
+ // add ongoing
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ mGroupHelper.onNotificationPosted(sbn, true);
+
+ // Summary is now ongoing
+ verify(mCallback).updateAutogroupSummary(
+ anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT));
+ }
+
+ @Test
+ public void testAutoGrouped_singleOngoing_appGroupOngoingChild() {
+ final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ }
+ notifications.add(sbn);
}
- mGroupHelper.onNotificationRemoved(notifications.get(0));
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
- verify(mCallback, times(AUTOGROUP_AT_COUNT + 2))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
+ // app group the ongoing child
+ StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+ mGroupHelper.onNotificationPosted(sbn, true);
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(
- userId, pkg), AUTOGROUP_AT_COUNT);
+ // Summary is no longer ongoing
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
}
-
@Test
- public void testAutoGroupCount_UpdateToNoneOngoingNotification() {
+ public void testAutoGrouped_singleOngoing_removeNonOngoingChild() {
final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i == 0) {
+ sbn.getNotification().flags |= FLAG_ONGOING_EVENT;
+ }
+ notifications.add(sbn);
}
for (StatusBarNotification sbn: notifications) {
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ mGroupHelper.onNotificationPosted(sbn, false);
}
- for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, true);
+ // remove ongoing
+ mGroupHelper.onNotificationRemoved(notifications.get(1));
+
+ // Summary is still ongoing
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testAutoGrouped_allAutoCancel_updateChildNotAutoCancel() {
+ final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(sbn);
}
- notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- mGroupHelper.onNotificationUpdated(notifications.get(0));
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
- verify(mCallback, times(1))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
+ // One notification is no longer autocancelable
+ notifications.get(0).getNotification().flags &= ~FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(notifications.get(0), true);
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(
- userId, pkg), 1);
+ // Summary should no longer be autocancelable
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
}
@Test
- public void testAutoGroupCount_AddOneOngoingNotification() {
+ public void testAutoGrouped_almostAllAutoCancel_updateChildAutoCancel() {
final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ if (i != 0) {
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ }
+ notifications.add(sbn);
}
- StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT);
- sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT;
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
-
- for (StatusBarNotification current: notifications) {
- mGroupHelper.onNotificationPosted(current, true);
+ for (StatusBarNotification sbn: notifications) {
+ mGroupHelper.onNotificationPosted(sbn, false);
}
- verify(mCallback, times(1))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
+ // Missing notification is now autocancelable
+ notifications.get(0).getNotification().flags |= FLAG_AUTO_CANCEL;
+ mGroupHelper.onNotificationPosted(notifications.get(0), true);
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(
- userId, pkg), 1);
+ // Summary should now autocancelable
+ verify(mCallback).updateAutogroupSummary(
+ anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL));
}
@Test
- public void testAutoGroupCount_UpdateNoneOngoing() {
+ public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() {
final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
ArrayList<StatusBarNotification> notifications = new ArrayList<>();
- for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) {
- notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM));
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(sbn);
}
for (StatusBarNotification sbn: notifications) {
- sbn.setOverrideGroupKey(AUTOGROUP_KEY);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+
+ // One notification is now grouped by app
+ StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now");
+ mGroupHelper.onNotificationPosted(sbn, true);
+
+ // Summary should be still be autocancelable
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testAutoGrouped_allAutoCancel_removeChild() {
+ final String pkg = "package";
+
+ // Post AUTOGROUP_AT_COUNT ongoing notifications
+ ArrayList<StatusBarNotification> notifications = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ sbn.getNotification().flags |= FLAG_AUTO_CANCEL;
+ notifications.add(sbn);
}
for (StatusBarNotification sbn: notifications) {
- mGroupHelper.onNotificationPosted(sbn, true);
+ mGroupHelper.onNotificationPosted(sbn, false);
}
- verify(mCallback, times(0))
- .updateAutogroupSummary(anyInt(), anyString(), eq(true));
+ mGroupHelper.onNotificationRemoved(notifications.get(0));
- int userId = UserHandle.SYSTEM.getIdentifier();
- assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0);
+ // Summary should still be autocancelable
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt());
}
-
@Test
- public void testDropToZeroRemoveGroup() throws Exception {
+ public void testDropToZeroRemoveGroup() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -371,7 +564,8 @@ public class GroupHelperTest extends UiServiceTestCase {
posted.add(sbn);
mGroupHelper.onNotificationPosted(sbn, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -390,7 +584,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- public void testAppStartsGrouping() throws Exception {
+ public void testAppStartsGrouping() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -399,7 +593,7 @@ public class GroupHelperTest extends UiServiceTestCase {
mGroupHelper.onNotificationPosted(sbn, false);
}
verify(mCallback, times(1)).addAutoGroupSummary(
- anyInt(), eq(pkg), anyString(), anyBoolean());
+ anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -408,9 +602,10 @@ public class GroupHelperTest extends UiServiceTestCase {
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
final StatusBarNotification sbn =
getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
- mGroupHelper.onNotificationPosted(sbn, false);
+ sbn.setOverrideGroupKey("autogrouped");
+ mGroupHelper.onNotificationPosted(sbn, true);
verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
- if (i < AUTOGROUP_AT_COUNT -1) {
+ if (i < AUTOGROUP_AT_COUNT - 1) {
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
}
}
@@ -418,8 +613,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
- public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
- throws Exception {
+ public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -427,7 +621,8 @@ public class GroupHelperTest extends UiServiceTestCase {
posted.add(sbn);
mGroupHelper.onNotificationPosted(sbn, false);
}
- verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false));
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS));
verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
@@ -441,10 +636,7 @@ public class GroupHelperTest extends UiServiceTestCase {
Mockito.reset(mCallback);
// only one child remains
- Map<String, LinkedHashSet<String>> ungroupedForUser =
- mGroupHelper.mUngroupedNotifications.get(UserHandle.USER_SYSTEM);
- assertNotNull(ungroupedForUser);
- assertEquals(1, ungroupedForUser.get(pkg).size());
+ assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg));
// Add new notification; it should be autogrouped even though the total count is
// < AUTOGROUP_AT_COUNT
@@ -454,5 +646,8 @@ public class GroupHelperTest extends UiServiceTestCase {
verify(mCallback, times(1)).addAutoGroup(sbn.getKey());
verify(mCallback, never()).removeAutoGroup(anyString());
verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS));
+ verify(mCallback, never()).addAutoGroupSummary(
+ anyInt(), anyString(), anyString(), anyInt());
}
}
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 eceb589bba76..9cfdaa7cad0c 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -754,13 +754,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
String groupKey, boolean isSummary) {
+ return generateNotificationRecord(channel, id, "tag" + System.currentTimeMillis(), groupKey,
+ isSummary);
+ }
+
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
+ String tag, String groupKey, boolean isSummary) {
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setGroup(groupKey)
.setGroupSummary(isSummary);
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
- "tag" + System.currentTimeMillis(), mUid, 0,
+ tag, mUid, 0,
nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
}
@@ -1899,7 +1905,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mSummaryByGroupKey.put("pkg", summary);
mService.mAutobundledSummaries.put(0, new ArrayMap<>());
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
- mService.updateAutobundledSummaryFlags(0, "pkg", true, false);
+ mService.updateAutobundledSummaryFlags(
+ 0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false);
assertTrue(summary.getSbn().isOngoing());
}
@@ -1915,7 +1922,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey());
mService.mSummaryByGroupKey.put("pkg", summary);
- mService.updateAutobundledSummaryFlags(0, "pkg", false, false);
+ mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false);
assertFalse(summary.getSbn().isOngoing());
}
@@ -2897,7 +2904,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true);
NotificationRecord r = mService.createAutoGroupSummary(
- temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false);
+ temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0);
assertThat(r.isImportanceFixed()).isTrue();
}
@@ -4213,7 +4220,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception {
+ public void testOnlyAutogroupIfNeeded_newNotification_ghUpdate() {
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
@@ -4226,17 +4233,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups()
- throws Exception {
- NotificationRecord r =
- generateNotificationRecord(mTestNotificationChannel, 0, "group", false);
+ public void testOnlyAutogroupIfNeeded_groupChanged_ghUpdate() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", "group", false);
mService.addNotification(r);
- r = generateNotificationRecord(mTestNotificationChannel, 0, null, false);
- mService.addEnqueuedNotification(r);
+ NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", null, false);
+ mService.addEnqueuedNotification(update);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(update.getKey(),
+ update.getSbn().getPackageName(), update.getUid(),
+ SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -4244,16 +4252,39 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups()
- throws Exception {
- NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group",
- false);
+ public void testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", "group", false);
mService.addNotification(r);
- mService.addEnqueuedNotification(r);
+ NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", null, false);
+ update.getNotification().flags = FLAG_AUTO_CANCEL;
+ mService.addEnqueuedNotification(update);
NotificationManagerService.PostNotificationRunnable runnable =
- mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), SystemClock.elapsedRealtime());
+ mService.new PostNotificationRunnable(update.getKey(),
+ update.getSbn().getPackageName(), update.getUid(),
+ SystemClock.elapsedRealtime());
+ runnable.run();
+ waitForIdle();
+
+ verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean());
+ }
+
+ @Test
+ public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+ mService.addNotification(r);
+ NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0,
+ "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false);
+ update.getNotification().color = Color.BLACK;
+ mService.addEnqueuedNotification(update);
+
+ NotificationManagerService.PostNotificationRunnable runnable =
+ mService.new PostNotificationRunnable(update.getKey(),
+ update.getSbn().getPackageName(),
+ update.getUid(), SystemClock.elapsedRealtime());
runnable.run();
waitForIdle();
@@ -10220,10 +10251,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// grouphelper is a mock here, so make the calls it would make
- // add summary; wait for it to be posted
- mService.addAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), nr1.getKey(),
- true);
- waitForIdle();
+ // add summary
+ mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(),
+ nr1.getSbn().getPackageName(), nr1.getKey(),
+ GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT));
// cancel both children
mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(),
@@ -10232,9 +10263,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
nr1.getSbn().getId(), nr1.getSbn().getUserId());
waitForIdle();
- // group helper would send 'remove flag' and then 'remove summary' events
- mService.updateAutobundledSummaryFlags(nr1.getUserId(), nr1.getSbn().getPackageName(),
- false, false);
+ // group helper would send 'remove summary' event
mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName());
waitForIdle();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
index e6569f7e0ce2..9fe0e49c4ab8 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java
@@ -98,6 +98,41 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase {
}
@Test
+ public void testHasDiffs_autoBundled() {
+ NotificationRecord r = generateRecord();
+
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ 1,
+ r.getPackageVisibilityOverride(),
+ r.canShowBadge(),
+ r.canBubble(),
+ r.getNotification().isBubbleNotification(),
+ r.getChannel(),
+ r.getGroupKey(),
+ r.getPeopleOverride(),
+ r.getSnoozeCriteria(),
+ r.getUserSentiment(),
+ r.getSuppressedVisualEffects(),
+ r.getSystemGeneratedSmartActions(),
+ r.getSmartReplies(),
+ r.getImportance(),
+ r.getRankingScore(),
+ r.isConversation(),
+ r.getProposedImportance(),
+ r.hasSensitiveContent());
+
+ Bundle signals = new Bundle();
+ signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group");
+ Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
+ r.addAdjustment(adjustment);
+ NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor();
+ adjustmentExtractor.process(r);
+
+ assertTrue(extractorData.hasDiffForRankingLocked(r, 1));
+ assertTrue(extractorData.hasDiffForLoggingLocked(r, 1));
+ }
+
+ @Test
public void testHasDiffs_sensitiveContentChange() {
NotificationRecord r = generateRecord();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index b1a9f081253c..34bb664c9598 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -96,8 +96,6 @@ public class RoleObserverTest extends UiServiceTestCase {
private TestableNotificationManagerService mService;
private NotificationManagerService.RoleObserver mRoleObserver;
- private TestableContext mContext = spy(getContext());
-
@Mock
private PreferencesHelper mPreferencesHelper;
@Mock
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
new file mode 100644
index 000000000000..bcd807ab6d2f
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeDiff;
+import android.service.notification.ZenPolicy;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.ArrayMap;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class ZenModeDiffTest extends UiServiceTestCase {
+ // version is not included in the diff; manual & automatic rules have special handling
+ public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
+ Set.of("version", "manualRule", "automaticRules");
+
+ @Test
+ public void testRuleDiff_addRemoveSame() {
+ // Test add, remove, and both sides same
+ ZenModeConfig.ZenRule r = makeRule();
+
+ // Both sides same rule
+ ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
+ assertFalse(dSame.hasDiff());
+
+ // from existent rule to null: expect deleted
+ ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
+ assertTrue(deleted.hasDiff());
+ assertTrue(deleted.wasRemoved());
+
+ // from null to new rule: expect added
+ ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+ }
+
+ @Test
+ public void testRuleDiff_fieldDiffs() throws Exception {
+ // Start these the same
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
+ generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testConfigDiff_addRemoveSame() {
+ // Default config, will test add, remove, and no change
+ ZenModeConfig c = new ZenModeConfig();
+
+ ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
+ assertFalse(dSame.hasDiff());
+
+ ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
+ assertTrue(added.hasDiff());
+ assertTrue(added.wasAdded());
+
+ ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
+ assertTrue(removed.hasDiff());
+ assertTrue(removed.wasRemoved());
+ }
+
+ @Test
+ public void testConfigDiff_fieldDiffs() throws Exception {
+ // these two start the same
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // maps mapping field name -> expected output value as we set diffs
+ ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
+ ArrayMap<String, Object> expectedTo = new ArrayMap<>();
+ List<Field> fieldsForDiff = getFieldsForDiffCheck(
+ ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
+ generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);
+
+ ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(d.hasDiff());
+
+ // Now diff them and check that each of the fields has a diff
+ for (Field f : fieldsForDiff) {
+ String name = f.getName();
+ assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
+ assertTrue(d.getDiffForField(name).hasDiff());
+ assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
+ assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
+ assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
+ assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
+ }
+ }
+
+ @Test
+ public void testConfigDiff_specialSenders() {
+ // these two start the same
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // set c1 and c2 to have some different senders
+ c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
+ c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
+ c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;
+
+ ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(d.hasDiff());
+
+ // Diff in top-level fields
+ assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
+ assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());
+
+ // Bonus testing of stringification of people senders and conversation senders
+ assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
+ assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
+ }
+
+ @Test
+ public void testConfigDiff_hasRuleDiffs() {
+ // two default configs
+ ZenModeConfig c1 = new ZenModeConfig();
+ ZenModeConfig c2 = new ZenModeConfig();
+
+ // two initially-identical rules
+ ZenModeConfig.ZenRule r1 = makeRule();
+ ZenModeConfig.ZenRule r2 = makeRule();
+
+ // one that will become a manual rule
+ ZenModeConfig.ZenRule m = makeRule();
+
+ // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
+ c1.automaticRules.put(r1.id, r1);
+ ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(deleteRule.hasDiff());
+ assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
+ assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
+ assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());
+
+ // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
+ r2.component = null;
+ r2.pkg = "different";
+ c2.manualRule = m;
+ c2.automaticRules.put(r2.id, r2);
+
+ // Expect diffs in both manual rule (added) and automatic rule (changed)
+ ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
+ assertTrue(changed.hasDiff());
+ assertTrue(changed.getManualRuleDiff().hasDiff());
+
+ ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
+ assertNotNull(automaticDiffs);
+ assertTrue(automaticDiffs.containsKey("ruleId"));
+ assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
+ assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
+ assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
+ assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
+ }
+
+ // Helper methods for working with configs, policies, rules
+ // Just makes a zen rule with fields filled in
+ private ZenModeConfig.ZenRule makeRule() {
+ ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.configurationActivity = new ComponentName("a", "a");
+ rule.component = new ComponentName("b", "b");
+ rule.conditionId = new Uri.Builder().scheme("hello").build();
+ rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
+ rule.enabled = true;
+ rule.creationTime = 123;
+ rule.id = "ruleId";
+ rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.modified = false;
+ rule.name = "name";
+ rule.snoozing = true;
+ rule.pkg = "a";
+ return rule;
+ }
+
+ // Get the fields on which we would want to check a diff. The requirements are: not final or/
+ // static (as these should/can never change), and not in a specific list that's exempted.
+ private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
+ throws SecurityException {
+ Field[] fields = c.getDeclaredFields();
+ ArrayList<Field> out = new ArrayList<>();
+
+ for (Field field : fields) {
+ // Check for exempt reasons
+ int m = field.getModifiers();
+ if (Modifier.isFinal(m)
+ || Modifier.isStatic(m)
+ || exemptNames.contains(field.getName())) {
+ continue;
+ }
+ out.add(field);
+ }
+ return out;
+ }
+
+ // Generate a set of generic diffs for the specified two objects and the fields to generate
+ // diffs for, and store the results in the provided expectation maps to be able to check the
+ // output later.
+ private void generateFieldDiffs(Object a, Object b, List<Field> fields,
+ ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
+ throws Exception {
+ // different classes passed in means bad input
+ assertEquals(a.getClass(), b.getClass());
+
+ // Loop through fields for which we want to check diffs, set a diff and keep track of
+ // what we set.
+ for (Field f : fields) {
+ f.setAccessible(true);
+ // Just double-check also that the fields actually are for the class declared
+ assertEquals(f.getDeclaringClass(), a.getClass());
+ Class t = f.getType();
+ // handle the full set of primitive types first
+ if (boolean.class.equals(t)) {
+ f.setBoolean(a, true);
+ expectedA.put(f.getName(), true);
+ f.setBoolean(b, false);
+ expectedB.put(f.getName(), false);
+ } else if (int.class.equals(t)) {
+ // these are not actually valid going to be valid for arbitrary int enum fields, but
+ // we just put something in there regardless.
+ f.setInt(a, 2);
+ expectedA.put(f.getName(), 2);
+ f.setInt(b, 1);
+ expectedB.put(f.getName(), 1);
+ } else if (long.class.equals(t)) {
+ f.setLong(a, 200L);
+ expectedA.put(f.getName(), 200L);
+ f.setLong(b, 100L);
+ expectedB.put(f.getName(), 100L);
+ } else if (t.isPrimitive()) {
+ // This method doesn't yet handle other primitive types. If the relevant diff
+ // classes gain new fields of these types, please add another clause here.
+ fail("primitive type not handled by generateFieldDiffs: " + t.getName());
+ } else if (String.class.equals(t)) {
+ f.set(a, "string1");
+ expectedA.put(f.getName(), "string1");
+ f.set(b, "string2");
+ expectedB.put(f.getName(), "string2");
+ } else {
+ // catch-all for other types: have the field be "added"
+ f.set(a, null);
+ expectedA.put(f.getName(), null);
+ try {
+ f.set(b, t.getDeclaredConstructor().newInstance());
+ expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
+ } catch (Exception e) {
+ // No default constructor, or blithely attempting to construct something doesn't
+ // work for some reason. If the default value isn't null, then keep it.
+ if (f.get(b) != null) {
+ expectedB.put(f.getName(), f.get(b));
+ } else {
+ // If we can't even rely on that, fail. Have the test-writer special case
+ // something, as this is not able to be genericized.
+ fail("could not generically construct value for field: " + f.getName());
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 6f9798ea7d69..b2a54010e75e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -69,8 +69,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
@@ -78,7 +76,6 @@ import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.content.ComponentName;
import android.content.ContentResolver;
-import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -90,7 +87,6 @@ import android.media.AudioManagerInternal;
import android.media.AudioSystem;
import android.media.VolumePolicy;
import android.net.Uri;
-import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
@@ -98,6 +94,7 @@ import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
@@ -877,7 +874,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL);
assertEquals("Config mismatch: current vs expected: "
- + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig);
+ + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected,
+ mZenModeHelperSpy.mConfig);
}
@Test
@@ -1046,7 +1044,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
assertEquals(
- "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual);
+ "Config mismatch: current vs expected: "
+ + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11));
}
@@ -1062,7 +1061,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM);
assertEquals("Config mismatch: current vs original: "
- + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig);
+ + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original),
+ original, mZenModeHelperSpy.mConfig);
assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode());
}
@@ -1083,8 +1083,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10);
expected.user = 10;
- assertEquals(
- "Config mismatch: current vs original: " + actual.diff(expected), expected, actual);
+ assertEquals("Config mismatch: current vs original: "
+ + new ZenModeDiff.ConfigDiff(actual, expected),
+ expected, actual);
assertEquals(expected.hashCode(), actual.hashCode());
expected.user = 0;
assertNotEquals(expected, mZenModeHelperSpy.mConfig);
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 986fb71afa2d..e704ebf32270 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -40,6 +40,7 @@ android_test {
"platform-test-annotations",
"services.core",
"services.voiceinteraction",
+ "services.soundtrigger",
"servicestests-core-utils",
"servicestests-utils-mockito-extended",
"truth-prebuilt",
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java
new file mode 100644
index 000000000000..bc94dacea324
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+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 static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.Phrase;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.RecognitionMode;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.SoundModelType;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public final class SoundTriggerDuplicateModelHandlerTest {
+ // Component under test
+ private SoundTriggerDuplicateModelHandler mComponent;
+
+ private static final String DUPLICATE_UUID = "abcddead-beef-0123-3210-0123456789ab";
+ private static final String DIFFERENT_UUID = "0000dead-beef-0123-3210-0123456789ab";
+
+ @Mock private ISoundTriggerHal mUnderlying;
+ @Mock private ISoundTriggerHal.GlobalCallback mGlobalCallback;
+ @Mock private ISoundTriggerHal.ModelCallback mModelCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mComponent = new SoundTriggerDuplicateModelHandler(mUnderlying);
+ doNothing().when(mUnderlying).registerCallback(any());
+ mComponent.registerCallback(mGlobalCallback);
+ verify(mUnderlying).registerCallback(eq(mGlobalCallback));
+ }
+
+ @Test
+ public void loadSoundModel_throwsResourceContention_whenDuplicateUuid() {
+ final var soundModel = createSoundModelOne();
+ final var soundModelSameUuid = createSoundModelTwo();
+ // First sound model load should complete successfully
+ mComponent.loadSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+ assertEquals(
+ assertThrows(
+ RecoverableException.class,
+ () -> mComponent.loadSoundModel(soundModelSameUuid, mModelCallback))
+ .errorCode,
+ Status.RESOURCE_CONTENTION);
+ // Model has not been unloaded, so we don't get a callback
+ verify(mGlobalCallback, never()).onResourcesAvailable();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void loadSoundModel_doesNotThrowResourceContention_whenDifferentUuid() {
+ final var soundModel = createSoundModelOne();
+ // Make all other fields the same
+ final var soundModelDifferentUuid = createSoundModelOne();
+ soundModelDifferentUuid.uuid = DIFFERENT_UUID;
+ InOrder inOrder = Mockito.inOrder(mUnderlying);
+ // First sound model load should complete successfully
+ mComponent.loadSoundModel(soundModel, mModelCallback);
+ inOrder.verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+ mComponent.loadSoundModel(soundModelDifferentUuid, mModelCallback);
+ inOrder.verify(mUnderlying).loadSoundModel(eq(soundModelDifferentUuid), eq(mModelCallback));
+ // No contention, so we don't get a callback
+ verify(mGlobalCallback, never()).onResourcesAvailable();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void loadSoundModel_doesNotThrow_afterDuplicateUuidHasBeenUnloaded() {
+ final var soundModel = createSoundModelOne();
+ // First sound model load should complete successfully
+ int handle = mComponent.loadSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+ // Unload model should complete successfully
+ mComponent.unloadSoundModel(handle);
+ verify(mUnderlying).unloadSoundModel(eq(handle));
+ // Since the model with the same UUID was unloaded, the subsequent load model
+ // should succeed.
+ mComponent.loadSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying, times(2)).loadSoundModel(eq(soundModel), eq(mModelCallback));
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void unloadSoundModel_triggersResourceCallback_afterDuplicateUuidRejected() {
+ final var soundModel = createSoundModelOne();
+ final var soundModelSameUuid = createSoundModelTwo();
+ // First sound model load should complete successfully
+ int handle = mComponent.loadSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback));
+ assertEquals(
+ assertThrows(
+ RecoverableException.class,
+ () -> mComponent.loadSoundModel(soundModelSameUuid, mModelCallback))
+ .errorCode,
+ Status.RESOURCE_CONTENTION);
+ mComponent.unloadSoundModel(handle);
+ verify(mUnderlying).unloadSoundModel(eq(handle));
+ verify(mGlobalCallback).onResourcesAvailable();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ // Next tests are same as above, but for phrase sound model.
+ @Test
+ public void loadPhraseSoundModel_throwsResourceContention_whenDuplicateUuid() {
+ final var soundModel = createPhraseSoundModelOne();
+ final var soundModelSameUuid = createPhraseSoundModelTwo();
+ // First sound model load should complete successfully
+ mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+ assertEquals(
+ assertThrows(
+ RecoverableException.class,
+ () ->
+ mComponent.loadPhraseSoundModel(
+ soundModelSameUuid, mModelCallback))
+ .errorCode,
+ Status.RESOURCE_CONTENTION);
+ // Model has not been unloaded, so we don't get a callback
+ verify(mGlobalCallback, never()).onResourcesAvailable();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void loadPhraseSoundModel_doesNotThrowResourceContention_whenDifferentUuid() {
+ final var soundModel = createPhraseSoundModelOne();
+ // Make all other fields the same
+ final var soundModelDifferentUuid = createPhraseSoundModelOne();
+ soundModelDifferentUuid.common.uuid = DIFFERENT_UUID;
+ InOrder inOrder = Mockito.inOrder(mUnderlying);
+ // First sound model load should complete successfully
+ mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+ inOrder.verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+ mComponent.loadPhraseSoundModel(soundModelDifferentUuid, mModelCallback);
+ inOrder.verify(mUnderlying).loadPhraseSoundModel(eq(soundModelDifferentUuid),
+ eq(mModelCallback));
+ // No contention, so we don't get a callback
+ verify(mGlobalCallback, never()).onResourcesAvailable();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void loadPhraseSoundModel_doesNotThrow_afterDuplicateUuidHasBeenUnloaded() {
+ final var soundModel = createPhraseSoundModelOne();
+ // First sound model load should complete successfully
+ int handle = mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+ // Unload model should complete successfully
+ mComponent.unloadSoundModel(handle);
+ verify(mUnderlying).unloadSoundModel(eq(handle));
+ // Since the model with the same UUID was unloaded, the subsequent load model
+ // should succeed.
+ mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying, times(2)).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void unloadSoundModel_triggersResourceCallback_afterDuplicateUuidRejectedPhrase() {
+ final var soundModel = createPhraseSoundModelOne();
+ final var soundModelSameUuid = createPhraseSoundModelTwo();
+ // First sound model load should complete successfully
+ int handle = mComponent.loadPhraseSoundModel(soundModel, mModelCallback);
+ verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback));
+ assertEquals(
+ assertThrows(
+ RecoverableException.class,
+ () ->
+ mComponent.loadPhraseSoundModel(
+ soundModelSameUuid, mModelCallback))
+ .errorCode,
+ Status.RESOURCE_CONTENTION);
+ mComponent.unloadSoundModel(handle);
+ verify(mUnderlying).unloadSoundModel(eq(handle));
+ verify(mGlobalCallback).onResourcesAvailable();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ @Test
+ public void testDelegation() {
+ // Test that the rest of the interface delegates its calls to the underlying object
+ // appropriately.
+ // This test method does not test load/unloadSoundModel
+ var properties = new Properties();
+ InOrder inOrder = Mockito.inOrder(mUnderlying);
+ doReturn(properties).when(mUnderlying).getProperties();
+ assertEquals(mComponent.getProperties(), properties);
+ inOrder.verify(mUnderlying).getProperties();
+ var mockGlobalCallback = mock(ISoundTriggerHal.GlobalCallback.class);
+ mComponent.registerCallback(mockGlobalCallback);
+ inOrder.verify(mUnderlying).registerCallback(eq(mockGlobalCallback));
+ int modelId = 5;
+ int deviceHandle = 2;
+ int ioHandle = 3;
+ var config = mock(RecognitionConfig.class);
+ mComponent.startRecognition(modelId, deviceHandle, ioHandle, config);
+ inOrder.verify(mUnderlying)
+ .startRecognition(eq(modelId), eq(deviceHandle), eq(ioHandle), eq(config));
+
+ mComponent.stopRecognition(modelId);
+ inOrder.verify(mUnderlying).stopRecognition(eq(modelId));
+ mComponent.forceRecognitionEvent(modelId);
+ inOrder.verify(mUnderlying).forceRecognitionEvent(eq(modelId));
+ int param = 10;
+ int value = 50;
+ var modelParamRange = new ModelParameterRange();
+ doReturn(modelParamRange).when(mUnderlying).queryParameter(anyInt(), anyInt());
+ assertEquals(mComponent.queryParameter(param, value), modelParamRange);
+ inOrder.verify(mUnderlying).queryParameter(param, value);
+ doReturn(value).when(mUnderlying).getModelParameter(anyInt(), anyInt());
+ assertEquals(mComponent.getModelParameter(modelId, param), value);
+ inOrder.verify(mUnderlying).getModelParameter(eq(modelId), eq(param));
+ mComponent.setModelParameter(modelId, param, value);
+ inOrder.verify(mUnderlying).setModelParameter(eq(modelId), eq(param), eq(value));
+ var recipient = mock(IBinder.DeathRecipient.class);
+ mComponent.linkToDeath(recipient);
+ inOrder.verify(mUnderlying).linkToDeath(eq(recipient));
+ mComponent.unlinkToDeath(recipient);
+ inOrder.verify(mUnderlying).unlinkToDeath(eq(recipient));
+ mComponent.flushCallbacks();
+ inOrder.verify(mUnderlying).flushCallbacks();
+ var token = mock(IBinder.class);
+ mComponent.clientAttached(token);
+ inOrder.verify(mUnderlying).clientAttached(eq(token));
+ mComponent.clientDetached(token);
+ inOrder.verify(mUnderlying).clientDetached(eq(token));
+ mComponent.reboot();
+ inOrder.verify(mUnderlying).reboot();
+ mComponent.detach();
+ inOrder.verify(mUnderlying).detach();
+ verifyNoMoreInteractions(mUnderlying);
+ verifyNoMoreInteractions(mGlobalCallback);
+ }
+
+ private static SoundModel createSoundModelOne() {
+ SoundModel model = new SoundModel();
+ model.type = SoundModelType.GENERIC;
+ model.uuid = DUPLICATE_UUID;
+ model.vendorUuid = "87654321-5432-6543-7654-456789fedcba";
+ byte[] data = new byte[] {91, 92, 93, 94, 95};
+ model.data = TestUtil.byteArrayToParcelFileDescriptor(data);
+ model.dataSize = data.length;
+ return model;
+ }
+
+ // Different except for the same UUID
+ private static SoundModel createSoundModelTwo() {
+ SoundModel model = new SoundModel();
+ model.type = SoundModelType.GENERIC;
+ model.uuid = DUPLICATE_UUID;
+ model.vendorUuid = "12345678-9876-5432-1012-345678901234";
+ byte[] data = new byte[] {19, 18, 17, 16};
+ model.data = TestUtil.byteArrayToParcelFileDescriptor(data);
+ model.dataSize = data.length;
+ return model;
+ }
+
+ private static PhraseSoundModel createPhraseSoundModelOne() {
+ PhraseSoundModel model = new PhraseSoundModel();
+ model.common = createSoundModelOne();
+ model.common.type = SoundModelType.KEYPHRASE;
+ model.phrases = new Phrase[1];
+ model.phrases[0] = new Phrase();
+ model.phrases[0].id = 123;
+ model.phrases[0].users = new int[] {5, 6, 7};
+ model.phrases[0].locale = "locale";
+ model.phrases[0].text = "text";
+ model.phrases[0].recognitionModes =
+ RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION;
+ return model;
+ }
+
+ private static PhraseSoundModel createPhraseSoundModelTwo() {
+ PhraseSoundModel model = new PhraseSoundModel();
+ model.common = createSoundModelTwo();
+ model.common.type = SoundModelType.KEYPHRASE;
+ model.phrases = new Phrase[1];
+ model.phrases[0] = new Phrase();
+ model.phrases[0].id = 321;
+ model.phrases[0].users = new int[] {4, 3, 2, 1};
+ model.phrases[0].locale = "differentLocale";
+ model.phrases[0].text = "differentText";
+ model.phrases[0].recognitionModes = 0;
+ return model;
+ }
+}
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java
index 39561f74d7ed..3b7bc952639e 100644
--- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java
+++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java
@@ -466,7 +466,7 @@ class TestUtil {
assertEquals(43, event.phraseExtras[0].levels[0].levelPercent);
}
- private static ParcelFileDescriptor byteArrayToParcelFileDescriptor(byte[] data) {
+ static ParcelFileDescriptor byteArrayToParcelFileDescriptor(byte[] data) {
try (SharedMemory shmem = SharedMemory.create("", data.length)) {
ByteBuffer buffer = shmem.mapReadWrite();
buffer.put(data);
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 2696d2bf58b9..f12b53acd06b 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -87,6 +87,8 @@
android:showWhenLocked="true"/>
<activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
+ <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" />
+
<service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
android:foregroundServiceType="mediaProjection"
android:enabled="true">
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 b8a21ec4c030..8f2b470908c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -83,6 +83,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED;
import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED;
+import static com.android.server.wm.ActivityRecord.LAUNCH_SOURCE_TYPE_HOME;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.FINISHING;
@@ -589,12 +590,18 @@ public class ActivityRecordTests extends WindowTestsBase {
throw new IllegalStateException("Orientation in new config should be either"
+ "landscape or portrait.");
}
+
+ final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation();
+ spyOn(displayRotation);
+
activity.setRequestedOrientation(requestedOrientation);
final ActivityConfigurationChangeItem expected =
ActivityConfigurationChangeItem.obtain(newConfig);
verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
eq(activity.token), eq(expected));
+
+ verify(displayRotation).onSetRequestedOrientation();
}
@Test
@@ -3682,6 +3689,23 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(activity.inTransition());
}
+ /**
+ * Verifies the task is moved to back when back pressed if the root activity was originally
+ * started from Launcher.
+ */
+ @Test
+ public void testMoveTaskToBackWhenStartedFromLauncher() {
+ final Task task = createTask(mDisplayContent);
+ final ActivityRecord ar = createActivityRecord(task);
+ task.realActivity = ar.mActivityComponent;
+ ar.intent.setAction(Intent.ACTION_MAIN);
+ ar.intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ doReturn(true).when(ar).isLaunchSourceType(eq(LAUNCH_SOURCE_TYPE_HOME));
+
+ mAtm.mActivityClientController.onBackPressed(ar.token, null /* callback */);
+ verify(task).moveTaskToBack(any());
+ }
+
private ICompatCameraControlCallback getCompatCameraControlCallback() {
return new ICompatCameraControlCallback.Stub() {
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index ba9f809e9a2a..7330411d1dd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -1063,6 +1064,51 @@ public class DisplayContentTests extends WindowTestsBase {
assertEquals(SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation());
}
+ private void updateAllDisplayContentAndRotation(DisplayContent dc) {
+ // NB updateOrientation will not revert the user orientation until a settings change
+ // takes effect.
+ dc.updateOrientation();
+ dc.onDisplayChanged(dc);
+ dc.mWmService.updateRotation(true /* alwaysSendConfiguration */,
+ false /* forceRelayout */);
+ waitUntilHandlersIdle();
+ }
+
+ @Test
+ public void testNoSensorRevert() {
+ final DisplayContent dc = mDisplayContent;
+ spyOn(dc);
+ doReturn(true).when(dc).getIgnoreOrientationRequest();
+ final DisplayRotation dr = dc.getDisplayRotation();
+ spyOn(dr);
+ doReturn(false).when(dr).useDefaultSettingsProvider();
+ final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ app.setOrientation(SCREEN_ORIENTATION_LANDSCAPE, app);
+
+ assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
+ dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_LOCKED,
+ ROTATION_90);
+ updateAllDisplayContentAndRotation(dc);
+ assertEquals(ROTATION_90, dc.getDisplayRotation()
+ .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_90));
+
+ app.setOrientation(SCREEN_ORIENTATION_NOSENSOR);
+ updateAllDisplayContentAndRotation(dc);
+ assertTrue(dc.getRotationReversionController().isAnyOverrideActive());
+ assertEquals(ROTATION_0, dc.getRotation());
+
+ app.setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
+ updateAllDisplayContentAndRotation(dc);
+ assertFalse(dc.getRotationReversionController().isAnyOverrideActive());
+ assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED,
+ dc.getDisplayRotation().getUserRotationMode());
+ assertEquals(ROTATION_90, dc.getDisplayRotation().getUserRotation());
+ assertEquals(ROTATION_90, dc.getDisplayRotation()
+ .rotationForOrientation(SCREEN_ORIENTATION_UNSPECIFIED, ROTATION_0));
+ dc.getDisplayRotation().setUserRotation(WindowManagerPolicy.USER_ROTATION_FREE,
+ ROTATION_0);
+ }
+
@Test
public void testOnDescendantOrientationRequestChanged() {
final DisplayContent dc = createNewDisplay();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index c2b3783b7311..a3117269eb01 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -365,6 +365,23 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
}
@Test
+ public void testCameraDisconnected_revertRotationAndRefresh() throws Exception {
+ configureActivityAndDisplay(SCREEN_ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE);
+ // Open camera and test for compat treatment
+ mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_LANDSCAPE);
+ assertActivityRefreshRequested(/* refreshRequested */ true);
+ // Close camera and test for revert
+ mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+ callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+ assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+ SCREEN_ORIENTATION_UNSPECIFIED);
+ assertActivityRefreshRequested(/* refreshRequested */ true);
+ }
+
+ @Test
public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 495f868b1b11..4b2d1071d113 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -70,6 +70,7 @@ import android.view.IRotationWatcher;
import android.view.Surface;
import android.view.WindowManager;
+import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.FakeSettingsProvider;
@@ -114,6 +115,7 @@ public class DisplayRotationTests {
private static WindowManagerService sMockWm;
private DisplayContent mMockDisplayContent;
+ private DisplayRotationReversionController mMockDisplayRotationReversionController;
private DisplayPolicy mMockDisplayPolicy;
private DisplayAddress mMockDisplayAddress;
private Context mMockContext;
@@ -140,6 +142,8 @@ public class DisplayRotationTests {
private DeviceStateController mDeviceStateController;
private TestDisplayRotation mTarget;
+ @Nullable
+ private DisplayRotationImmersiveAppCompatPolicy mDisplayRotationImmersiveAppCompatPolicyMock;
@BeforeClass
public static void setUpOnce() {
@@ -165,7 +169,7 @@ public class DisplayRotationTests {
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
mMockStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
LocalServices.addService(StatusBarManagerInternal.class, mMockStatusBarManagerInternal);
-
+ mDisplayRotationImmersiveAppCompatPolicyMock = null;
mBuilder = new DisplayRotationBuilder();
}
@@ -578,6 +582,38 @@ public class DisplayRotationTests {
}
@Test
+ public void testNotifiesChoiceWhenSensorUpdates_immersiveApp() throws Exception {
+ mDisplayRotationImmersiveAppCompatPolicyMock = mock(
+ DisplayRotationImmersiveAppCompatPolicy.class);
+ when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced(
+ Surface.ROTATION_90)).thenReturn(true);
+
+ mBuilder.build();
+ configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
+
+ thawRotation();
+
+ enableOrientationSensor();
+
+ mOrientationSensorListener.onSensorChanged(createSensorEvent(Surface.ROTATION_90));
+ assertTrue(waitForUiHandler());
+
+ verify(mMockStatusBarManagerInternal).onProposedRotationChanged(Surface.ROTATION_90, true);
+
+ // An imaginary ActivityRecord.setRequestedOrientation call disables immersive mode:
+ when(mDisplayRotationImmersiveAppCompatPolicyMock.isRotationLockEnforced(
+ Surface.ROTATION_90)).thenReturn(false);
+
+ // And then ActivityRecord.setRequestedOrientation calls onSetRequestedOrientation.
+ mTarget.onSetRequestedOrientation();
+
+ // onSetRequestedOrientation should lead to a second call to
+ // mOrientationListener.onProposedRotationChanged
+ // but now, instead of notifying mMockStatusBarManagerInternal, it calls updateRotation:
+ verify(sMockWm).updateRotation(false, false);
+ }
+
+ @Test
public void testAllowAllRotations_allowsUpsideDownSuggestion()
throws Exception {
mBuilder.build();
@@ -1374,6 +1410,10 @@ public class DisplayRotationTests {
when(mMockContext.getResources().getBoolean(
com.android.internal.R.bool.config_windowManagerHalfFoldAutoRotateOverride))
.thenReturn(mSupportHalfFoldAutoRotateOverride);
+ mMockDisplayRotationReversionController =
+ mock(DisplayRotationReversionController.class);
+ when(mMockDisplayContent.getRotationReversionController())
+ .thenReturn(mMockDisplayRotationReversionController);
mMockResolver = mock(ContentResolver.class);
when(mMockContext.getContentResolver()).thenReturn(mMockResolver);
@@ -1404,7 +1444,7 @@ public class DisplayRotationTests {
}
}
- private static class TestDisplayRotation extends DisplayRotation {
+ private class TestDisplayRotation extends DisplayRotation {
IntConsumer mProposedRotationCallback;
TestDisplayRotation(DisplayContent dc, DisplayAddress address, DisplayPolicy policy,
@@ -1417,7 +1457,7 @@ public class DisplayRotationTests {
@Override
DisplayRotationImmersiveAppCompatPolicy initImmersiveAppCompatPolicy(
WindowManagerService service, DisplayContent displayContent) {
- return null;
+ return mDisplayRotationImmersiveAppCompatPolicyMock;
}
@Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index c131c84a50ce..7092b0b5ac34 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -104,7 +104,6 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
when(mMockRunner.asBinder()).thenReturn(new Binder());
mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks,
DEFAULT_DISPLAY));
- mController.mShouldAttachNavBarToAppDuringTransition = false;
mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask();
assertNotNull(mRootHomeTask);
}
@@ -814,13 +813,13 @@ public class RecentsAnimationControllerTest extends WindowTestsBase {
}
private void setupForShouldAttachNavBarDuringTransition() {
- mController.mShouldAttachNavBarToAppDuringTransition = true;
final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar"));
mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs);
mWm.setRecentsAnimationController(mController);
doReturn(navBar).when(mController).getNavigationBarWindow();
final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy());
doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy();
+ doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition();
}
private static void initializeRecentsAnimationController(RecentsAnimationController controller,
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index e96d1abf9ced..de943d240084 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -428,20 +429,24 @@ public class SizeCompatTests extends WindowTestsBase {
.setLaunchedFromUid(mActivity.getUid())
.build();
doReturn(false).when(translucentActivity).fillsParent();
- WindowConfiguration translucentWinConf = translucentActivity.getWindowConfiguration();
- translucentActivity.setActivityType(ACTIVITY_TYPE_STANDARD);
- translucentActivity.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- translucentActivity.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- translucentActivity.setAlwaysOnTop(true);
+ final Configuration requestedConfig =
+ translucentActivity.getRequestedOverrideConfiguration();
+ final WindowConfiguration translucentWinConf = requestedConfig.windowConfiguration;
+ translucentWinConf.setActivityType(ACTIVITY_TYPE_STANDARD);
+ translucentWinConf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ translucentWinConf.setDisplayWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+ translucentWinConf.setAlwaysOnTop(true);
+ translucentActivity.onRequestedOverrideConfigurationChanged(requestedConfig);
mTask.addChild(translucentActivity);
- // We check the WIndowConfiguration properties
- translucentWinConf = translucentActivity.getWindowConfiguration();
+ // The original override of WindowConfiguration should keep.
assertEquals(ACTIVITY_TYPE_STANDARD, translucentActivity.getActivityType());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getWindowingMode());
assertEquals(WINDOWING_MODE_MULTI_WINDOW, translucentWinConf.getDisplayWindowingMode());
assertTrue(translucentWinConf.isAlwaysOnTop());
+ // Unless display is going to be rotated, it should always inherit from parent.
+ assertEquals(ROTATION_UNDEFINED, translucentWinConf.getDisplayRotation());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
new file mode 100644
index 000000000000..41bfc806f839
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java
@@ -0,0 +1,194 @@
+/*
+ * 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;
+
+import static android.Manifest.permission.ACCESS_SURFACE_FLINGER;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible;
+import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.view.Gravity;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowlessWindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+
+@Presubmit
+@SmallTest
+@RunWith(WindowTestRunner.class)
+public class SurfaceControlViewHostTests {
+ private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
+ TestActivity.class);
+ private Instrumentation mInstrumentation;
+ private TestActivity mActivity;
+
+ private View mView1;
+ private View mView2;
+ private SurfaceControlViewHost mScvh1;
+ private SurfaceControlViewHost mScvh2;
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // ACCESS_SURFACE_FLINGER is necessary to call waitForWindow
+ // INTERNAL_SYSTEM_WINDOW is necessary to add SCVH with no host parent
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(ACCESS_SURFACE_FLINGER,
+ INTERNAL_SYSTEM_WINDOW);
+ mActivity = mActivityRule.launchActivity(null);
+ }
+
+ @After
+ public void tearDown() {
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void requestFocusWithMultipleWindows() throws InterruptedException, RemoteException {
+ SurfaceControl sc = new SurfaceControl.Builder()
+ .setName("SurfaceControlViewHostTests")
+ .setCallsite("requestFocusWithMultipleWindows")
+ .build();
+ mView1 = new Button(mActivity);
+ mView2 = new Button(mActivity);
+
+ mActivity.runOnUiThread(() -> {
+ TestWindowlessWindowManager wwm = new TestWindowlessWindowManager(
+ mActivity.getResources().getConfiguration(), sc, null);
+
+ try {
+ mActivity.attachToSurfaceView(sc);
+ } catch (InterruptedException e) {
+ }
+
+ mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+ wwm, "requestFocusWithMultipleWindows");
+ mScvh2 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+ wwm, "requestFocusWithMultipleWindows");
+
+
+ mView1.setBackgroundColor(Color.RED);
+ mView2.setBackgroundColor(Color.BLUE);
+
+ WindowManager.LayoutParams lp1 = new WindowManager.LayoutParams(200, 200,
+ TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ WindowManager.LayoutParams lp2 = new WindowManager.LayoutParams(100, 100,
+ TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+ mScvh1.setView(mView1, lp1);
+ mScvh2.setView(mView2, lp2);
+ });
+
+ assertTrue("Failed to wait for view1", waitForWindowVisible(mView1));
+ assertTrue("Failed to wait for view2", waitForWindowVisible(mView2));
+
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mScvh1.getFocusGrantToken(), true);
+ assertTrue("Failed to gain focus for view1", waitForWindowFocus(mView1, true));
+
+ WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */,
+ mScvh2.getFocusGrantToken(), true);
+ assertTrue("Failed to gain focus for view2", waitForWindowFocus(mView2, true));
+ }
+
+ private static class TestWindowlessWindowManager extends WindowlessWindowManager {
+ private final SurfaceControl mRoot;
+
+ TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface,
+ IBinder hostInputToken) {
+ super(c, rootSurface, hostInputToken);
+ mRoot = rootSurface;
+ }
+
+ @Override
+ protected SurfaceControl getParentSurface(IWindow window,
+ WindowManager.LayoutParams attrs) {
+ return mRoot;
+ }
+ }
+
+ public static class TestActivity extends Activity implements SurfaceHolder.Callback {
+ private SurfaceView mSurfaceView;
+ private final CountDownLatch mSvReadyLatch = new CountDownLatch(1);
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final FrameLayout content = new FrameLayout(this);
+ mSurfaceView = new SurfaceView(this);
+ mSurfaceView.setBackgroundColor(Color.BLACK);
+ mSurfaceView.setZOrderOnTop(true);
+ final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500,
+ Gravity.LEFT | Gravity.TOP);
+ content.addView(mSurfaceView, lp);
+ setContentView(content);
+ mSurfaceView.getHolder().addCallback(this);
+ }
+
+ @Override
+ public void surfaceCreated(@NonNull SurfaceHolder holder) {
+ mSvReadyLatch.countDown();
+ }
+
+ @Override
+ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
+ int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
+ }
+
+ public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException {
+ mSvReadyLatch.await();
+ new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl())
+ .show(sc).apply();
+ }
+ }
+}
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
index 8e91ca28fcf1..77efc4b0d561 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SyncEngineTests.java
@@ -42,6 +42,8 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
+import com.android.server.testutils.TestHandler;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -371,6 +373,49 @@ public class SyncEngineTests extends WindowTestsBase {
mAppWindow.removeImmediately();
}
+ @Test
+ public void testQueueSyncSet() {
+ final TestHandler testHandler = new TestHandler(null);
+ TestWindowContainer mockWC = new TestWindowContainer(mWm, true /* waiter */);
+ TestWindowContainer mockWC2 = new TestWindowContainer(mWm, true /* waiter */);
+
+ final BLASTSyncEngine bse = createTestBLASTSyncEngine(testHandler);
+
+ BLASTSyncEngine.TransactionReadyListener listener = mock(
+ BLASTSyncEngine.TransactionReadyListener.class);
+
+ int id = startSyncSet(bse, listener);
+ bse.addToSyncSet(id, mockWC);
+ bse.setReady(id);
+ bse.onSurfacePlacement();
+ verify(listener, times(0)).onTransactionReady(eq(id), notNull());
+
+ final int[] nextId = new int[]{-1};
+ bse.queueSyncSet(
+ () -> nextId[0] = startSyncSet(bse, listener),
+ () -> {
+ bse.setReady(nextId[0]);
+ bse.addToSyncSet(nextId[0], mockWC2);
+ });
+
+ // Make sure it is queued
+ assertEquals(-1, nextId[0]);
+
+ // Finish the original sync and see that we've started a new sync-set immediately but
+ // that the readiness was posted.
+ mockWC.onSyncFinishedDrawing();
+ verify(mWm.mWindowPlacerLocked).requestTraversal();
+ bse.onSurfacePlacement();
+ verify(listener, times(1)).onTransactionReady(eq(id), notNull());
+
+ assertTrue(nextId[0] != -1);
+ assertFalse(bse.isReady(nextId[0]));
+
+ // now make sure the applySync callback was posted.
+ testHandler.flush();
+ assertTrue(bse.isReady(nextId[0]));
+ }
+
static int startSyncSet(BLASTSyncEngine engine,
BLASTSyncEngine.TransactionReadyListener listener) {
return engine.startSyncSet(listener, BLAST_TIMEOUT_DURATION, "Test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 90506d4f8651..43b429c76749 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1404,19 +1404,17 @@ public class TransitionTests extends WindowTestsBase {
// We are now going to simulate closing task1 to return back to (open) task2.
final Transition closeTransition = controller.createTransition(TRANSIT_CLOSE);
- closeTransition.collectExistenceChange(task1);
- closeTransition.collectExistenceChange(activity1);
closeTransition.collectExistenceChange(task2);
closeTransition.collectExistenceChange(activity2);
closeTransition.setTransientLaunch(activity2, task1);
final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1);
assertNotNull(task1ChangeInfo);
assertTrue(task1ChangeInfo.hasChanged());
+ // Make sure the unrelated activity is NOT collected.
final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1);
- assertNotNull(activity1ChangeInfo);
- assertTrue(activity1ChangeInfo.hasChanged());
+ assertNull(activity1ChangeInfo);
// No need to wait for the activity in transient hide task.
- assertTrue(activity1.isSyncFinished());
+ assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState);
activity1.setVisibleRequested(false);
activity2.setVisibleRequested(true);
@@ -1444,6 +1442,7 @@ public class TransitionTests extends WindowTestsBase {
}
}
});
+ assertTrue(activity1.isVisible());
controller.finishTransition(closeTransition);
assertTrue(wasInFinishingTransition[0]);
assertNull(controller.mFinishingTransition);
@@ -1452,6 +1451,7 @@ public class TransitionTests extends WindowTestsBase {
assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState());
// Because task1 is occluded by task2, finishTransition should make activity1 invisible.
assertFalse(activity1.isVisibleRequested());
+ // Make sure activity1 visibility was committed
assertFalse(activity1.isVisible());
assertFalse(activity1.app.hasActivityInVisibleTask());
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 7e3ec55f262a..f85cdf0b5035 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -77,6 +77,7 @@ import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -886,7 +887,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
BLASTSyncEngine createTestBLASTSyncEngine() {
- return new BLASTSyncEngine(mWm) {
+ return createTestBLASTSyncEngine(mWm.mH);
+ }
+
+ BLASTSyncEngine createTestBLASTSyncEngine(Handler handler) {
+ return new BLASTSyncEngine(mWm, handler) {
@Override
void scheduleTimeout(SyncGroup s, long timeoutMs) {
// Disable timeout.
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 6cf2b2d7a31e..74ba45c130e3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -500,7 +500,6 @@ public class ZOrderingTests extends WindowTestsBase {
RecentsAnimationController controller = new RecentsAnimationController(
mWm, mockRunner, null, displayId);
spyOn(controller);
- controller.mShouldAttachNavBarToAppDuringTransition = true;
doReturn(mNavBarWindow).when(controller).getNavigationBarWindow();
mWm.setRecentsAnimationController(controller);
@@ -508,6 +507,10 @@ public class ZOrderingTests extends WindowTestsBase {
spyOn(mDisplayContent.mInputMethodWindow);
doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible();
+ DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+ spyOn(policy);
+ doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition();
+
// create home activity
Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
index 337e1f92050c..7fe8582f96de 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java
@@ -27,6 +27,8 @@ import android.util.Slog;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.server.audio.AudioService;
+import java.util.Arrays;
+
/**
* Represents the ALSA specification, and attributes of an ALSA device.
*/
@@ -36,17 +38,21 @@ public final class UsbAlsaDevice {
private final int mCardNum;
private final int mDeviceNum;
+ private final String mAlsaCardDeviceString;
private final String mDeviceAddress;
- private final boolean mHasOutput;
- private final boolean mHasInput;
- private final boolean mIsInputHeadset;
- private final boolean mIsOutputHeadset;
- private final boolean mIsDock;
+ // The following two constant will be used as index to access arrays.
+ private static final int INPUT = 0;
+ private static final int OUTPUT = 1;
+ private static final int NUM_DIRECTIONS = 2;
+ private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"};
+ private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS];
- private boolean mSelected = false;
- private int mOutputState;
- private int mInputState;
+ private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS];
+ private final boolean mIsDock;
+ private final int[] mDeviceType = new int[NUM_DIRECTIONS];
+ private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS];
+ private int[] mState = new int[NUM_DIRECTIONS];
private UsbAlsaJackDetector mJackDetector;
private IAudioService mAudioService;
@@ -60,11 +66,13 @@ public final class UsbAlsaDevice {
mCardNum = card;
mDeviceNum = device;
mDeviceAddress = deviceAddress;
- mHasOutput = hasOutput;
- mHasInput = hasInput;
- mIsInputHeadset = isInputHeadset;
- mIsOutputHeadset = isOutputHeadset;
+ mHasDevice[OUTPUT] = hasOutput;
+ mHasDevice[INPUT] = hasInput;
+ mIsHeadset[INPUT] = isInputHeadset;
+ mIsHeadset[OUTPUT] = isOutputHeadset;
mIsDock = isDock;
+ initDeviceType();
+ mAlsaCardDeviceString = getAlsaCardDeviceString();
}
/**
@@ -104,28 +112,28 @@ public final class UsbAlsaDevice {
* @return true if the device supports output.
*/
public boolean hasOutput() {
- return mHasOutput;
+ return mHasDevice[OUTPUT];
}
/**
* @return true if the device supports input (recording).
*/
public boolean hasInput() {
- return mHasInput;
+ return mHasDevice[INPUT];
}
/**
- * @return true if the device is a headset for purposes of input.
+ * @return true if the device is a headset for purposes of output.
*/
- public boolean isInputHeadset() {
- return mIsInputHeadset;
+ public boolean isOutputHeadset() {
+ return mIsHeadset[OUTPUT];
}
/**
- * @return true if the device is a headset for purposes of output.
+ * @return true if the device is a headset for purposes of input.
*/
- public boolean isOutputHeadset() {
- return mIsOutputHeadset;
+ public boolean isInputHeadset() {
+ return mIsHeadset[INPUT];
}
/**
@@ -157,6 +165,9 @@ public final class UsbAlsaDevice {
/** Begins a jack-detection thread. */
private synchronized void startJackDetect() {
+ if (mJackDetector != null) {
+ return;
+ }
// If no jack detect capabilities exist, mJackDetector will be null.
mJackDetector = UsbAlsaJackDetector.startJackDetect(this);
}
@@ -171,75 +182,152 @@ public final class UsbAlsaDevice {
/** Start using this device as the selected USB Audio Device. */
public synchronized void start() {
- mSelected = true;
- mInputState = 0;
- mOutputState = 0;
+ startInput();
+ startOutput();
+ }
+
+ /** Start using this device as the selected USB input device. */
+ public synchronized void startInput() {
+ startDevice(INPUT);
+ }
+
+ /** Start using this device as selected USB output device. */
+ public synchronized void startOutput() {
+ startDevice(OUTPUT);
+ }
+
+ private void startDevice(int direction) {
+ if (mIsSelected[direction]) {
+ return;
+ }
+ mIsSelected[direction] = true;
+ mState[direction] = 0;
startJackDetect();
- updateWiredDeviceConnectionState(true);
+ updateWiredDeviceConnectionState(direction, true /*enable*/);
}
/** Stop using this device as the selected USB Audio Device. */
public synchronized void stop() {
- stopJackDetect();
- updateWiredDeviceConnectionState(false);
- mSelected = false;
+ stopInput();
+ stopOutput();
}
- /** Updates AudioService with the connection state of the alsaDevice.
- * Checks ALSA Jack state for inputs and outputs before reporting.
- */
- public synchronized void updateWiredDeviceConnectionState(boolean enable) {
- if (!mSelected) {
- Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!");
+ /** Stop using this device as the selected USB input device. */
+ public synchronized void stopInput() {
+ if (!mIsSelected[INPUT]) {
return;
}
- String alsaCardDeviceString = getAlsaCardDeviceString();
- if (alsaCardDeviceString == null) {
+ if (!mIsSelected[OUTPUT]) {
+ // Stop jack detection when both input and output are stopped
+ stopJackDetect();
+ }
+ updateInputWiredDeviceConnectionState(false /*enable*/);
+ mIsSelected[INPUT] = false;
+ }
+
+ /** Stop using this device as the selected USB output device. */
+ public synchronized void stopOutput() {
+ if (!mIsSelected[OUTPUT]) {
return;
}
- try {
- // Output Device
- if (mHasOutput) {
- int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
- : (mIsOutputHeadset
- ? AudioSystem.DEVICE_OUT_USB_HEADSET
- : AudioSystem.DEVICE_OUT_USB_DEVICE);
- if (DEBUG) {
- Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device)
- + " addr:" + alsaCardDeviceString
- + " name:" + mDeviceName);
- }
- boolean connected = isOutputJackConnected();
- Slog.i(TAG, "OUTPUT JACK connected: " + connected);
- int outputState = (enable && connected) ? 1 : 0;
- if (outputState != mOutputState) {
- mOutputState = outputState;
- AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
- alsaCardDeviceString, mDeviceName);
- mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG);
- }
- }
+ if (!mIsSelected[INPUT]) {
+ // Stop jack detection when both input and output are stopped
+ stopJackDetect();
+ }
+ updateOutputWiredDeviceConnectionState(false /*enable*/);
+ mIsSelected[OUTPUT] = false;
+ }
+
+ private void initDeviceType() {
+ mDeviceType[INPUT] = mHasDevice[INPUT]
+ ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET
+ : AudioSystem.DEVICE_IN_USB_DEVICE)
+ : AudioSystem.DEVICE_NONE;
+ mDeviceType[OUTPUT] = mHasDevice[OUTPUT]
+ ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET
+ : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET
+ : AudioSystem.DEVICE_OUT_USB_DEVICE))
+ : AudioSystem.DEVICE_NONE;
+ }
- // Input Device
- if (mHasInput) {
- int device = mIsInputHeadset
- ? AudioSystem.DEVICE_IN_USB_HEADSET
- : AudioSystem.DEVICE_IN_USB_DEVICE;
- boolean connected = isInputJackConnected();
- Slog.i(TAG, "INPUT JACK connected: " + connected);
- int inputState = (enable && connected) ? 1 : 0;
- if (inputState != mInputState) {
- mInputState = inputState;
- AudioDeviceAttributes attributes = new AudioDeviceAttributes(device,
- alsaCardDeviceString, mDeviceName);
- mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG);
- }
+ /**
+ * @return the output device type that will be used to notify AudioService about device
+ * connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE}
+ * will be returned.
+ */
+ public int getOutputDeviceType() {
+ return mDeviceType[OUTPUT];
+ }
+
+ /**
+ * @return the input device type that will be used to notify AudioService about device
+ * connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE}
+ * will be returned.
+ */
+ public int getInputDeviceType() {
+ return mDeviceType[INPUT];
+ }
+
+ private boolean updateWiredDeviceConnectionState(int direction, boolean enable) {
+ if (!mIsSelected[direction]) {
+ Slog.e(TAG, "Updating wired device connection state on unselected device");
+ return false;
+ }
+ if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) {
+ Slog.d(TAG,
+ "Unable to set device connection state as " + DIRECTION_STR[direction]
+ + " device type is none");
+ return false;
+ }
+ if (mAlsaCardDeviceString == null) {
+ Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection "
+ + "state failed as alsa card device string is null");
+ return false;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction])
+ + " addr:" + mAlsaCardDeviceString
+ + " name:" + mDeviceName);
+ }
+ boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected();
+ Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected);
+ int state = (enable && connected) ? 1 : 0;
+ if (state != mState[direction]) {
+ mState[direction] = state;
+ AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+ mDeviceType[direction], mAlsaCardDeviceString, mDeviceName);
+ try {
+ mAudioService.setWiredDeviceConnectionState(attributes, state, TAG);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for "
+ + DIRECTION_STR[direction]);
+ return false;
}
- } catch (RemoteException e) {
- Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState");
}
+ return true;
}
+ /**
+ * Notify AudioService about the input device connection state.
+ *
+ * @param enable true to notify the device as connected.
+ * @return true only when it successfully notifies AudioService about the device
+ * connection state.
+ */
+ public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) {
+ return updateWiredDeviceConnectionState(INPUT, enable);
+ }
+
+ /**
+ * Notify AudioService about the output device connection state.
+ *
+ * @param enable true to notify the device as connected.
+ * @return true only when it successfully notifies AudioService about the device
+ * connection state.
+ */
+ public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) {
+ return updateWiredDeviceConnectionState(OUTPUT, enable);
+ }
/**
* @Override
@@ -249,8 +337,8 @@ public final class UsbAlsaDevice {
return "UsbAlsaDevice: [card: " + mCardNum
+ ", device: " + mDeviceNum
+ ", name: " + mDeviceName
- + ", hasOutput: " + mHasOutput
- + ", hasInput: " + mHasInput + "]";
+ + ", hasOutput: " + mHasDevice[OUTPUT]
+ + ", hasInput: " + mHasDevice[INPUT] + "]";
}
/**
@@ -262,8 +350,8 @@ public final class UsbAlsaDevice {
dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum);
dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum);
dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName);
- dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput);
- dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput);
+ dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]);
+ dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]);
dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress);
dump.end(token);
@@ -294,10 +382,8 @@ public final class UsbAlsaDevice {
UsbAlsaDevice other = (UsbAlsaDevice) obj;
return (mCardNum == other.mCardNum
&& mDeviceNum == other.mDeviceNum
- && mHasOutput == other.mHasOutput
- && mHasInput == other.mHasInput
- && mIsInputHeadset == other.mIsInputHeadset
- && mIsOutputHeadset == other.mIsOutputHeadset
+ && Arrays.equals(mHasDevice, other.mHasDevice)
+ && Arrays.equals(mIsHeadset, other.mIsHeadset)
&& mIsDock == other.mIsDock);
}
@@ -310,10 +396,10 @@ public final class UsbAlsaDevice {
int result = 1;
result = prime * result + mCardNum;
result = prime * result + mDeviceNum;
- result = prime * result + (mHasOutput ? 0 : 1);
- result = prime * result + (mHasInput ? 0 : 1);
- result = prime * result + (mIsInputHeadset ? 0 : 1);
- result = prime * result + (mIsOutputHeadset ? 0 : 1);
+ result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1);
+ result = prime * result + (mHasDevice[INPUT] ? 0 : 1);
+ result = prime * result + (mIsHeadset[INPUT] ? 0 : 1);
+ result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1);
result = prime * result + (mIsDock ? 0 : 1);
return result;
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
index c4988478df71..d4f0b59dd7f2 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java
@@ -81,7 +81,8 @@ public final class UsbAlsaJackDetector implements Runnable {
if (mStopJackDetect) {
return false;
}
- mAlsaDevice.updateWiredDeviceConnectionState(true);
+ mAlsaDevice.updateOutputWiredDeviceConnectionState(true);
+ mAlsaDevice.updateInputWiredDeviceConnectionState(true);
}
return true;
}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index aa1d556d02d3..99881e194b07 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -20,12 +20,14 @@ import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.usb.UsbDevice;
+import android.media.AudioManager;
import android.media.IAudioService;
import android.media.midi.MidiDeviceInfo;
import android.os.Bundle;
import android.os.FileObserver;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.service.usb.UsbAlsaManagerProto;
import android.util.Slog;
@@ -42,6 +44,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Stack;
/**
* UsbAlsaManager manages USB audio and MIDI devices.
@@ -51,8 +54,9 @@ public final class UsbAlsaManager {
private static final boolean DEBUG = false;
// Flag to turn on/off multi-peripheral select mode
- // Set to true to have single-device-only mode
- private static final boolean mIsSingleMode = true;
+ // Set to true to have multi-devices mode
+ private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean(
+ "ro.audio.multi_usb_mode", false /*def*/);
private static final String ALSA_DIRECTORY = "/dev/snd/";
@@ -70,7 +74,11 @@ public final class UsbAlsaManager {
// this is needed to map USB devices to ALSA Audio Devices, especially to remove an
// ALSA device when we are notified that its associated USB device has been removed.
private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>();
- private UsbAlsaDevice mSelectedDevice;
+ // A map from device type to attached devices. Given the audio framework only supports
+ // single device connection per device type, only the last attached device will be
+ // connected to audio framework. Once the last device is removed, previous device can
+ // be connected to audio framework.
+ private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>();
//
// Device Denylist
@@ -162,11 +170,6 @@ public final class UsbAlsaManager {
Slog.d(TAG, "selectAlsaDevice() " + alsaDevice);
}
- // This must be where an existing USB audio device is deselected.... (I think)
- if (mIsSingleMode && mSelectedDevice != null) {
- deselectAlsaDevice();
- }
-
// FIXME Does not yet handle the case where the setting is changed
// after device connection. Ideally we should handle the settings change
// in SettingsObserver. Here we should log that a USB device is connected
@@ -178,21 +181,18 @@ public final class UsbAlsaManager {
return;
}
- mSelectedDevice = alsaDevice;
alsaDevice.start();
+
if (DEBUG) {
Slog.d(TAG, "selectAlsaDevice() - done.");
}
}
- private synchronized void deselectAlsaDevice() {
+ private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) {
if (DEBUG) {
- Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice);
- }
- if (mSelectedDevice != null) {
- mSelectedDevice.stop();
- mSelectedDevice = null;
+ Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice);
}
+ selectedDevice.stop();
}
private int getAlsaDeviceListIndexFor(String deviceAddress) {
@@ -204,32 +204,86 @@ public final class UsbAlsaManager {
return -1;
}
- private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) {
+ private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+ if (deviceType == AudioManager.DEVICE_NONE) {
+ Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device);
+ return;
+ }
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null) {
+ mAttachedDevices.put(deviceType, new Stack<>());
+ devices = mAttachedDevices.get(deviceType);
+ }
+ devices.push(device);
+ }
+
+ private void addAlsaDevice(UsbAlsaDevice device) {
+ mAlsaDevices.add(0, device);
+ addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device);
+ addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device);
+ }
+
+ private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) {
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null) {
+ return;
+ }
+ devices.remove(device);
+ if (devices.isEmpty()) {
+ mAttachedDevices.remove(deviceType);
+ }
+ }
+
+ private UsbAlsaDevice removeAlsaDevice(String deviceAddress) {
int index = getAlsaDeviceListIndexFor(deviceAddress);
if (index > -1) {
- return mAlsaDevices.remove(index);
+ UsbAlsaDevice device = mAlsaDevices.remove(index);
+ removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device);
+ removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device);
+ return device;
} else {
return null;
}
}
- /* package */ UsbAlsaDevice selectDefaultDevice() {
+ private UsbAlsaDevice selectDefaultDevice(int deviceType) {
if (DEBUG) {
- Slog.d(TAG, "selectDefaultDevice()");
+ Slog.d(TAG, "selectDefaultDevice():" + deviceType);
}
- if (mAlsaDevices.size() > 0) {
- UsbAlsaDevice alsaDevice = mAlsaDevices.get(0);
- if (DEBUG) {
- Slog.d(TAG, " alsaDevice:" + alsaDevice);
- }
- if (alsaDevice != null) {
- selectAlsaDevice(alsaDevice);
- }
- return alsaDevice;
- } else {
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null || devices.isEmpty()) {
return null;
}
+ UsbAlsaDevice alsaDevice = devices.peek();
+ Slog.d(TAG, "select default device:" + alsaDevice);
+ if (AudioManager.isInputDevice(deviceType)) {
+ alsaDevice.startInput();
+ } else {
+ alsaDevice.startOutput();
+ }
+ return alsaDevice;
+ }
+
+ private void deselectCurrentDevice(int deviceType) {
+ if (DEBUG) {
+ Slog.d(TAG, "deselectCurrentDevice():" + deviceType);
+ }
+ if (deviceType == AudioManager.DEVICE_NONE) {
+ return;
+ }
+
+ Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType);
+ if (devices == null || devices.isEmpty()) {
+ return;
+ }
+ UsbAlsaDevice alsaDevice = devices.peek();
+ Slog.d(TAG, "deselect current device:" + alsaDevice);
+ if (AudioManager.isInputDevice(deviceType)) {
+ alsaDevice.stopInput();
+ } else {
+ alsaDevice.stopOutput();
+ }
}
/* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice,
@@ -246,6 +300,7 @@ public final class UsbAlsaManager {
AlsaCardsParser.AlsaCardRecord cardRec =
mCardsParser.findCardNumFor(deviceAddress);
if (cardRec == null) {
+ Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress);
return;
}
@@ -275,12 +330,19 @@ public final class UsbAlsaManager {
new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/,
deviceAddress, hasOutput, hasInput,
isInputHeadset, isOutputHeadset, isDock);
- if (alsaDevice != null) {
- alsaDevice.setDeviceNameAndDescription(
- cardRec.getCardName(), cardRec.getCardDescription());
- mAlsaDevices.add(0, alsaDevice);
- selectAlsaDevice(alsaDevice);
+ alsaDevice.setDeviceNameAndDescription(
+ cardRec.getCardName(), cardRec.getCardDescription());
+ if (IS_MULTI_MODE) {
+ deselectCurrentDevice(alsaDevice.getInputDeviceType());
+ deselectCurrentDevice(alsaDevice.getOutputDeviceType());
+ } else {
+ // At single mode, the first device is the selected device.
+ if (!mAlsaDevices.isEmpty()) {
+ deselectAlsaDevice(mAlsaDevices.get(0));
+ }
}
+ addAlsaDevice(alsaDevice);
+ selectAlsaDevice(alsaDevice);
}
addMidiDevice(deviceAddress, usbDevice, parser, cardRec);
@@ -346,12 +408,20 @@ public final class UsbAlsaManager {
}
// Audio
- UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress);
+ UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress);
Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice);
- if (alsaDevice != null && alsaDevice == mSelectedDevice) {
+ if (alsaDevice != null) {
waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/);
- deselectAlsaDevice();
- selectDefaultDevice(); // if there any external devices left, select one of them
+ deselectAlsaDevice(alsaDevice);
+ if (IS_MULTI_MODE) {
+ selectDefaultDevice(alsaDevice.getOutputDeviceType());
+ selectDefaultDevice(alsaDevice.getInputDeviceType());
+ } else {
+ // If there are any external devices left, select the latest attached one
+ if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) {
+ selectAlsaDevice(mAlsaDevices.get(0));
+ }
+ }
}
// MIDI
@@ -362,7 +432,6 @@ public final class UsbAlsaManager {
}
logDevices("usbDeviceRemoved()");
-
}
/* package */ void setPeripheralMidiState(boolean enabled, int card, int device) {
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 7332d2d8b0f6..de8d1440e6ac 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -9,11 +9,60 @@ package {
filegroup {
name: "services.voiceinteraction-sources",
- srcs: ["java/**/*.java"],
+ srcs: ["java/com/android/server/voiceinteraction/*.java"],
path: "java",
visibility: ["//frameworks/base/services"],
}
+filegroup {
+ name: "services.soundtrigger_middleware-sources",
+ srcs: ["java/com/android/server/soundtrigger_middleware/*.java"],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "services.soundtrigger_service-sources",
+ srcs: ["java/com/android/server/soundtrigger/*.java"],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+filegroup {
+ name: "services.soundtrigger-sources",
+ srcs: [
+ ":services.soundtrigger_service-sources",
+ ":services.soundtrigger_middleware-sources",
+ ],
+ path: "java",
+ visibility: ["//frameworks/base/services"],
+}
+
+java_library_static {
+ name: "services.soundtrigger_middleware",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.soundtrigger_middleware-sources"],
+ libs: [
+ "services.core",
+ ],
+ static_libs: [
+ "android.hardware.soundtrigger-V2.3-java",
+ ],
+ visibility: ["//visibility/base/services/tests/voiceinteraction"],
+}
+
+java_library_static {
+ name: "services.soundtrigger",
+ defaults: ["platform_service_defaults"],
+ srcs: [":services.soundtrigger_service-sources"],
+ libs: [
+ "services.core",
+ ],
+ static_libs: [
+ "services.soundtrigger_middleware",
+ ],
+}
+
java_library_static {
name: "services.voiceinteraction",
defaults: ["platform_service_defaults"],
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index 5fe1c8d2ecb0..f098155a9bf7 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -5,6 +5,9 @@
"options": [
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
+ },
+ {
+ "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest"
}
]
},
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 5efd158133ed..07dc1c66bc4d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -315,12 +315,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig,
int keyphraseId, boolean runInBatterySaverMode) {
synchronized (mLock) {
+ // TODO Remove previous callback handling
IRecognitionStatusCallback oldCallback = modelData.getCallback();
if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) {
Slog.w(TAG, "Canceling previous recognition for model id: "
+ modelData.getModelId());
try {
- oldCallback.onError(STATUS_ERROR);
+ oldCallback.onPreempted();
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in onDetectionStopped", e);
}
@@ -759,15 +760,12 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
onRecognitionAbortLocked(event);
break;
case SoundTrigger.RECOGNITION_STATUS_FAILURE:
- // Fire failures to all listeners since it's not tied to a keyphrase.
- onRecognitionFailureLocked();
- break;
case SoundTrigger.RECOGNITION_STATUS_SUCCESS:
case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE:
if (isKeyphraseRecognitionEvent(event)) {
- onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event);
+ onKeyphraseRecognitionLocked((KeyphraseRecognitionEvent) event);
} else {
- onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event);
+ onGenericRecognitionLocked((GenericRecognitionEvent) event);
}
break;
}
@@ -778,7 +776,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return event instanceof KeyphraseRecognitionEvent;
}
- private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) {
+ private void onGenericRecognitionLocked(GenericRecognitionEvent event) {
MetricsLogger.count(mContext, "sth_generic_recognition_event", 1);
if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS
&& event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
@@ -901,17 +899,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
- private void onRecognitionFailureLocked() {
- Slog.w(TAG, "Recognition failure");
- MetricsLogger.count(mContext, "sth_recognition_failure_event", 1);
- try {
- sendErrorCallbacksToAllLocked(STATUS_ERROR);
- } finally {
- internalClearModelStateLocked();
- internalClearGlobalStateLocked();
- }
- }
-
private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) {
if (event == null) {
Slog.w(TAG, "Null RecognitionEvent received.");
@@ -927,7 +914,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
return keyphraseExtras[0].id;
}
- private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) {
+ private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) {
Slog.i(TAG, "Recognition success");
MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1);
int keyphraseId = getKeyphraseIdFromEvent(event);
@@ -1001,7 +988,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
private void onServiceDiedLocked() {
try {
MetricsLogger.count(mContext, "sth_service_died", 1);
- sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT);
+ for (ModelData modelData : mModelDataMap.values()) {
+ IRecognitionStatusCallback callback = modelData.getCallback();
+ if (callback != null) {
+ try {
+ callback.onModuleDied();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "RemoteException send moduleDied for model handle " +
+ modelData.getHandle(), e);
+ }
+ }
+ }
} finally {
internalClearModelStateLocked();
internalClearGlobalStateLocked();
@@ -1111,21 +1108,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
}
}
- // Sends an error callback to all models with a valid registered callback.
- private void sendErrorCallbacksToAllLocked(int errorCode) {
- for (ModelData modelData : mModelDataMap.values()) {
- IRecognitionStatusCallback callback = modelData.getCallback();
- if (callback != null) {
- try {
- callback.onError(errorCode);
- } catch (RemoteException e) {
- Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " +
- modelData.getHandle(), e);
- }
- }
- }
- }
-
/**
* Stops and unloads all models. This is intended as a clean-up call with the expectation that
* this instance is not used after.
@@ -1342,11 +1324,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
// Notify of error if needed.
if (notifyClientOnError) {
try {
- callback.onError(status);
+ callback.onResumeFailed(status);
} catch (DeadObjectException e) {
forceStopAndUnloadModelLocked(modelData, e);
} catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
+ Slog.w(TAG, "RemoteException in onResumeFailed", e);
}
}
} else {
@@ -1382,15 +1364,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
status = mModule.stopRecognition(modelData.getHandle());
if (status != SoundTrigger.STATUS_OK) {
- Slog.w(TAG, "stopRecognition call failed with " + status);
+ Slog.e(TAG, "stopRecognition call failed with " + status);
MetricsLogger.count(mContext, "sth_stop_recognition_error", 1);
if (notify) {
try {
- callback.onError(status);
+ callback.onPauseFailed(status);
} catch (DeadObjectException e) {
forceStopAndUnloadModelLocked(modelData, e);
} catch (RemoteException e) {
- Slog.w(TAG, "RemoteException in onError", e);
+ Slog.w(TAG, "RemoteException in onPauseFailed", e);
}
}
} else {
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 04c1c0451e63..1bbea89f5acb 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -25,6 +25,7 @@ import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.GET_SERVICES;
import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE;
+import static android.hardware.soundtrigger.SoundTrigger.STATUS_DEAD_OBJECT;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR;
import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK;
import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY;
@@ -39,12 +40,13 @@ import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.ModelParams;
-import android.hardware.soundtrigger.ConversionUtil;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
@@ -64,6 +66,7 @@ import android.media.permission.SafeCloseable;
import android.media.soundtrigger.ISoundTriggerDetectionService;
import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
import android.media.soundtrigger.SoundTriggerDetectionService;
+import android.media.soundtrigger_middleware.ISoundTriggerInjection;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.os.Binder;
import android.os.Bundle;
@@ -74,8 +77,8 @@ import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.ServiceSpecificException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
@@ -86,6 +89,7 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.app.ISoundTriggerSession;
+import com.android.server.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.utils.EventLogger;
@@ -98,8 +102,8 @@ import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.UUID;
-import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
/**
* A single SystemService to manage all sound/voice-based sound models on the DSP.
@@ -296,6 +300,23 @@ public class SoundTriggerService extends SystemService {
return listUnderlyingModuleProperties(originatorIdentity);
}
}
+
+ @Override
+ public void attachInjection(@NonNull ISoundTriggerInjection injection) {
+ if (PermissionChecker.checkCallingPermissionForPreflight(mContext,
+ android.Manifest.permission.MANAGE_SOUND_TRIGGER, null)
+ != PermissionChecker.PERMISSION_GRANTED) {
+ throw new SecurityException();
+ }
+ try {
+ ISoundTriggerMiddlewareService.Stub
+ .asInterface(ServiceManager
+ .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE))
+ .attachFakeHalInjection(injection);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
class SoundTriggerSessionStub extends ISoundTriggerSession.Stub {
@@ -1368,8 +1389,7 @@ public class SoundTriggerService extends SystemService {
}));
}
- @Override
- public void onError(int status) {
+ private void onError(int status) {
if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid
@@ -1392,6 +1412,30 @@ public class SoundTriggerService extends SystemService {
}
@Override
+ public void onPreempted() {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onPreempted");
+ onError(STATUS_ERROR);
+ }
+
+ @Override
+ public void onModuleDied() {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onModuleDied");
+ onError(STATUS_DEAD_OBJECT);
+ }
+
+ @Override
+ public void onResumeFailed(int status) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onResumeFailed: " + status);
+ onError(status);
+ }
+
+ @Override
+ public void onPauseFailed(int status) {
+ if (DEBUG) Slog.v(TAG, mPuuid + ": onPauseFailed: " + status);
+ onError(status);
+ }
+
+ @Override
public void onRecognitionPaused() {
Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused");
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java
new file mode 100644
index 000000000000..01041932567c
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java
@@ -0,0 +1,205 @@
+/*
+ * 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.soundtrigger_middleware;
+
+import android.annotation.NonNull;
+import android.media.soundtrigger.ModelParameterRange;
+import android.media.soundtrigger.PhraseSoundModel;
+import android.media.soundtrigger.Properties;
+import android.media.soundtrigger.RecognitionConfig;
+import android.media.soundtrigger.SoundModel;
+import android.media.soundtrigger.Status;
+import android.os.IBinder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This wrapper prevents a models with the same UUID from being loaded concurrently. This is used to
+ * protect STHAL implementations, which don't support concurrent loads of the same model. We reject
+ * the duplicate load with {@link Status#RESOURCE_CONTENTION}.
+ */
+public class SoundTriggerDuplicateModelHandler implements ISoundTriggerHal {
+ private final @NonNull ISoundTriggerHal mDelegate;
+
+ private GlobalCallback mGlobalCallback;
+ // There are rarely more than two models loaded.
+ private final List<ModelData> mModelList = new ArrayList<>();
+
+ private static final class ModelData {
+ ModelData(int modelId, String uuid) {
+ mModelId = modelId;
+ mUuid = uuid;
+ }
+
+ int getModelId() {
+ return mModelId;
+ }
+
+ String getUuid() {
+ return mUuid;
+ }
+
+ boolean getWasContended() {
+ return mWasContended;
+ }
+
+ void setWasContended() {
+ mWasContended = true;
+ }
+
+ private int mModelId;
+ private String mUuid;
+ private boolean mWasContended = false;
+ }
+
+ public SoundTriggerDuplicateModelHandler(@NonNull ISoundTriggerHal delegate) {
+ mDelegate = delegate;
+ }
+
+ @Override
+ public void reboot() {
+ mDelegate.reboot();
+ }
+
+ @Override
+ public void detach() {
+ mDelegate.detach();
+ }
+
+ @Override
+ public Properties getProperties() {
+ return mDelegate.getProperties();
+ }
+
+ @Override
+ public void registerCallback(GlobalCallback callback) {
+ mGlobalCallback = callback;
+ mDelegate.registerCallback(mGlobalCallback);
+ }
+
+ @Override
+ public int loadSoundModel(SoundModel soundModel, ModelCallback callback) {
+ synchronized (this) {
+ checkDuplicateModelUuid(soundModel.uuid);
+ var result = mDelegate.loadSoundModel(soundModel, callback);
+ mModelList.add(new ModelData(result, soundModel.uuid));
+ return result;
+ }
+ }
+
+ @Override
+ public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) {
+ synchronized (this) {
+ checkDuplicateModelUuid(soundModel.common.uuid);
+ var result = mDelegate.loadPhraseSoundModel(soundModel, callback);
+ mModelList.add(new ModelData(result, soundModel.common.uuid));
+ return result;
+ }
+ }
+
+ @Override
+ public void unloadSoundModel(int modelHandle) {
+ mDelegate.unloadSoundModel(modelHandle);
+ for (int i = 0; i < mModelList.size(); i++) {
+ if (mModelList.get(i).getModelId() == modelHandle) {
+ var modelData = mModelList.remove(i);
+ if (modelData.getWasContended()) {
+ mGlobalCallback.onResourcesAvailable();
+ }
+ // Model ID is unique
+ return;
+ }
+ }
+ }
+
+ // Uninteresting delegation calls to follow.
+ @Override
+ public void stopRecognition(int modelHandle) {
+ mDelegate.stopRecognition(modelHandle);
+ }
+
+ @Override
+ public void startRecognition(
+ int modelHandle, int deviceHandle, int ioHandle, RecognitionConfig config) {
+ mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config);
+ }
+
+ @Override
+ public void forceRecognitionEvent(int modelHandle) {
+ mDelegate.forceRecognitionEvent(modelHandle);
+ }
+
+ @Override
+ public int getModelParameter(int modelHandle, int param) {
+ return mDelegate.getModelParameter(modelHandle, param);
+ }
+
+ @Override
+ public void setModelParameter(int modelHandle, int param, int value) {
+ mDelegate.setModelParameter(modelHandle, param, value);
+ }
+
+ @Override
+ public ModelParameterRange queryParameter(int modelHandle, int param) {
+ return mDelegate.queryParameter(modelHandle, param);
+ }
+
+ @Override
+ public void linkToDeath(IBinder.DeathRecipient recipient) {
+ mDelegate.linkToDeath(recipient);
+ }
+
+ @Override
+ public void unlinkToDeath(IBinder.DeathRecipient recipient) {
+ mDelegate.unlinkToDeath(recipient);
+ }
+
+ @Override
+ public String interfaceDescriptor() {
+ return mDelegate.interfaceDescriptor();
+ }
+
+ @Override
+ public void flushCallbacks() {
+ mDelegate.flushCallbacks();
+ }
+
+ @Override
+ public void clientAttached(IBinder binder) {
+ mDelegate.clientAttached(binder);
+ }
+
+ @Override
+ public void clientDetached(IBinder binder) {
+ mDelegate.clientDetached(binder);
+ }
+
+ /**
+ * Helper for handling duplicate model. If there is a load attempt for a model with a UUID which
+ * is already loaded: 1) Reject with {@link Status.RESOURCE_CONTENTION} 2) Mark the already
+ * loaded model as contended, as we need to dispatch a resource available callback following the
+ * original model being unloaded.
+ */
+ private void checkDuplicateModelUuid(String uuid) {
+ var model = mModelList.stream().filter(x -> x.getUuid().equals(uuid)).findFirst();
+ if (model.isPresent()) {
+ model.get().setWasContended();
+ throw new RecoverableException(Status.RESOURCE_CONTENTION);
+ }
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index d2d8f1ad7a71..6223b2e8ace4 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -170,7 +170,8 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
*/
private void attachToHal() {
mHalService = new SoundTriggerHalEnforcer(
- new SoundTriggerHalWatchdog(mHalFactory.create()));
+ new SoundTriggerHalWatchdog(
+ new SoundTriggerDuplicateModelHandler(mHalFactory.create())));
mHalService.linkToDeath(this);
mHalService.registerCallback(this);
mProperties = mHalService.getProperties();
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index aaf7a9eacce6..5846ff699f69 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -39,7 +39,7 @@ import java.util.UUID;
*
* @hide
*/
-public class DatabaseHelper extends SQLiteOpenHelper {
+public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb {
static final String TAG = "SoundModelDBHelper";
static final boolean DBG = false;
@@ -153,11 +153,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Updates the given keyphrase model, adds it, if it doesn't already exist.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
synchronized(this) {
SQLiteDatabase db = getWritableDatabase();
@@ -193,9 +189,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Deletes the sound model and associated keyphrases.
- */
+ @Override
public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
// Normalize the locale to guard against SQL injection.
bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
@@ -218,12 +212,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID.
- * Returns null if a match isn't found.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
String bcp47Locale) {
// Sanitize the locale to guard against SQL injection.
@@ -237,12 +226,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
}
}
- /**
- * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string.
- * Returns null if a match isn't found.
- *
- * TODO: We only support one keyphrase currently.
- */
+ @Override
public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
String bcp47Locale) {
// Sanitize the locale to guard against SQL injection.
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index f3cb9baedd4b..486945d9159e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -56,6 +56,7 @@ import android.service.voice.HotwordDetector;
import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
import android.service.voice.ISandboxedDetectionService;
import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
+import android.service.voice.SoundTriggerFailure;
import android.service.voice.VisualQueryDetectionService;
import android.service.voice.VisualQueryDetectionServiceFailure;
import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
@@ -153,7 +154,8 @@ final class HotwordDetectionConnection {
private int mRestartCount = 0;
@NonNull private ServiceConnection mRemoteHotwordDetectionService;
@NonNull private ServiceConnection mRemoteVisualQueryDetectionService;
- private IBinder mAudioFlinger;
+ @GuardedBy("mLock")
+ @Nullable private IBinder mAudioFlinger;
@GuardedBy("mLock")
private boolean mDebugHotwordLogging = false;
@@ -193,7 +195,7 @@ final class HotwordDetectionConnection {
new Intent(VisualQueryDetectionService.SERVICE_INTERFACE);
visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName);
- initAudioFlingerLocked();
+ initAudioFlinger();
mHotwordDetectionServiceConnectionFactory =
new ServiceConnectionFactory(hotwordDetectionServiceIntent,
@@ -226,31 +228,41 @@ final class HotwordDetectionConnection {
}
}
- private void initAudioFlingerLocked() {
+ private void initAudioFlinger() {
if (DEBUG) {
- Slog.d(TAG, "initAudioFlingerLocked");
+ Slog.d(TAG, "initAudioFlinger");
}
- mAudioFlinger = ServiceManager.waitForService("media.audio_flinger");
- if (mAudioFlinger == null) {
+ final IBinder audioFlinger = ServiceManager.waitForService("media.audio_flinger");
+ if (audioFlinger == null) {
+ setAudioFlinger(null);
throw new IllegalStateException("Service media.audio_flinger wasn't found.");
}
if (DEBUG) {
Slog.d(TAG, "Obtained audio_flinger binder.");
}
try {
- mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
+ audioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
} catch (RemoteException e) {
Slog.w(TAG, "Audio server died before we registered a DeathRecipient; "
- + "retrying init.", e);
- initAudioFlingerLocked();
+ + "retrying init.", e);
+ initAudioFlinger();
+ return;
+ }
+
+ setAudioFlinger(audioFlinger);
+ }
+
+ private void setAudioFlinger(@Nullable IBinder audioFlinger) {
+ synchronized (mLock) {
+ mAudioFlinger = audioFlinger;
}
}
private void audioServerDied() {
Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
+ // TODO: Check if this needs to be scheduled on a different thread.
+ initAudioFlinger();
synchronized (mLock) {
- // TODO: Check if this needs to be scheduled on a different thread.
- initAudioFlingerLocked();
// We restart the process instead of simply sending over the new binder, to avoid race
// conditions with audio reading in the service.
restartProcessLocked();
@@ -576,8 +588,31 @@ final class HotwordDetectionConnection {
}
@Override
- public void onError(int status) throws RemoteException {
- mExternalCallback.onError(status);
+ public void onPreempted() throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION,
+ "Unexpected startRecognition on already started ST session"));
+ }
+
+ @Override
+ public void onModuleDied() throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_MODULE_DIED,
+ "STHAL died"));
+ }
+
+ @Override
+ public void onResumeFailed(int status) throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+ "STService recognition resume failed with: " + status));
+ }
+
+ @Override
+ public void onPauseFailed(int status) throws RemoteException {
+ mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure(
+ SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED,
+ "STService recognition pause failed with: " + status));
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
new file mode 100644
index 000000000000..f10c2f6a2325
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java
@@ -0,0 +1,90 @@
+/**
+ * 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.voiceinteraction;
+
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for registering and querying the enrolled keyphrase model database for
+ * {@link VoiceInteractionManagerService}.
+ * This interface only supports one keyphrase per {@link KeyphraseSoundModel}.
+ * The non-update methods are uniquely keyed on fields of the first keyphrase
+ * {@link KeyphraseSoundModel#getKeyphrases()}.
+ * @hide
+ */
+public interface IEnrolledModelDb {
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists.
+ *
+ * @param soundModel - The sound model to register in the database.
+ * Updates the sound model if the keyphrase id, users, locale match an existing entry.
+ * Must have one and only one associated {@link Keyphrase}.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel);
+
+ /**
+ * Deletes the previously registered keyphrase sound model from the database.
+ *
+ * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale);
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+ * contingent on the userHandle existing in the user list for the model.
+ * Returns null if a match isn't found.
+ *
+ * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale);
+
+ //TODO(273286174): We only support one keyphrase currently.
+ /**
+ * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair,
+ * contingent on the userHandle existing in the user list for the model.
+ * Returns null if a match isn't found.
+ *
+ * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query.
+ * @param userHandle - The user handle making this request. Must be included in the user
+ * list of the registered sound model.
+ * @param bcp47Locale - The locale of the (first) keyphrase associated with this model.
+ * @return - {@code true} if successful, {@code false} if unsuccessful
+ */
+ KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+ String bcp47Locale);
+
+ /**
+ * Dumps contents of database for dumpsys
+ */
+ void dump(PrintWriter pw);
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
new file mode 100644
index 000000000000..9bbaf8e25f95
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java
@@ -0,0 +1,148 @@
+/**
+ * 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.voiceinteraction;
+
+import android.annotation.NonNull;
+import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
+import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+
+/**
+ * In memory model enrollment database for testing purposes.
+ * @hide
+ */
+public class TestModelEnrollmentDatabase implements IEnrolledModelDb {
+
+ // Record representing the primary key used in the real model database.
+ private static final class EnrollmentKey {
+ private final int mKeyphraseId;
+ private final List<Integer> mUserIds;
+ private final String mLocale;
+
+ EnrollmentKey(int keyphraseId,
+ @NonNull List<Integer> userIds, @NonNull String locale) {
+ mKeyphraseId = keyphraseId;
+ mUserIds = Objects.requireNonNull(userIds);
+ mLocale = Objects.requireNonNull(locale);
+ }
+
+ int keyphraseId() {
+ return mKeyphraseId;
+ }
+
+ List<Integer> userIds() {
+ return mUserIds;
+ }
+
+ String locale() {
+ return mLocale;
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner sj = new StringJoiner(", ", "{", "}");
+ sj.add("keyphraseId: " + mKeyphraseId);
+ sj.add("userIds: " + mUserIds.toString());
+ sj.add("locale: " + mLocale.toString());
+ return "EnrollmentKey: " + sj.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int res = 1;
+ res = prime * res + mKeyphraseId;
+ res = prime * res + mUserIds.hashCode();
+ res = prime * res + mLocale.hashCode();
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) return true;
+ if (other == null) return false;
+ if (!(other instanceof EnrollmentKey)) return false;
+ EnrollmentKey that = (EnrollmentKey) other;
+ if (mKeyphraseId != that.mKeyphraseId) return false;
+ if (!mUserIds.equals(that.mUserIds)) return false;
+ if (!mLocale.equals(that.mLocale)) return false;
+ return true;
+ }
+
+ }
+
+ private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>();
+
+ @Override
+ public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) {
+ final Keyphrase keyphrase = soundModel.getKeyphrases()[0];
+ mModelMap.put(new EnrollmentKey(keyphrase.getId(),
+ Arrays.stream(keyphrase.getUsers()).boxed().toList(),
+ keyphrase.getLocale().toLanguageTag()),
+ soundModel);
+ return true;
+ }
+
+ @Override
+ public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+ return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId)
+ && key.locale().equals(bcp47Locale)
+ && key.userIds().contains(userHandle));
+ }
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale) {
+ return mModelMap.entrySet()
+ .stream()
+ .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId)
+ && entry.getKey().locale().equals(bcp47Locale)
+ && entry.getKey().userIds().contains(userHandle))
+ .findFirst()
+ .map((entry) -> entry.getValue())
+ .orElse(null);
+ }
+
+ @Override
+ public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle,
+ String bcp47Locale) {
+ return mModelMap.entrySet()
+ .stream()
+ .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase)
+ && entry.getKey().locale().equals(bcp47Locale)
+ && entry.getKey().userIds().contains(userHandle)))
+ .findFirst()
+ .map((entry) -> entry.getValue())
+ .orElse(null);
+ }
+
+
+ /**
+ * Dumps contents of database for dumpsys
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("Using test enrollment database, with enrolled models:");
+ pw.println(mModelMap);
+ }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e1da2ca2a086..1d7b966bab51 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -99,11 +99,11 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.UiThread;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.LegacyPermissionManagerInternal;
-import com.android.server.soundtrigger.SoundTriggerInternal;
import com.android.server.utils.Slogf;
import com.android.server.utils.TimingsTraceAndSlog;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -125,7 +125,9 @@ public class VoiceInteractionManagerService extends SystemService {
final Context mContext;
final ContentResolver mResolver;
- final DatabaseHelper mDbHelper;
+ // Can be overridden for testing purposes
+ private IEnrolledModelDb mDbHelper;
+ private final IEnrolledModelDb mRealDbHelper;
final ActivityManagerInternal mAmInternal;
final ActivityTaskManagerInternal mAtmInternal;
final UserManagerInternal mUserManagerInternal;
@@ -143,7 +145,7 @@ public class VoiceInteractionManagerService extends SystemService {
mResolver = context.getContentResolver();
mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
- mDbHelper = new DatabaseHelper(context);
+ mDbHelper = mRealDbHelper = new DatabaseHelper(context);
mServiceStub = new VoiceInteractionManagerServiceStub();
mAmInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
@@ -1605,6 +1607,42 @@ public class VoiceInteractionManagerService extends SystemService {
}
}
+ @Override
+ @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)
+ public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) {
+ super.setModelDatabaseForTestEnabled_enforcePermission();
+ enforceCallerAllowedToEnrollVoiceModel();
+ synchronized (this) {
+ if (enabled) {
+ // Replace the dbhelper with a new test db
+ final var db = new TestModelEnrollmentDatabase();
+ try {
+ // Listen to our caller death, and make sure we revert to the real
+ // db if they left the model in a test state.
+ token.linkToDeath(() -> {
+ synchronized (this) {
+ if (mDbHelper == db) {
+ mDbHelper = mRealDbHelper;
+ mImpl.notifySoundModelsChangedLocked();
+ }
+ }
+ }, 0);
+ } catch (RemoteException e) {
+ // If the caller is already dead, nothing to do.
+ return;
+ }
+ mDbHelper = db;
+ mImpl.notifySoundModelsChangedLocked();
+ } else {
+ // Nothing to do if the db is already set to the real impl.
+ if (mDbHelper != mRealDbHelper) {
+ mDbHelper = mRealDbHelper;
+ mImpl.notifySoundModelsChangedLocked();
+ }
+ }
+ }
+ }
+
//----------------- SoundTrigger APIs --------------------------------//
@Override
public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) {
@@ -1712,28 +1750,27 @@ public class VoiceInteractionManagerService extends SystemService {
final long caller = Binder.clearCallingIdentity();
try {
KeyphraseSoundModel soundModel =
- mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale);
+ mDbHelper.getKeyphraseSoundModel(keyphraseId,
+ callingUserId, bcp47Locale);
if (soundModel == null
|| soundModel.getUuid() == null
|| soundModel.getKeyphrases() == null) {
Slog.w(TAG, "No matching sound model found in startRecognition");
return SoundTriggerInternal.STATUS_ERROR;
- } else {
- // Regardless of the status of the start recognition, we need to make sure
- // that we unload this model if needed later.
- synchronized (VoiceInteractionManagerServiceStub.this) {
- mLoadedKeyphraseIds.put(keyphraseId, this);
- if (mSessionExternalCallback == null
- || mSessionInternalCallback == null
- || callback.asBinder() != mSessionExternalCallback.asBinder()) {
- mSessionInternalCallback = createSoundTriggerCallbackLocked(
- callback);
- mSessionExternalCallback = callback;
- }
+ }
+ // Regardless of the status of the start recognition, we need to make sure
+ // that we unload this model if needed later.
+ synchronized (VoiceInteractionManagerServiceStub.this) {
+ mLoadedKeyphraseIds.put(keyphraseId, this);
+ if (mSessionExternalCallback == null
+ || mSessionInternalCallback == null
+ || callback.asBinder() != mSessionExternalCallback.asBinder()) {
+ mSessionInternalCallback = createSoundTriggerCallbackLocked(callback);
+ mSessionExternalCallback = callback;
}
- return mSession.startRecognition(keyphraseId, soundModel,
- mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
}
+ return mSession.startRecognition(keyphraseId, soundModel,
+ mSessionInternalCallback, recognitionConfig, runInBatterySaverMode);
} finally {
Binder.restoreCallingIdentity(caller);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 62be2a555bc4..0ad86c11d29a 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -71,7 +71,6 @@ import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.view.IWindowManager;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IHotwordRecognitionStatusCallback;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.app.IVoiceActionCheckCallback;
@@ -248,7 +247,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
Context.RECEIVER_EXPORTED);
}
- @GuardedBy("this")
public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) {
final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid);
final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid);
@@ -258,7 +256,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
/* direct= */ true);
}
- @GuardedBy("this")
public boolean showSessionLocked(@Nullable Bundle args, int flags,
@Nullable String attributionTag,
@Nullable IVoiceInteractionSessionShowCallback showCallback,
@@ -331,7 +328,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public boolean hideSessionLocked() {
if (mActiveSession != null) {
return mActiveSession.hideLocked();
@@ -339,7 +335,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return false;
}
- @GuardedBy("this")
public boolean deliverNewSessionLocked(IBinder token,
IVoiceInteractionSession session, IVoiceInteractor interactor) {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -350,7 +345,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return true;
}
- @GuardedBy("this")
public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType) {
try {
@@ -373,7 +367,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid,
int callingUid, IBinder token, Intent intent, String resolvedType,
@NonNull Bundle bundle) {
@@ -397,7 +390,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public void requestDirectActionsLocked(@NonNull IBinder token, int taskId,
@NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback,
@NonNull RemoteCallback callback) {
@@ -453,7 +445,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId,
@Nullable Bundle arguments, int taskId, IBinder assistToken,
@Nullable RemoteCallback cancellationCallback,
@@ -480,7 +471,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public void setKeepAwakeLocked(IBinder token, boolean keepAwake) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -493,7 +483,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public void closeSystemDialogsLocked(IBinder token) {
try {
if (mActiveSession == null || token != mActiveSession.mToken) {
@@ -506,7 +495,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public void finishLocked(IBinder token, boolean finishTask) {
if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) {
Slog.w(TAG, "finish does not match active session");
@@ -516,7 +504,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mActiveSession = null;
}
- @GuardedBy("this")
public void setDisabledShowContextLocked(int callingUid, int flags) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -526,7 +513,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mDisabledShowContext = flags;
}
- @GuardedBy("this")
public int getDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -536,7 +522,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return mDisabledShowContext;
}
- @GuardedBy("this")
public int getUserDisabledShowContextLocked(int callingUid) {
int activeUid = mInfo.getServiceInfo().applicationInfo.uid;
if (callingUid != activeUid) {
@@ -550,7 +535,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return mInfo.getSupportsLocalInteraction();
}
- @GuardedBy("this")
public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
@@ -563,7 +547,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mActiveSession.startListeningVisibleActivityChangedLocked();
}
- @GuardedBy("this")
public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
if (DEBUG) {
Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token);
@@ -576,7 +559,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mActiveSession.stopListeningVisibleActivityChangedLocked();
}
- @GuardedBy("this")
public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken);
@@ -591,7 +573,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mActiveSession.notifyActivityDestroyedLocked(activityToken);
}
- @GuardedBy("this")
public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) {
if (DEBUG) {
Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type);
@@ -606,7 +587,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mActiveSession.notifyActivityEventChangedLocked(activityToken, type);
}
- @GuardedBy("this")
public void updateStateLocked(
@Nullable PersistableBundle options,
@Nullable SharedMemory sharedMemory,
@@ -627,7 +607,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
private void verifyDetectorForHotwordDetectionLocked(
@Nullable SharedMemory sharedMemory,
IHotwordRecognitionStatusCallback callback,
@@ -685,7 +664,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
voiceInteractionServiceUid);
}
- @GuardedBy("this")
private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) {
Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked");
@@ -724,7 +702,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public void initAndVerifyDetectorLocked(
@NonNull Identity voiceInteractorIdentity,
@Nullable PersistableBundle options,
@@ -769,7 +746,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
detectorType);
}
- @GuardedBy("this")
public void destroyDetectorLocked(IBinder token) {
Slog.v(TAG, "destroyDetectorLocked");
@@ -788,7 +764,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
public void shutdownHotwordDetectionServiceLocked() {
if (DEBUG) {
Slog.d(TAG, "shutdownHotwordDetectionServiceLocked");
@@ -801,7 +776,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection = null;
}
- @GuardedBy("this")
public void setVisualQueryDetectionAttentionListenerLocked(
@Nullable IVisualQueryDetectionAttentionListener listener) {
if (mHotwordDetectionConnection == null) {
@@ -810,7 +784,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
}
- @GuardedBy("this")
public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
if (DEBUG) {
Slog.d(TAG, "startPerceivingLocked");
@@ -824,7 +797,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.startPerceivingLocked(callback);
}
- @GuardedBy("this")
public void stopPerceivingLocked() {
if (DEBUG) {
Slog.d(TAG, "stopPerceivingLocked");
@@ -838,7 +810,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.stopPerceivingLocked();
}
- @GuardedBy("this")
public void startListeningFromMicLocked(
AudioFormat audioFormat,
IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
@@ -854,7 +825,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback);
}
- @GuardedBy("this")
public void startListeningFromExternalSourceLocked(
ParcelFileDescriptor audioStream,
AudioFormat audioFormat,
@@ -879,7 +849,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
options, token, callback);
}
- @GuardedBy("this")
public void stopListeningFromMicLocked() {
if (DEBUG) {
Slog.d(TAG, "stopListeningFromMicLocked");
@@ -893,7 +862,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.stopListeningFromMicLocked();
}
- @GuardedBy("this")
public void triggerHardwareRecognitionEventForTestLocked(
SoundTrigger.KeyphraseRecognitionEvent event,
IHotwordRecognitionStatusCallback callback) {
@@ -908,7 +876,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback);
}
- @GuardedBy("this")
public IRecognitionStatusCallback createSoundTriggerCallbackLocked(
IHotwordRecognitionStatusCallback callback) {
if (DEBUG) {
@@ -933,12 +900,11 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
return null;
}
- @GuardedBy("this")
boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) {
return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0
&& (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0;
}
- @GuardedBy("this")
+
boolean verifyProcessSharingLocked() {
// only check this if both VQDS and HDS are declared in the app
ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser);
@@ -960,7 +926,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.forceRestart();
}
- @GuardedBy("this")
void setDebugHotwordLoggingLocked(boolean logging) {
if (mHotwordDetectionConnection == null) {
Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active");
@@ -969,7 +934,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging);
}
- @GuardedBy("this")
void resetHotwordDetectionConnectionLocked() {
if (DEBUG) {
Slog.d(TAG, "resetHotwordDetectionConnectionLocked");
@@ -984,7 +948,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
mHotwordDetectionConnection = null;
}
- @GuardedBy("this")
public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!mValid) {
pw.print(" NOT VALID: ");
@@ -1023,7 +986,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
void startLocked() {
Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE);
intent.setComponent(mComponent);
@@ -1048,7 +1010,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
void shutdownLocked() {
// If there is an active session, cancel it to allow it to clean up its window and other
// state.
@@ -1076,7 +1037,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne
}
}
- @GuardedBy("this")
void notifySoundModelsChangedLocked() {
if (mService == null) {
Slog.w(TAG, "Not bound to voice interaction service " + mComponent);
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
index f3ef834168b5..52ff90f38113 100644
--- a/telecomm/java/android/telecom/CallAttributes.java
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -59,7 +59,10 @@ public final class CallAttributes implements Parcelable {
public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
/** @hide **/
- public static final String CALLER_PID = "CallerPid";
+ public static final String CALLER_PID_KEY = "CallerPid";
+
+ /** @hide **/
+ public static final String CALLER_UID_KEY = "CallerUid";
private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle,
@NonNull CharSequence displayName,
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index e39af5aa3327..9dd2a61671ec 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -2682,71 +2682,76 @@ public class TelecomManager {
}
/**
- * Reports a new call with the specified {@link CallAttributes} to the telecom service. This
- * method can be used to report both incoming and outgoing calls. By reporting the call, the
- * system is aware of the call and can provide updates on services (ex. Another device wants to
- * disconnect the call) or events (ex. a new Bluetooth route became available).
- *
+ * Add a call to the Android system service Telecom. This allows the system to start tracking an
+ * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready
+ * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor,
+ * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}.
* <p>
- * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
- * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
- * will asynchronously provide an update on whether the new call was added successfully via
- * an {@link OutcomeReceiver}. Additionally, callbacks will run on the executor thread that was
- * passed in.
- *
* <p>
- * Note: Only packages that register with
+ * <p>
+ * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a
+ * valid call and are posting a {@link android.app.Notification.CallStyle} notification.
+ * When your application is given foreground execution priority, your app is treated as a
+ * foreground service. Foreground execution priority will prevent the
+ * {@link android.app.ActivityManager} from killing your application when it is placed the
+ * background. Foreground execution priority is removed from your app when all of your app's
+ * calls terminate or your app no longer posts a valid notification.
+ * <p>
+ * <p>
+ * <p>
+ * <b>Note</b>: Only packages that register with
* {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
* can utilize this API. {@link PhoneAccount}s that set the capabilities
* {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
* {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
* are not supported and will cause an exception to be thrown.
- *
* <p>
- * Usage example:
+ * <p>
+ * <p>
+ * <b>Usage example:</b>
* <pre>
- *
- * // An app should first define their own construct of a Call that overrides all the
- * // {@link CallControlCallback}s and {@link CallEventCallback}s
- * private class MyVoipCall {
- * public String callId = "";
- *
- * public CallControlCallEventCallback handshakes = new
- * CallControlCallEventCallback() {
- * // override/ implement all {@link CallControlCallback}s
+ * // Its up to your app on how you want to wrap the objects. One such implementation can be:
+ * class MyVoipCall {
+ * ...
+ * public CallControlCallEventCallback handshakes = new CallControlCallback() {
+ * ...
* }
- * public CallEventCallback events = new
- * CallEventCallback() {
- * // override/ implement all {@link CallEventCallback}s
- * }
- * public MyVoipCall(String id){
- * callId = id;
- * }
*
- * PhoneAccountHandle handle = new PhoneAccountHandle(
- * new ComponentName("com.example.voip.app",
- * "com.example.voip.app.NewCallActivity"), "123");
+ * public CallEventCallback events = new CallEventCallback() {
+ * ...
+ * }
*
- * CallAttributes callAttributes = new CallAttributes.Builder(handle,
- * CallAttributes.DIRECTION_OUTGOING,
- * "John Smith", Uri.fromParts("tel", "123", null))
- * .build();
+ * public MyVoipCall(String id){
+ * ...
+ * }
+ * }
*
* MyVoipCall myFirstOutgoingCall = new MyVoipCall("1");
*
- * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+ * telecomManager.addCall(callAttributes,
+ * Runnable::run,
+ * new OutcomeReceiver() {
* public void onResult(CallControl callControl) {
- * // The call has been added successfully
+ * // The call has been added successfully. For demonstration
+ * // purposes, the call is disconnected immediately ...
+ * callControl.disconnect(
+ * new DisconnectCause(DisconnectCause.LOCAL) )
* }
- * }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events);
+ * },
+ * myFirstOutgoingCall.handshakes,
+ * myFirstOutgoingCall.events);
* </pre>
*
- * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. )
- * @param executor thread to run background CallEventCallback updates on
- * @param pendingControl OutcomeReceiver that receives the result of addCall transaction
- * @param handshakes object that overrides {@link CallControlCallback}s
- * @param events object that overrides {@link CallEventCallback}s
+ * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.)
+ * @param executor execution context to run {@link CallControlCallback} updates on
+ * @param pendingControl Receives the result of addCall transaction. Upon success, a
+ * CallControl object is provided which can be used to do things like
+ * disconnect the call that was added.
+ * @param handshakes callback that receives <b>actionable</b> updates that originate from
+ * Telecom.
+ * @param events callback that receives <b>non</b>-actionable updates that originate
+ * from Telecom.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
@SuppressLint("SamShouldBeLast")
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 78c61964edfd..559faf9b20de 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -3142,10 +3142,10 @@ public class SubscriptionManager {
*
* Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
* true). To check for permissions for non-embedded subscription as well,
+ * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param info The subscription to check.
* @return whether the app is authorized to manage this subscription per its metadata.
- *
* @see android.telephony.TelephonyManager#hasCarrierPrivileges
*/
public boolean canManageSubscription(SubscriptionInfo info) {
@@ -3159,12 +3159,12 @@ public class SubscriptionManager {
*
* Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns
* true). To check for permissions for non-embedded subscription as well,
+ * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}.
*
* @param info The subscription to check.
* @param packageName Package name of the app to check.
*
* @return whether the app is authorized to manage this subscription per its access rules.
- *
* @see android.telephony.TelephonyManager#hasCarrierPrivileges
* @hide
*/
diff --git a/keystore/java/android/security/GenerateRkpKeyException.java b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
index a2d65e4e7119..c838f6fbb8ac 100644
--- a/keystore/java/android/security/GenerateRkpKeyException.java
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright 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
+ * http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,18 +14,6 @@
* limitations under the License.
*/
-package android.security;
+package android.telephony.satellite;
-/**
- * Thrown on problems in attempting to attest to a key using a remotely provisioned key.
- *
- * @hide
- */
-public class GenerateRkpKeyException extends Exception {
-
- /**
- * Constructs a new {@code GenerateRkpKeyException}.
- */
- public GenerateRkpKeyException() {
- }
-}
+parcelable AntennaDirection;
diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java
new file mode 100644
index 000000000000..02b0bc7364a2
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaDirection.java
@@ -0,0 +1,138 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna
+ * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless
+ * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen
+ * when the device is held in default portrait mode with screen facing the user:
+ *
+ * Z axis is vertical along the plane of the device with positive Z pointing up and negative z
+ * pointing towards bottom of the device
+ * Y axis is horizontal along the plane of the device with positive Y pointing towards right of
+ * the phone screen and negative Y pointing towards left
+ * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for
+ * positive X and pointing away from back of the phone for negative X.
+ * @hide
+ */
+public final class AntennaDirection implements Parcelable {
+ /** Antenna x axis direction. */
+ private float mX;
+
+ /** Antenna y axis direction. */
+ private float mY;
+
+ /** Antenna z axis direction. */
+ private float mZ;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AntennaDirection(float x, float y, float z) {
+ mX = x;
+ mY = y;
+ mZ = z;
+ }
+
+ private AntennaDirection(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeFloat(mX);
+ out.writeFloat(mY);
+ out.writeFloat(mZ);
+ }
+
+ @NonNull
+ public static final Creator<AntennaDirection> CREATOR =
+ new Creator<>() {
+ @Override
+ public AntennaDirection createFromParcel(Parcel in) {
+ return new AntennaDirection(in);
+ }
+
+ @Override
+ public AntennaDirection[] newArray(int size) {
+ return new AntennaDirection[size];
+ }
+ };
+
+ @Override
+ @NonNull public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("X:");
+ sb.append(mX);
+ sb.append(",");
+
+ sb.append("Y:");
+ sb.append(mY);
+ sb.append(",");
+
+ sb.append("Z:");
+ sb.append(mZ);
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AntennaDirection that = (AntennaDirection) o;
+ return mX == that.mX
+ && mY == that.mY
+ && mZ == that.mZ;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mX, mY, mZ);
+ }
+
+ public float getX() {
+ return mX;
+ }
+
+ public float getY() {
+ return mY;
+ }
+
+ public float getZ() {
+ return mZ;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mX = in.readFloat();
+ mY = in.readFloat();
+ mZ = in.readFloat();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.aidl b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
new file mode 100644
index 000000000000..00525624329c
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony.satellite;
+
+parcelable AntennaPosition;
diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java
new file mode 100644
index 000000000000..eefc8b00f8e8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/AntennaPosition.java
@@ -0,0 +1,117 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @hide
+ */
+public final class AntennaPosition implements Parcelable {
+ /** Antenna direction used for satellite communication. */
+ @NonNull AntennaDirection mAntennaDirection;
+
+ /** Enum corresponding to device hold position to be used by the end user. */
+ @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition;
+
+ /**
+ * @hide
+ */
+ @UnsupportedAppUsage
+ public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) {
+ mAntennaDirection = antennaDirection;
+ mSuggestedHoldPosition = suggestedHoldPosition;
+ }
+
+ private AntennaPosition(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeParcelable(mAntennaDirection, flags);
+ out.writeInt(mSuggestedHoldPosition);
+ }
+
+ @NonNull
+ public static final Creator<AntennaPosition> CREATOR =
+ new Creator<>() {
+ @Override
+ public AntennaPosition createFromParcel(Parcel in) {
+ return new AntennaPosition(in);
+ }
+
+ @Override
+ public AntennaPosition[] newArray(int size) {
+ return new AntennaPosition[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AntennaPosition that = (AntennaPosition) o;
+ return Objects.equals(mAntennaDirection, that.mAntennaDirection)
+ && mSuggestedHoldPosition == that.mSuggestedHoldPosition;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAntennaDirection, mSuggestedHoldPosition);
+ }
+
+ @Override
+ @NonNull public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("antennaDirection:");
+ sb.append(mAntennaDirection);
+ sb.append(",");
+
+ sb.append("suggestedHoldPosition:");
+ sb.append(mSuggestedHoldPosition);
+ return sb.toString();
+ }
+
+ @NonNull
+ public AntennaDirection getAntennaDirection() {
+ return mAntennaDirection;
+ }
+
+ @SatelliteManager.DeviceHoldPosition
+ public int getSuggestedHoldPosition() {
+ return mSuggestedHoldPosition;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(),
+ AntennaDirection.class);
+ mSuggestedHoldPosition = in.readInt();
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java
index e8a8576daf58..a559b32f0021 100644
--- a/telephony/java/android/telephony/satellite/PointingInfo.java
+++ b/telephony/java/android/telephony/satellite/PointingInfo.java
@@ -17,7 +17,6 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,7 +33,7 @@ public final class PointingInfo implements Parcelable {
/**
* @hide
*/
- @UnsupportedAppUsage
+
public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) {
mSatelliteAzimuthDegrees = satelliteAzimuthDegrees;
mSatelliteElevationDegrees = satelliteElevationDegrees;
diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
index 87c8db317195..6856cc0c5df2 100644
--- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
+++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java
@@ -17,11 +17,13 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -44,15 +46,24 @@ public final class SatelliteCapabilities implements Parcelable {
private int mMaxBytesPerOutgoingDatagram;
/**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+ */
+ @NonNull
+ private Map<Integer, AntennaPosition> mAntennaPositionMap;
+
+ /**
* @hide
*/
- @UnsupportedAppUsage
public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies,
- boolean isPointingRequired, int maxBytesPerOutgoingDatagram) {
+ boolean isPointingRequired, int maxBytesPerOutgoingDatagram,
+ @NonNull Map<Integer, AntennaPosition> antennaPositionMap) {
mSupportedRadioTechnologies = supportedRadioTechnologies == null
? new HashSet<>() : supportedRadioTechnologies;
mIsPointingRequired = isPointingRequired;
mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram;
+ mAntennaPositionMap = antennaPositionMap;
}
private SatelliteCapabilities(Parcel in) {
@@ -77,6 +88,17 @@ public final class SatelliteCapabilities implements Parcelable {
out.writeBoolean(mIsPointingRequired);
out.writeInt(mMaxBytesPerOutgoingDatagram);
+
+ if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) {
+ int size = mAntennaPositionMap.size();
+ out.writeInt(size);
+ for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) {
+ out.writeInt(entry.getKey());
+ out.writeParcelable(entry.getValue(), flags);
+ }
+ } else {
+ out.writeInt(0);
+ }
}
@NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() {
@@ -109,11 +131,32 @@ public final class SatelliteCapabilities implements Parcelable {
sb.append(mIsPointingRequired);
sb.append(",");
- sb.append("maxBytesPerOutgoingDatagram");
+ sb.append("maxBytesPerOutgoingDatagram:");
sb.append(mMaxBytesPerOutgoingDatagram);
+ sb.append(",");
+
+ sb.append("antennaPositionMap:");
+ sb.append(mAntennaPositionMap);
return sb.toString();
}
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SatelliteCapabilities that = (SatelliteCapabilities) o;
+ return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies)
+ && mIsPointingRequired == that.mIsPointingRequired
+ && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram
+ && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired,
+ mMaxBytesPerOutgoingDatagram, mAntennaPositionMap);
+ }
+
/**
* @return The list of technologies supported by the satellite modem.
*/
@@ -141,6 +184,16 @@ public final class SatelliteCapabilities implements Parcelable {
return mMaxBytesPerOutgoingDatagram;
}
+ /**
+ * Antenna Position received from satellite modem which gives information about antenna
+ * direction to be used with satellite communication and suggested device hold positions.
+ * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition
+ */
+ @NonNull
+ public Map<Integer, AntennaPosition> getAntennaPositionMap() {
+ return mAntennaPositionMap;
+ }
+
private void readFromParcel(Parcel in) {
mSupportedRadioTechnologies = new HashSet<>();
int numSupportedRadioTechnologies = in.readInt();
@@ -152,5 +205,14 @@ public final class SatelliteCapabilities implements Parcelable {
mIsPointingRequired = in.readBoolean();
mMaxBytesPerOutgoingDatagram = in.readInt();
+
+ mAntennaPositionMap = new HashMap<>();
+ int antennaPositionMapSize = in.readInt();
+ for (int i = 0; i < antennaPositionMapSize; i++) {
+ int key = in.readInt();
+ AntennaPosition antennaPosition = in.readParcelable(
+ AntennaPosition.class.getClassLoader(), AntennaPosition.class);
+ mAntennaPositionMap.put(key, antennaPosition);
+ }
}
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
index 44f56f3d315c..d3cb8a07e4ba 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java
@@ -17,7 +17,6 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -33,7 +32,6 @@ public final class SatelliteDatagram implements Parcelable {
/**
* @hide
*/
- @UnsupportedAppUsage
public SatelliteDatagram(@NonNull byte[] data) {
mData = data;
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index d0409bf5df49..b2dec71ecb32 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -17,7 +17,6 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
import java.util.function.Consumer;
@@ -37,7 +36,6 @@ public interface SatelliteDatagramCallback {
* that they received the datagram. If the callback is not received within
* five minutes, Telephony will resend the datagram.
*/
- @UnsupportedAppUsage
void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
int pendingCount, @NonNull Consumer<Void> callback);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 20f9bc8bef05..c5830b8249c8 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -23,7 +23,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
-import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
@@ -37,8 +36,8 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyFrameworkInitializer;
import com.android.internal.telephony.IIntegerConsumer;
-import com.android.internal.telephony.IVoidConsumer;
import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.IVoidConsumer;
import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
@@ -83,7 +82,7 @@ public class SatelliteManager {
* @param context The context the SatelliteManager belongs to.
* @hide
*/
- @UnsupportedAppUsage
+
public SatelliteManager(@Nullable Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
}
@@ -129,7 +128,7 @@ public class SatelliteManager {
* {@link #requestIsSatelliteEnabled(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_SATELLITE_ENABLED = "satellite_enabled";
/**
@@ -137,7 +136,7 @@ public class SatelliteManager {
* {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled";
/**
@@ -145,7 +144,7 @@ public class SatelliteManager {
* {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_SATELLITE_SUPPORTED = "satellite_supported";
/**
@@ -153,7 +152,7 @@ public class SatelliteManager {
* {@link #requestSatelliteCapabilities(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_SATELLITE_CAPABILITIES = "satellite_capabilities";
/**
@@ -161,7 +160,7 @@ public class SatelliteManager {
* {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_SATELLITE_PROVISIONED = "satellite_provisioned";
/**
@@ -169,7 +168,7 @@ public class SatelliteManager {
* {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_SATELLITE_COMMUNICATION_ALLOWED =
"satellite_communication_allowed";
@@ -178,7 +177,7 @@ public class SatelliteManager {
* {@link #requestTimeForNextSatelliteVisibility(Executor, OutcomeReceiver)}.
* @hide
*/
- @UnsupportedAppUsage
+
public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
/**
@@ -334,6 +333,46 @@ public class SatelliteManager {
@Retention(RetentionPolicy.SOURCE)
public @interface NTRadioTechnology {}
+ /** Suggested device hold position is unknown. */
+ public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0;
+ /** User is suggested to hold the device in portrait mode. */
+ public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1;
+ /** User is suggested to hold the device in landscape mode with left hand. */
+ public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2;
+ /** User is suggested to hold the device in landscape mode with right hand. */
+ public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3;
+
+ /** @hide */
+ @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = {
+ DEVICE_HOLD_POSITION_UNKNOWN,
+ DEVICE_HOLD_POSITION_PORTRAIT,
+ DEVICE_HOLD_POSITION_LANDSCAPE_LEFT,
+ DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DeviceHoldPosition {}
+
+ /** Display mode is unknown. */
+ public static final int DISPLAY_MODE_UNKNOWN = 0;
+ /** Display mode of the device used for satellite communication for non-foldable phones. */
+ public static final int DISPLAY_MODE_FIXED = 1;
+ /** Display mode of the device used for satellite communication for foldabale phones when the
+ * device is opened. */
+ public static final int DISPLAY_MODE_OPENED = 2;
+ /** Display mode of the device used for satellite communication for foldabable phones when the
+ * device is closed. */
+ public static final int DISPLAY_MODE_CLOSED = 3;
+
+ /** @hide */
+ @IntDef(prefix = {"ANTENNA_POSITION_"}, value = {
+ DISPLAY_MODE_UNKNOWN,
+ DISPLAY_MODE_FIXED,
+ DISPLAY_MODE_OPENED,
+ DISPLAY_MODE_CLOSED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DisplayMode {}
+
/**
* Request to enable or disable the satellite modem and demo mode. If the satellite modem is
* enabled, this may also disable the cellular modem, and if the satellite modem is disabled,
@@ -349,7 +388,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode,
@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
@@ -392,7 +431,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -447,7 +486,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -500,7 +539,7 @@ public class SatelliteManager {
*
* @throws IllegalStateException if the Telephony process is not currently available.
*/
- @UnsupportedAppUsage
+
public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -554,7 +593,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -747,7 +786,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener,
@NonNull SatelliteTransmissionUpdateCallback callback) {
@@ -817,7 +856,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void stopSatelliteTransmissionUpdates(
@NonNull SatelliteTransmissionUpdateCallback callback,
@NonNull @CallbackExecutor Executor executor,
@@ -873,7 +912,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor executor,
@@ -923,7 +962,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void deprovisionSatelliteService(@NonNull String token,
@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
@@ -963,7 +1002,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
@SatelliteError public int registerForSatelliteProvisionStateChanged(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteProvisionStateCallback callback) {
@@ -1006,7 +1045,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void unregisterForSatelliteProvisionStateChanged(
@NonNull SatelliteProvisionStateCallback callback) {
Objects.requireNonNull(callback);
@@ -1045,7 +1084,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
Objects.requireNonNull(executor);
@@ -1097,7 +1136,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
@SatelliteError public int registerForSatelliteModemStateChanged(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteStateCallback callback) {
@@ -1137,7 +1176,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) {
Objects.requireNonNull(callback);
ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback);
@@ -1172,7 +1211,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
@SatelliteError public int registerForSatelliteDatagram(
@NonNull @CallbackExecutor Executor executor,
@NonNull SatelliteDatagramCallback callback) {
@@ -1228,7 +1267,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) {
Objects.requireNonNull(callback);
ISatelliteDatagramCallback internalCallback =
@@ -1266,7 +1305,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor,
@SatelliteError @NonNull Consumer<Integer> resultListener) {
Objects.requireNonNull(executor);
@@ -1319,7 +1358,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void sendSatelliteDatagram(@DatagramType int datagramType,
@NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
@NonNull @CallbackExecutor Executor executor,
@@ -1365,7 +1404,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestIsSatelliteCommunicationAllowedForCurrentLocation(
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
@@ -1423,7 +1462,7 @@ public class SatelliteManager {
* @throws IllegalStateException if the Telephony process is not currently available.
*/
@RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
- @UnsupportedAppUsage
+
public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<Duration, SatelliteException> callback) {
Objects.requireNonNull(executor);
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index 20875af94f5a..a62eb8b8a5fb 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -16,8 +16,6 @@
package android.telephony.satellite;
-import android.compat.annotation.UnsupportedAppUsage;
-
/**
* A callback class for monitoring satellite provision state change events.
*
@@ -30,6 +28,5 @@ public interface SatelliteProvisionStateCallback {
* @param provisioned The new provision state. {@code true} means satellite is provisioned
* {@code false} means satellite is not provisioned.
*/
- @UnsupportedAppUsage
void onSatelliteProvisionStateChanged(boolean provisioned);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index 3312e3a90281..d9ecaa3467e3 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -16,8 +16,6 @@
package android.telephony.satellite;
-import android.compat.annotation.UnsupportedAppUsage;
-
/**
* A callback class for monitoring satellite modem state change events.
*
@@ -28,6 +26,5 @@ public interface SatelliteStateCallback {
* Called when satellite modem state changes.
* @param state The new satellite modem state.
*/
- @UnsupportedAppUsage
void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index 17fff4480679..d4fe57a0be2e 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -17,7 +17,6 @@
package android.telephony.satellite;
import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
/**
* A callback class for monitoring satellite position update and datagram transfer state change
@@ -31,7 +30,6 @@ public interface SatelliteTransmissionUpdateCallback {
*
* @param pointingInfo The pointing info containing the satellite location.
*/
- @UnsupportedAppUsage
void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo);
/**
@@ -41,7 +39,6 @@ public interface SatelliteTransmissionUpdateCallback {
* @param sendPendingCount The number of datagrams that are currently being sent.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- @UnsupportedAppUsage
void onSendDatagramStateChanged(
@SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount,
@SatelliteManager.SatelliteError int errorCode);
@@ -53,7 +50,6 @@ public interface SatelliteTransmissionUpdateCallback {
* @param receivePendingCount The number of datagrams that are currently pending to be received.
* @param errorCode If datagram transfer failed, the reason for failure.
*/
- @UnsupportedAppUsage
void onReceiveDatagramStateChanged(
@SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount,
@SatelliteManager.SatelliteError int errorCode);
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
index cd69da18c5b0..eaf96abeb80a 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl
@@ -17,7 +17,7 @@
package android.telephony.satellite.stub;
import android.telephony.satellite.stub.NTRadioTechnology;
-
+import android.telephony.satellite.AntennaPosition;
/**
* {@hide}
*/
@@ -36,4 +36,14 @@ parcelable SatelliteCapabilities {
* The maximum number of bytes per datagram that can be sent over satellite.
*/
int maxBytesPerOutgoingDatagram;
+
+ /**
+ * Keys which are used to fill mAntennaPositionMap.
+ */
+ int[] antennaPositionKeys;
+
+ /**
+ * Antenna Position for different display modes received from satellite modem.
+ */
+ AntennaPosition[] antennaPositionValues;
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index cbdf38ae95d4..ee9d6c1a2448 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2989,4 +2989,14 @@ interface ITelephony {
* {@code false} otherwise.
*/
boolean setSatelliteServicePackageName(in String servicePackageName);
+
+ /**
+ * This API can be used by only CTS to update the timeout duration in milliseconds that
+ * satellite should stay at listening mode to wait for the next incoming page before disabling
+ * listening mode.
+ *
+ * @param timeoutMillis The timeout duration in millisecond.
+ * @return {@code true} if the timeout duration is set successfully, {@code false} otherwise.
+ */
+ boolean setSatelliteListeningTimeoutDuration(in long timeoutMillis);
}
diff --git a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
index cfebf3462b57..4299e0d616fb 100644
--- a/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
+++ b/tests/DynamicCodeLoggerIntegrationTests/src/com/android/server/pm/dex/DynamicCodeLoggerIntegrationTests.java
@@ -87,7 +87,7 @@ public final class DynamicCodeLoggerIntegrationTests {
// avoid flakiness we run these tests multiple times, allowing progressively longer between
// code loading and checking the logs on each try.)
private static final int AUDIT_LOG_RETRIES = 10;
- private static final int RETRY_DELAY_MS = 2_000;
+ private static final int RETRY_DELAY_MS = 500;
private static Context sContext;
private static int sMyUid;
@@ -253,7 +253,7 @@ public final class DynamicCodeLoggerIntegrationTests {
"/DynamicCodeLoggerNativeExecutable", privateCopyFile);
EventLog.writeEvent(EventLog.getTagCode("auditd"),
- "type=1400 avc: granted { execute_no_trans } "
+ "type=1400 avc: granted { execute_no_trans } "
+ "path=\"" + privateCopyFile + "\" "
+ "scontext=u:r:untrusted_app: "
+ "tcontext=u:object_r:app_data_file: "
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index f2ffc19f2a4e..7272abba897d 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -29,6 +29,7 @@
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
<option name="teardown-command" value="settings delete system show_touches" />
<option name="teardown-command" value="settings delete system pointer_location" />
+ <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true"/>
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 314b9e4a853b..f389e13a6884 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -260,7 +260,7 @@ fun FlickerTest.snapshotStartingWindowLayerCoversExactlyOnApp(component: ICompon
snapshotLayers
.mapNotNull { snapshotLayer -> snapshotLayer.layer.visibleRegion }
.toTypedArray()
- val snapshotRegion = RegionSubject(visibleAreas, this, timestamp)
+ val snapshotRegion = RegionSubject(visibleAreas, timestamp)
val appVisibleRegion = it.visibleRegion(component)
if (snapshotRegion.region.isNotEmpty) {
snapshotRegion.coversExactly(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
index 6066d2e74209..34fa921f5777 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeOnDismissPopupDialogTest.kt
@@ -88,7 +88,7 @@ open class CloseImeOnDismissPopupDialogTest(flicker: FlickerTest) : BaseTest(fli
imeSnapshotLayers
.mapNotNull { imeSnapshotLayer -> imeSnapshotLayer.layer.visibleRegion }
.toTypedArray()
- val imeVisibleRegion = RegionSubject(visibleAreas, this, timestamp)
+ val imeVisibleRegion = RegionSubject(visibleAreas, timestamp)
val appVisibleRegion = it.visibleRegion(imeTestApp)
if (imeVisibleRegion.region.isNotEmpty) {
imeVisibleRegion.coversAtMost(appVisibleRegion.region)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 2fff00133eb8..d5208e060883 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -73,7 +73,16 @@ open class CloseImeToHomeOnFinishActivityTest(flicker: FlickerTest) : BaseTest(f
@Test
@IwTest(focusArea = "ime")
override fun cujCompleted() {
- super.cujCompleted()
+ runAndIgnoreAssumptionViolation { entireScreenCovered() }
+ runAndIgnoreAssumptionViolation { statusBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { statusBarLayerPositionAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { statusBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { visibleWindowsShownMoreThanOneConsecutiveEntry() }
+ runAndIgnoreAssumptionViolation { taskBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { taskBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { navBarLayerIsVisibleAtStartAndEnd() }
+ runAndIgnoreAssumptionViolation { navBarWindowIsAlwaysVisible() }
+ runAndIgnoreAssumptionViolation { navBarWindowIsVisibleAtStartAndEnd() }
imeLayerBecomesInvisible()
imeWindowBecomesInvisible()
}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java
new file mode 100644
index 000000000000..6b924f335ef7
--- /dev/null
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java
@@ -0,0 +1,116 @@
+/*
+ * 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.google.android.test.handwritingime;
+
+import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_EDITOR_BOUNDS;
+import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_NONE;
+import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.inputmethod.CursorAnchorInfo;
+import android.view.inputmethod.EditorBoundsInfo;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.graphics.ColorUtils;
+
+import java.util.List;
+
+public class BoundsInfoDrawHelper {
+ private static final Paint sPaint = new Paint();
+ private static final int EDITOR_BOUNDS_COLOR =
+ ColorUtils.setAlphaComponent(Color.DKGRAY, 128);
+ private static final int HANDWRITING_BOUNDS_COLOR =
+ ColorUtils.setAlphaComponent(Color.BLUE, 128);
+ private static final int VISIBLE_LINE_BOUNDS_COLOR =
+ ColorUtils.setAlphaComponent(Color.MAGENTA, 128);
+
+ public static void draw(Canvas canvas, View inkView, int boundsInfoMode,
+ CursorAnchorInfo cursorAnchorInfo) {
+ if (boundsInfoMode == BOUNDS_INFO_NONE || cursorAnchorInfo == null) {
+ return;
+ }
+
+ // The matrix in CursorAnchorInfo transforms the editor coordinates to on-screen
+ // coordinates. We then transform the matrix from the on-screen coordinates to the
+ // inkView's coordinates. So the result matrix transforms the editor coordinates
+ // to the inkView coordinates.
+ final Matrix matrix = cursorAnchorInfo.getMatrix();
+ inkView.transformMatrixToLocal(matrix);
+
+ if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) {
+ drawEditorBoundsInfo(canvas, matrix, cursorAnchorInfo.getEditorBoundsInfo());
+ }
+
+ if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) {
+ drawVisibleLineBounds(canvas, matrix, cursorAnchorInfo.getVisibleLineBounds());
+ }
+ }
+
+ private static void setPaintForEditorBoundsInfo() {
+ sPaint.reset();
+ sPaint.setStyle(Paint.Style.STROKE);
+ sPaint.setStrokeWidth(5f);
+ }
+
+ private static void drawEditorBoundsInfo(Canvas canvas, Matrix matrix,
+ @Nullable EditorBoundsInfo editorBoundsInfo) {
+ if (editorBoundsInfo == null) {
+ return;
+ }
+ final RectF editorBounds = editorBoundsInfo.getEditorBounds();
+ setPaintForEditorBoundsInfo();
+ if (editorBounds != null) {
+ final RectF localEditorBounds = new RectF(editorBounds);
+ matrix.mapRect(localEditorBounds);
+ sPaint.setColor(EDITOR_BOUNDS_COLOR);
+ canvas.drawRect(localEditorBounds, sPaint);
+ }
+
+ final RectF handwritingBounds = editorBoundsInfo.getHandwritingBounds();
+ if (handwritingBounds != null) {
+ final RectF localHandwritingBounds = new RectF(handwritingBounds);
+ matrix.mapRect(localHandwritingBounds);
+ sPaint.setColor(HANDWRITING_BOUNDS_COLOR);
+ canvas.drawRect(localHandwritingBounds, sPaint);
+ }
+ }
+
+ private static void setPaintForVisibleLineBounds() {
+ sPaint.reset();
+ sPaint.setStyle(Paint.Style.STROKE);
+ sPaint.setStrokeWidth(2f);
+ sPaint.setColor(VISIBLE_LINE_BOUNDS_COLOR);
+ }
+
+ private static void drawVisibleLineBounds(Canvas canvas, Matrix matrix,
+ List<RectF> visibleLineBounds) {
+ if (visibleLineBounds.isEmpty()) {
+ return;
+ }
+ setPaintForVisibleLineBounds();
+ for (RectF lineBound : visibleLineBounds) {
+ matrix.mapRect(lineBound);
+ canvas.drawRect(lineBound, sPaint);
+ }
+ }
+}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
index 2fd236895467..8380dcf4b4a4 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -25,7 +25,9 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
+import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.DeleteGesture;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
@@ -34,6 +36,7 @@ import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.Spinner;
@@ -43,9 +46,6 @@ import java.util.Random;
import java.util.function.IntConsumer;
public class HandwritingIme extends InputMethodService {
-
- public static final int HEIGHT_DP = 100;
-
private static final int OP_NONE = 0;
private static final int OP_SELECT = 1;
private static final int OP_DELETE = 2;
@@ -62,6 +62,12 @@ public class HandwritingIme extends InputMethodService {
private Spinner mRichGestureGranularitySpinner;
private PointF mRichGestureStartPoint;
+ static final int BOUNDS_INFO_NONE = 0;
+ static final int BOUNDS_INFO_VISIBLE_LINE_BOUNDS = 1;
+ static final int BOUNDS_INFO_EDITOR_BOUNDS = 2;
+ private int mBoundsInfoMode = BOUNDS_INFO_NONE;
+ private LinearLayout mBoundsInfoCheckBoxes;
+
private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value);
interface HandwritingFinisher {
@@ -201,12 +207,7 @@ public class HandwritingIme extends InputMethodService {
public View onCreateInputView() {
Log.d(TAG, "onCreateInputView");
final ViewGroup view = new FrameLayout(this);
- final View inner = new View(this);
- final float density = getResources().getDisplayMetrics().density;
- final int height = (int) (HEIGHT_DP * density);
view.setPadding(0, 0, 0, 0);
- view.addView(inner, new FrameLayout.LayoutParams(
- FrameLayout.LayoutParams.MATCH_PARENT, height));
LinearLayout layout = new LinearLayout(this);
layout.setLayoutParams(new LinearLayout.LayoutParams(
@@ -214,9 +215,9 @@ public class HandwritingIme extends InputMethodService {
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(getRichGestureActionsSpinner());
layout.addView(getRichGestureGranularitySpinner());
-
+ layout.addView(getBoundsInfoCheckBoxes());
+ layout.setBackgroundColor(getColor(R.color.holo_green_light));
view.addView(layout);
- inner.setBackgroundColor(getColor(R.color.holo_green_light));
return view;
}
@@ -228,7 +229,7 @@ public class HandwritingIme extends InputMethodService {
mRichGestureModeSpinner = new Spinner(this);
mRichGestureModeSpinner.setPadding(100, 0, 100, 0);
mRichGestureModeSpinner.setTooltipText("Handwriting IME mode");
- String[] items = new String[] {
+ String[] items = new String[]{
"Handwriting IME - Rich gesture disabled",
"Rich gesture SELECT",
"Rich gesture DELETE",
@@ -259,6 +260,69 @@ public class HandwritingIme extends InputMethodService {
return mRichGestureModeSpinner;
}
+ private void updateCursorAnchorInfo(int boundsInfoMode) {
+ final InputConnection ic = getCurrentInputConnection();
+ if (ic == null) return;
+
+ if (boundsInfoMode == BOUNDS_INFO_NONE) {
+ ic.requestCursorUpdates(0);
+ return;
+ }
+
+ final int cursorUpdateMode = InputConnection.CURSOR_UPDATE_MONITOR;
+ int cursorUpdateFilter = 0;
+ if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) {
+ cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS;
+ }
+
+ if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) {
+ cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS;
+ }
+ ic.requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter);
+ }
+
+ private void updateBoundsInfoMode() {
+ if (mInk != null) {
+ mInk.setBoundsInfoMode(mBoundsInfoMode);
+ }
+ updateCursorAnchorInfo(mBoundsInfoMode);
+ }
+
+ private View getBoundsInfoCheckBoxes() {
+ if (mBoundsInfoCheckBoxes != null) {
+ return mBoundsInfoCheckBoxes;
+ }
+ mBoundsInfoCheckBoxes = new LinearLayout(this);
+ mBoundsInfoCheckBoxes.setPadding(100, 0, 100, 0);
+ mBoundsInfoCheckBoxes.setOrientation(LinearLayout.HORIZONTAL);
+
+ final CheckBox editorBoundsInfoCheckBox = new CheckBox(this);
+ editorBoundsInfoCheckBox.setText("EditorBoundsInfo");
+ editorBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ mBoundsInfoMode |= BOUNDS_INFO_EDITOR_BOUNDS;
+ } else {
+ mBoundsInfoMode &= ~BOUNDS_INFO_EDITOR_BOUNDS;
+ }
+ updateBoundsInfoMode();
+ });
+
+ final CheckBox visibleLineBoundsInfoCheckBox = new CheckBox(this);
+ visibleLineBoundsInfoCheckBox.setText("VisibleLineBounds");
+ visibleLineBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ mBoundsInfoMode |= BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
+ } else {
+ mBoundsInfoMode &= ~BOUNDS_INFO_VISIBLE_LINE_BOUNDS;
+ }
+ updateBoundsInfoMode();
+ });
+
+ mBoundsInfoCheckBoxes.addView(editorBoundsInfoCheckBox);
+ mBoundsInfoCheckBoxes.addView(visibleLineBoundsInfoCheckBox);
+ return mBoundsInfoCheckBoxes;
+ }
+
private View getRichGestureGranularitySpinner() {
if (mRichGestureGranularitySpinner != null) {
return mRichGestureGranularitySpinner;
@@ -294,6 +358,7 @@ public class HandwritingIme extends InputMethodService {
Log.d(TAG, "onPrepareStylusHandwriting ");
if (mInk == null) {
mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer());
+ mInk.setBoundsInfoMode(mBoundsInfoMode);
}
}
@@ -323,4 +388,16 @@ public class HandwritingIme extends InputMethodService {
private boolean areRichGesturesEnabled() {
return mRichGestureMode != OP_NONE;
}
+
+ @Override
+ public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
+ if (mInk != null) {
+ mInk.setCursorAnchorInfo(cursorAnchorInfo);
+ }
+ }
+
+ @Override
+ public void onStartInput(EditorInfo attribute, boolean restarting) {
+ updateCursorAnchorInfo(mBoundsInfoMode);
+ }
}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
index e94c79ecca00..86b324cf08d9 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -26,6 +26,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowMetrics;
+import android.view.inputmethod.CursorAnchorInfo;
class InkView extends View {
private static final long FINISH_TIMEOUT = 1500;
@@ -37,6 +38,9 @@ class InkView extends View {
private static final float STYLUS_MOVE_TOLERANCE = 1;
private Runnable mFinishRunnable;
+ private CursorAnchorInfo mCursorAnchorInfo;
+ private int mBoundsInfoMode;
+
InkView(Context context, HandwritingIme.HandwritingFinisher hwController,
HandwritingIme.StylusConsumer consumer) {
super(context);
@@ -66,6 +70,7 @@ class InkView extends View {
canvas.drawPath(mPath, mPaint);
canvas.drawARGB(20, 255, 50, 50);
+ BoundsInfoDrawHelper.draw(canvas, this, mBoundsInfoMode, mCursorAnchorInfo);
}
private void stylusStart(float x, float y) {
@@ -156,4 +161,15 @@ class InkView extends View {
return mFinishRunnable;
}
+ void setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) {
+ mCursorAnchorInfo = cursorAnchorInfo;
+ invalidate();
+ }
+
+ void setBoundsInfoMode(int boundsInfoMode) {
+ if (boundsInfoMode != mBoundsInfoMode) {
+ invalidate();
+ }
+ mBoundsInfoMode = boundsInfoMode;
+ }
}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index a4c48fd5f342..4fa6fbe1d4e1 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -25,7 +25,7 @@ android_test {
"services.core.unboosted",
"testables",
"truth-prebuilt",
- "ub-uiautomator",
+ "androidx.test.uiautomator_uiautomator",
],
test_suites: ["device-tests"],
}
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 0246426c1d33..d185ee6ae116 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -27,14 +27,15 @@ import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLI
import android.os.SystemClock
import android.provider.Settings
import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
-import android.support.test.uiautomator.By
-import android.support.test.uiautomator.UiDevice
-import android.support.test.uiautomator.UiObject2
-import android.support.test.uiautomator.Until
import android.testing.PollingCheck
import android.view.InputDevice
import android.view.MotionEvent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index f8d885ae3faf..d7fa124623ce 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -417,9 +417,9 @@ public class PackageWatchdogTest {
int failureReason, int mitigationCount) {
if (versionedPackage.getVersionCode() == VERSION_CODE) {
// Only rollback for specific versionCode
- return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
}
- return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
}
};
@@ -442,13 +442,13 @@ public class PackageWatchdogTest {
public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
PackageWatchdog watchdog = createWatchdog();
TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_NONE);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
- PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
- PackageHealthObserverImpact.USER_IMPACT_LOW);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
// Start observing for all impact observers
watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
@@ -499,9 +499,9 @@ public class PackageWatchdogTest {
public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
PackageWatchdog watchdog = createWatchdog();
TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_LOW);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing for observerFirst and observerSecond with failure handling
watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
@@ -517,7 +517,7 @@ public class PackageWatchdogTest {
assertThat(observerSecond.mMitigatedPackages).isEmpty();
// After observerFirst handles failure, next action it has is high impact
- observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
+ observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
observerFirst.mMitigatedPackages.clear();
observerSecond.mMitigatedPackages.clear();
@@ -531,7 +531,7 @@ public class PackageWatchdogTest {
assertThat(observerFirst.mMitigatedPackages).isEmpty();
// After observerSecond handles failure, it has no further actions
- observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+ observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
observerFirst.mMitigatedPackages.clear();
observerSecond.mMitigatedPackages.clear();
@@ -545,7 +545,7 @@ public class PackageWatchdogTest {
assertThat(observerSecond.mMitigatedPackages).isEmpty();
// After observerFirst handles failure, it too has no further actions
- observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
+ observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
observerFirst.mMitigatedPackages.clear();
observerSecond.mMitigatedPackages.clear();
@@ -566,9 +566,9 @@ public class PackageWatchdogTest {
public void testPackageFailureNotifyOneSameImpact() throws Exception {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
// Start observing for observer1 and observer2 with failure handling
watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
@@ -592,11 +592,11 @@ public class PackageWatchdogTest {
TestController controller = new TestController();
PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
TestObserver observer3 = new TestObserver(OBSERVER_NAME_3,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
// Start observing with explicit health checks for APP_A and APP_B respectively
@@ -645,7 +645,7 @@ public class PackageWatchdogTest {
TestController controller = new TestController();
PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing with explicit health checks for APP_A and APP_B
controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
@@ -711,7 +711,7 @@ public class PackageWatchdogTest {
TestController controller = new TestController();
PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing with explicit health checks for APP_A and
// package observation duration == LONG_DURATION
@@ -742,7 +742,7 @@ public class PackageWatchdogTest {
TestController controller = new TestController();
PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
// Start observing with explicit health checks for APP_A and
// package observation duration == SHORT_DURATION / 2
@@ -818,7 +818,7 @@ public class PackageWatchdogTest {
// Start observing with failure handling
TestObserver observer = new TestObserver(OBSERVER_NAME_1,
- PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
// Notify of NetworkStack failure
@@ -1073,9 +1073,9 @@ public class PackageWatchdogTest {
public void testBootLoopMitigationDoneForLowestUserImpact() {
PackageWatchdog watchdog = createWatchdog();
TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
- bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+ bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
- bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30);
watchdog.registerHealthObserver(bootObserver1);
watchdog.registerHealthObserver(bootObserver2);
for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
@@ -1446,7 +1446,7 @@ public class PackageWatchdogTest {
TestObserver(String name) {
mName = name;
- mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
+ mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
}
TestObserver(String name, int impact) {
diff --git a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
index d2653d0de0d4..f20dd424c617 100644
--- a/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
+++ b/tests/SilkFX/res/drawable-nodpi/dark_gradient.xml
@@ -18,6 +18,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="#000000"
- android:endColor="#181818"
+ android:endColor="#222222"
android:angle="0"/>
</shape> \ No newline at end of file
diff --git a/tests/WindowAnimationJank/Android.bp b/tests/WindowAnimationJank/Android.bp
index ed86aa5f90ea..8542f885d645 100644
--- a/tests/WindowAnimationJank/Android.bp
+++ b/tests/WindowAnimationJank/Android.bp
@@ -25,7 +25,7 @@ android_test {
name: "WindowAnimationJank",
srcs: ["src/**/*.java"],
static_libs: [
- "ub-uiautomator",
+ "androidx.test.uiautomator_uiautomator",
"androidx.test.janktesthelper",
"junit",
],
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
index 25314644ca7e..48a359c4d0c6 100644
--- a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
+++ b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java
@@ -18,11 +18,12 @@ import android.app.UiAutomation;
import android.content.ComponentName;
import android.content.Intent;
import android.os.SystemClock;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.BySelector;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
/**
* Set of helpers to manipulate test activities.
diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
index a8ace162c4d0..cb7c5112cba7 100644
--- a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
+++ b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java
@@ -16,9 +16,8 @@
package android.windowanimationjank;
-import android.support.test.uiautomator.UiDevice;
-
import androidx.test.jank.JankTestBase;
+import androidx.test.uiautomator.UiDevice;
/**
* This adds additional system level jank monitor and its result is merged with primary monitor
diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java
index c6f18fd453c5..b850cb8e6dfd 100644
--- a/tests/testables/src/android/testing/TestableSettingsProvider.java
+++ b/tests/testables/src/android/testing/TestableSettingsProvider.java
@@ -72,7 +72,11 @@ public class TestableSettingsProvider extends MockContentProvider {
public Bundle call(String method, String arg, Bundle extras) {
// Methods are "GET_system", "GET_global", "PUT_secure", etc.
- final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId());
+ int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.USER_CURRENT);
+ if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) {
+ userId = UserHandle.myUserId();
+ }
+
final String[] commands = method.split("_", 2);
final String op = commands[0];
final String table = commands[1];
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04fdb765..1731f6be4bf2 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
- <application android:debuggable="true">
+ <application android:debuggable="true" android:testOnly="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 000000000000..6d2979423efa
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<configuration description="Runs Testable Tests.">
+ <option name="test-tag" value="TestablesTests" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="TestablesTests.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.testables"/>
+ </test>
+</configuration>
diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h
index a146466402f6..e2c161482857 100644
--- a/tools/aapt/SdkConstants.h
+++ b/tools/aapt/SdkConstants.h
@@ -49,6 +49,7 @@ enum {
SDK_S = 31,
SDK_S_V2 = 32,
SDK_TIRAMISU = 33,
+ SDK_UPSIDE_DOWN_CAKE = 34,
SDK_CUR_DEVELOPMENT = 10000,
};
diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h
index 40bcef787815..e47704ea2725 100644
--- a/tools/aapt2/SdkConstants.h
+++ b/tools/aapt2/SdkConstants.h
@@ -59,6 +59,7 @@ enum : ApiVersion {
SDK_S = 31,
SDK_S_V2 = 32,
SDK_TIRAMISU = 33,
+ SDK_UPSIDE_DOWN_CAKE = 34,
SDK_CUR_DEVELOPMENT = 10000,
};
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 25fbabce71ae..166fbddc2f56 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java
@@ -163,11 +163,11 @@ public final class NetworkProviderInfo implements Parcelable {
/**
* Sets the displayed connection strength of the remote device to the internet.
*
- * @param connectionStrength Connection strength in range 0 to 3.
+ * @param connectionStrength Connection strength in range 0 to 4.
* @return Returns the Builder object.
*/
@NonNull
- public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) {
+ public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) {
mConnectionStrength = connectionStrength;
return this;
}
@@ -205,8 +205,8 @@ public final class NetworkProviderInfo implements Parcelable {
if (batteryPercentage < 0 || batteryPercentage > 100) {
throw new IllegalArgumentException("BatteryPercentage must be in range 0-100");
}
- if (connectionStrength < 0 || connectionStrength > 3) {
- throw new IllegalArgumentException("ConnectionStrength must be in range 0-3");
+ if (connectionStrength < 0 || connectionStrength > 4) {
+ throw new IllegalArgumentException("ConnectionStrength must be in range 0-4");
}
}
@@ -265,9 +265,9 @@ public final class NetworkProviderInfo implements Parcelable {
/**
* Gets the displayed connection strength of the remote device to the internet.
*
- * @return Returns the connection strength in range 0 to 3.
+ * @return Returns the connection strength in range 0 to 4.
*/
- @IntRange(from = 0, to = 3)
+ @IntRange(from = 0, to = 4)
public int getConnectionStrength() {
return mConnectionStrength;
}
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 e5ef62b16dfd..feef0497c152 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -35,6 +35,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
@@ -173,10 +174,15 @@ public class SharedConnectivityManager {
R.string.config_sharedConnectivityServicePackage);
String serviceIntentAction = resources.getString(
R.string.config_sharedConnectivityServiceIntentAction);
+ if (TextUtils.isEmpty(servicePackageName) || TextUtils.isEmpty(serviceIntentAction)) {
+ Log.e(TAG, "To support shared connectivity service on this device, the"
+ + " service's package name and intent action strings must not be empty");
+ return null;
+ }
return new SharedConnectivityManager(context, servicePackageName, serviceIntentAction);
} catch (Resources.NotFoundException e) {
Log.e(TAG, "To support shared connectivity service on this device, the service's"
- + " package name and intent action string must be defined");
+ + " package name and intent action strings must be defined");
}
return null;
}
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index b585bd5cfd7b..a03a6c2fc673 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -105,6 +105,13 @@ public class SharedConnectivityManagerTest {
}
@Test
+ public void resourceStringsAreEmpty_createShouldReturnNull() {
+ when(mResources.getString(anyInt())).thenReturn("");
+
+ assertThat(SharedConnectivityManager.create(mContext)).isNull();
+ }
+
+ @Test
public void bindingToServiceOnFirstCallbackRegistration() {
SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
manager.registerCallback(mExecutor, mClientCallback);