summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Xin Li <delphij@google.com> 2021-11-18 17:00:37 +0000
committer Xin Li <delphij@google.com> 2021-11-18 17:00:37 +0000
commit10d9972786b9f564365b1f01af23b9df2ec00117 (patch)
tree9ca125a8245bdbd608c4e999b9ba287dcdd3bfcb
parentd353c81c5c50494d66c8b4bce3294b3b8c86a38b (diff)
parent0f473ac71a8dcefa15f377fe44c4f6003b6a518b (diff)
Merge sc-qpr1-dev-plus-aosp-without-vendor@7810918
Bug: 205056467 Merged-In: I13199bc39e9445929195f3d15579cbffe94e92b0 Change-Id: I35fa3c6c2abf679c51033f1395a08d511ed8739f
-rw-r--r--apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java11
-rw-r--r--cmds/bootanimation/Android.bp2
-rw-r--r--cmds/bootanimation/BootAnimation.cpp441
-rw-r--r--cmds/bootanimation/BootAnimation.h32
-rw-r--r--cmds/idmap2/OWNERS1
-rw-r--r--core/java/android/app/ActivityManager.java5
-rw-r--r--core/java/android/app/ActivityManagerInternal.java3
-rw-r--r--core/java/android/app/ActivityThread.java3
-rw-r--r--core/java/android/app/ContextImpl.java5
-rw-r--r--core/java/android/app/GameManager.java17
-rw-r--r--core/java/android/app/IGameManagerService.aidl1
-rw-r--r--core/java/android/app/Notification.java196
-rw-r--r--core/java/android/app/NotificationChannel.java14
-rw-r--r--core/java/android/app/WallpaperManager.java58
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java21
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl1
-rw-r--r--core/java/android/app/backup/BackupManager.java26
-rw-r--r--core/java/android/app/search/Query.java2
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java12
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/Intent.java16
-rw-r--r--core/java/android/content/integrity/OWNERS2
-rw-r--r--core/java/android/content/om/OWNERS2
-rw-r--r--core/java/android/content/pm/OWNERS4
-rw-r--r--core/java/android/content/pm/dex/OWNERS2
-rw-r--r--core/java/android/content/pm/parsing/OWNERS1
-rw-r--r--core/java/android/content/pm/permission/OWNERS2
-rw-r--r--core/java/android/content/pm/split/OWNERS2
-rw-r--r--core/java/android/content/pm/verify/domain/OWNERS1
-rw-r--r--core/java/android/content/res/OWNERS2
-rw-r--r--core/java/android/hardware/OWNERS2
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java34
-rw-r--r--core/java/android/hardware/biometrics/IAuthService.aidl11
-rw-r--r--core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl6
-rw-r--r--core/java/android/hardware/biometrics/IBiometricService.aidl11
-rw-r--r--core/java/android/hardware/camera2/CameraExtensionCharacteristics.java2
-rw-r--r--core/java/android/hardware/camera2/CaptureFailure.java9
-rw-r--r--core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java76
-rw-r--r--core/java/android/hardware/camera2/impl/CameraDeviceImpl.java77
-rw-r--r--core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java5
-rw-r--r--core/java/android/hardware/display/BrightnessInfo.java21
-rw-r--r--core/java/android/hardware/display/DisplayManager.java17
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java29
-rw-r--r--core/java/android/hardware/face/FaceManager.java67
-rw-r--r--core/java/android/hardware/face/IFaceService.aidl26
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java113
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintStateListener.java7
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl30
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl1
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java6
-rw-r--r--core/java/android/os/BatteryUsageStats.java10
-rw-r--r--core/java/android/os/BinderProxy.java2
-rw-r--r--core/java/android/os/GraphicsEnvironment.java190
-rw-r--r--core/java/android/os/incremental/OWNERS1
-rw-r--r--core/java/android/os/storage/StorageManagerInternal.java16
-rw-r--r--core/java/android/provider/Settings.java38
-rw-r--r--core/java/android/service/notification/StatusBarNotification.java2
-rw-r--r--core/java/android/service/translation/ITranslationService.aidl2
-rw-r--r--core/java/android/service/translation/TranslationService.java46
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java45
-rw-r--r--core/java/android/speech/RecognitionService.java11
-rw-r--r--core/java/android/text/TextLine.java2
-rw-r--r--core/java/android/text/method/TranslationTransformationMethod.java7
-rw-r--r--core/java/android/view/IWindowManager.aidl5
-rw-r--r--core/java/android/view/ScrollCaptureResponse.java49
-rw-r--r--core/java/android/view/Surface.java4
-rw-r--r--core/java/android/view/SurfaceView.java2
-rw-r--r--core/java/android/view/View.java3
-rw-r--r--core/java/android/view/ViewRootImpl.java17
-rw-r--r--core/java/android/view/animation/TranslateAnimation.java58
-rw-r--r--core/java/android/view/translation/TranslationCapability.java11
-rw-r--r--core/java/android/view/translation/TranslationRequest.java16
-rw-r--r--core/java/android/view/translation/TranslationResponseValue.java12
-rw-r--r--core/java/android/view/translation/UiTranslationController.java32
-rw-r--r--core/java/android/view/translation/UiTranslationManager.java48
-rw-r--r--core/java/android/view/translation/ViewTranslationCallback.java26
-rw-r--r--core/java/android/widget/RemoteViews.java10
-rw-r--r--core/java/android/widget/TextViewTranslationCallback.java15
-rw-r--r--core/java/android/window/ITaskOrganizer.aidl5
-rw-r--r--core/java/android/window/TaskOrganizer.java9
-rw-r--r--core/java/android/window/WindowContext.java3
-rw-r--r--core/java/android/window/WindowContextController.java20
-rw-r--r--core/java/android/window/WindowTokenClient.java15
-rw-r--r--core/java/com/android/internal/app/SuspendedAppActivity.java23
-rw-r--r--core/java/com/android/internal/app/chooser/SelectableTargetInfo.java2
-rw-r--r--core/java/com/android/internal/infra/OWNERS0
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java19
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java2
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java102
-rw-r--r--core/java/com/android/internal/os/BatteryUsageStatsProvider.java13
-rw-r--r--core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java11
-rw-r--r--core/java/com/android/internal/os/WifiPowerCalculator.java3
-rw-r--r--core/java/com/android/internal/policy/PhoneFallbackEventHandler.java8
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBarService.aidl3
-rw-r--r--core/java/com/android/internal/util/ContrastColorUtil.java19
-rw-r--r--core/java/com/android/internal/util/OWNERS1
-rw-r--r--core/java/com/android/internal/util/function/pooled/OWNERS0
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java9
-rw-r--r--core/java/com/android/internal/widget/NotificationActionListLayout.java53
-rw-r--r--core/java/com/android/server/OWNERS2
-rw-r--r--core/jni/OWNERS2
-rw-r--r--core/jni/android_os_GraphicsEnvironment.cpp11
-rw-r--r--core/jni/android_view_Surface.cpp6
-rw-r--r--core/proto/OWNERS3
-rw-r--r--core/proto/android/providers/settings/global.proto5
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/res/OWNERS1
-rw-r--r--core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml47
-rw-r--r--core/res/res/anim-ldrtl/task_close_enter.xml50
-rw-r--r--core/res/res/anim-ldrtl/task_close_exit.xml41
-rw-r--r--core/res/res/anim-ldrtl/task_open_enter.xml52
-rw-r--r--core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml52
-rw-r--r--core/res/res/anim-ldrtl/task_open_exit.xml41
-rw-r--r--core/res/res/anim/cross_profile_apps_thumbnail_enter.xml47
-rw-r--r--core/res/res/anim/task_close_enter.xml50
-rw-r--r--core/res/res/anim/task_close_exit.xml41
-rw-r--r--core/res/res/anim/task_open_enter.xml54
-rw-r--r--core/res/res/anim/task_open_enter_cross_profile_apps.xml50
-rw-r--r--core/res/res/anim/task_open_exit.xml41
-rw-r--r--core/res/res/color/overview_background.xml19
-rw-r--r--core/res/res/color/overview_background_dark.xml19
-rw-r--r--core/res/res/drawable/btn_notification_emphasized.xml4
-rw-r--r--core/res/res/values-night/colors.xml2
-rw-r--r--core/res/res/values/config.xml28
-rw-r--r--core/res/res/values/dimens.xml6
-rw-r--r--core/res/res/values/strings.xml4
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java191
-rw-r--r--core/tests/coretests/src/android/graphics/FontListParserTest.java48
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java41
-rw-r--r--core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java18
-rw-r--r--core/tests/coretests/src/android/window/WindowContextControllerTest.java20
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java92
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java8
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java36
-rw-r--r--core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java3
-rw-r--r--core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java99
-rw-r--r--data/etc/OWNERS2
-rw-r--r--data/etc/car/com.google.android.car.kitchensink.xml1
-rw-r--r--graphics/java/android/graphics/FontListParser.java39
-rw-r--r--graphics/java/android/graphics/ImageFormat.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java52
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java82
-rw-r--r--libs/androidfw/OWNERS1
-rw-r--r--libs/hwui/Properties.cpp5
-rw-r--r--libs/hwui/Properties.h1
-rw-r--r--libs/hwui/RenderNode.cpp9
-rw-r--r--libs/hwui/RenderNode.h1
-rw-r--r--libs/hwui/WebViewFunctorManager.cpp3
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp35
-rw-r--r--libs/hwui/renderthread/EglManager.cpp3
-rw-r--r--location/java/android/location/GpsNavigationMessage.java8
-rw-r--r--media/java/android/media/IMediaRouterClient.aidl1
-rw-r--r--media/java/android/media/MediaRouter.java35
-rw-r--r--native/android/OWNERS2
-rw-r--r--packages/CtsShim/OWNERS1
-rw-r--r--packages/PackageInstaller/AndroidManifest.xml8
-rw-r--r--packages/PackageInstaller/TEST_MAPPING5
-rw-r--r--packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java3
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml8
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values/styles.xml5
-rw-r--r--packages/SettingsLib/BarChartPreference/res/values/styles.xml6
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp1
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml3
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml (renamed from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml)4
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml (renamed from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml)0
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml (renamed from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml)4
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml (renamed from packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml)4
-rw-r--r--packages/SettingsLib/FooterPreference/res/values/styles.xml3
-rw-r--r--packages/SettingsLib/LayoutPreference/res/values/styles.xml5
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml (renamed from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml)0
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml (renamed from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml)0
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml (renamed from packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml)0
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml2
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml4
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml37
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml25
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml22
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/values/styles.xml9
-rw-r--r--packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml18
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml7
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml25
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml37
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml8
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml2
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values/dimens.xml1
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values/themes.xml2
-rw-r--r--packages/SettingsLib/TopIntroPreference/res/values/styles.xml3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java55
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java34
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java29
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java9
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/Shell/OWNERS1
-rw-r--r--packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml26
-rw-r--r--packages/SystemUI/res-keyguard/values/donottranslate.xml3
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml4
-rw-r--r--packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml25
-rw-r--r--packages/SystemUI/res/color/settingslib_state_off.xml5
-rw-r--r--packages/SystemUI/res/color/settingslib_state_on.xml5
-rw-r--r--packages/SystemUI/res/color/settingslib_track_off.xml5
-rw-r--r--packages/SystemUI/res/color/settingslib_track_on.xml5
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml483
-rw-r--r--packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml486
-rw-r--r--packages/SystemUI/res/drawable/ic_arrow_forward.xml26
-rw-r--r--packages/SystemUI/res/drawable/ic_friction_lock_closed.xml26
-rw-r--r--packages/SystemUI/res/drawable/ic_media_home_devices.xml16
-rw-r--r--packages/SystemUI/res/drawable/ic_settings_24dp.xml29
-rw-r--r--packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml34
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_background.xml23
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_footer_background.xml30
-rw-r--r--packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml22
-rw-r--r--packages/SystemUI/res/drawable/logout_button_background.xml3
-rw-r--r--packages/SystemUI/res/drawable/notif_footer_btn_background.xml31
-rw-r--r--packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml23
-rw-r--r--packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml33
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_off.xml29
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_on.xml29
-rw-r--r--packages/SystemUI/res/drawable/settingslib_thumb_selector.xml22
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_off_background.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_on_background.xml26
-rw-r--r--packages/SystemUI/res/drawable/settingslib_track_selector.xml22
-rw-r--r--packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml44
-rw-r--r--packages/SystemUI/res/layout/global_actions_toast.xml44
-rw-r--r--packages/SystemUI/res/layout/global_screenshot_static.xml1
-rw-r--r--packages/SystemUI/res/layout/internet_connectivity_dialog.xml348
-rw-r--r--packages/SystemUI/res/layout/internet_list_item.xml74
-rw-r--r--packages/SystemUI/res/layout/keyguard_qs_user_switch.xml32
-rw-r--r--packages/SystemUI/res/layout/keyguard_status_bar.xml2
-rw-r--r--packages/SystemUI/res/layout/media_smartspace_recommendations.xml29
-rw-r--r--packages/SystemUI/res/layout/media_view.xml12
-rw-r--r--packages/SystemUI/res/layout/privacy_dialog.xml8
-rw-r--r--packages/SystemUI/res/layout/privacy_dialog_item.xml2
-rw-r--r--packages/SystemUI/res/layout/quick_qs_status_icons.xml37
-rw-r--r--packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml13
-rw-r--r--packages/SystemUI/res/layout/screen_record_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml17
-rw-r--r--packages/SystemUI/res/layout/status_bar_notification_footer.xml8
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml3
-rw-r--r--packages/SystemUI/res/layout/udfps_enroll_view.xml17
-rw-r--r--packages/SystemUI/res/layout/udfps_view.xml2
-rw-r--r--packages/SystemUI/res/values-h800dp/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-night/styles.xml12
-rw-r--r--packages/SystemUI/res/values-sw600dp/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/attrs.xml4
-rw-r--r--packages/SystemUI/res/values/colors.xml20
-rw-r--r--packages/SystemUI/res/values/dimens.xml67
-rw-r--r--packages/SystemUI/res/values/strings.xml43
-rw-r--r--packages/SystemUI/res/values/styles.xml108
-rw-r--r--packages/SystemUI/res/xml/media_collapsed.xml17
-rw-r--r--packages/SystemUI/res/xml/media_expanded.xml16
-rw-r--r--packages/SystemUI/res/xml/media_recommendation_collapsed.xml19
-rw-r--r--packages/SystemUI/res/xml/media_recommendation_expanded.xml19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java32
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java51
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconView.java52
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java145
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt181
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt239
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java394
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java200
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java280
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java156
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt285
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java70
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java204
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java597
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java1072
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java232
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt221
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java8
-rw-r--r--packages/SystemUI/tests/AndroidManifest.xml7
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java205
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java104
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java102
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java144
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java221
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt113
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt49
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java226
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java209
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java167
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java646
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java334
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java65
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java217
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java)8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt176
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java88
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java5
-rw-r--r--packages/services/CameraExtensionsProxy/AndroidManifest.xml1
-rw-r--r--proto/src/metrics_constants/OWNERS1
-rw-r--r--services/OWNERS2
-rw-r--r--services/core/java/com/android/server/BootReceiver.java2
-rw-r--r--services/core/java/com/android/server/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/RescueParty.java58
-rw-r--r--services/core/java/com/android/server/SensorPrivacyService.java38
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java17
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java7
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java28
-rw-r--r--services/core/java/com/android/server/am/BatteryExternalStatsWorker.java2
-rw-r--r--services/core/java/com/android/server/am/CacheOomRanker.java236
-rw-r--r--services/core/java/com/android/server/am/CachedAppOptimizer.java76
-rw-r--r--services/core/java/com/android/server/am/CoreSettingsObserver.java2
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java58
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java64
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java6
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java84
-rw-r--r--services/core/java/com/android/server/apphibernation/AppHibernationService.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java6
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java94
-rw-r--r--services/core/java/com/android/server/audio/FadeOutManager.java20
-rw-r--r--services/core/java/com/android/server/audio/MediaFocusControl.java57
-rw-r--r--services/core/java/com/android/server/audio/PlaybackActivityMonitor.java6
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java14
-rw-r--r--services/core/java/com/android/server/biometrics/AuthSession.java35
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricSensor.java9
-rw-r--r--services/core/java/com/android/server/biometrics/BiometricService.java59
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java51
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java48
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/EnrollClient.java15
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java39
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java17
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/RemovalClient.java16
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java40
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java44
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java37
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java9
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java129
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java52
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java19
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java22
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java70
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java71
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java13
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java2
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java6
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java355
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java98
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerState.java6
-rw-r--r--services/core/java/com/android/server/display/HighBrightnessModeController.java11
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java18
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java109
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java3
-rw-r--r--services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java2
-rw-r--r--services/core/java/com/android/server/location/provider/LocationProviderManager.java72
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java18
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java245
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java43
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLogger.java21
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java40
-rw-r--r--services/core/java/com/android/server/notification/NotificationShellCmd.java101
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java3
-rw-r--r--services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java23
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java694
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java14
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java15
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java5
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java13
-rw-r--r--services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java25
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java11
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/OWNERS1
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java29
-rw-r--r--services/core/java/com/android/server/policy/SingleKeyGestureDetector.java39
-rw-r--r--services/core/java/com/android/server/power/Notifier.java29
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java18
-rw-r--r--services/core/java/com/android/server/power/ScreenUndimDetector.java297
-rw-r--r--services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java8
-rw-r--r--services/core/java/com/android/server/stats/OWNERS1
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java5
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java69
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java32
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java15
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java6
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java21
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java4
-rw-r--r--services/core/java/com/android/server/wm/DragState.java3
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java18
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java4
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java8
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimator.java50
-rw-r--r--services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java71
-rw-r--r--services/core/java/com/android/server/wm/Task.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java66
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowAnimationSpec.java59
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java55
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java5
-rw-r--r--services/core/jni/com_android_server_am_CachedAppOptimizer.cpp25
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp21
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java219
-rw-r--r--services/incremental/OWNERS1
-rw-r--r--services/java/com/android/server/SystemServer.java13
-rw-r--r--services/tests/PackageManager/packageinstaller/Android.bp39
-rw-r--r--services/tests/PackageManager/packageinstaller/AndroidManifest.xml29
-rw-r--r--services/tests/PackageManager/packageinstaller/AndroidTest.xml29
-rw-r--r--services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt50
-rw-r--r--services/tests/mockingservicestests/AndroidManifest.xml1
-rw-r--r--services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml7
-rw-r--r--services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml7
-rw-r--r--services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml7
-rw-r--r--services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml7
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java1
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java64
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java500
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java107
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java15
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java25
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt28
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java305
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java112
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java57
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java73
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java136
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java52
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java104
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java10
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java416
-rw-r--r--services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java101
-rw-r--r--services/tests/servicestests/src/com/android/server/power/NotifierTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java29
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java16
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java158
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java15
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java44
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java7
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java3
-rw-r--r--services/usb/java/com/android/server/usb/UsbUserPermissionManager.java8
-rw-r--r--services/uwb/java/com/android/server/uwb/UwbInjector.java22
-rw-r--r--services/uwb/java/com/android/server/uwb/UwbServiceImpl.java46
-rw-r--r--services/voiceinteraction/Android.bp6
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java12
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java66
-rw-r--r--telecomm/java/android/telecom/Connection.java15
-rw-r--r--telecomm/java/android/telecom/Phone.java29
-rw-r--r--telecomm/java/android/telecom/RemoteConnectionService.java26
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java14
-rw-r--r--tests/Codegen/OWNERS1
-rw-r--r--tools/aapt2/OWNERS1
-rw-r--r--tools/codegen/OWNERS1
648 files changed, 21660 insertions, 5335 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 45588e831cb9..84d05c8b4144 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -2918,6 +2918,13 @@ public class DeviceIdleController extends SystemService
reasonCode, reason).sendToTarget();
}
reportTempWhitelistChangedLocked(uid, true);
+ } else {
+ // The uid is already temp allowlisted, only need to update AMS for temp allowlist
+ // duration.
+ if (mLocalActivityManager != null) {
+ mLocalActivityManager.updateDeviceIdleTempAllowlist(null, uid, true,
+ duration, tempAllowListType, reasonCode, reason, callingUid);
+ }
}
}
if (informWhitelistChanged) {
@@ -3941,6 +3948,10 @@ public class DeviceIdleController extends SystemService
if (idleUntil) {
mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
+ } else if (mState == STATE_LOCATING) {
+ // Use setExact so we don't keep the GPS active for too long.
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);
} else {
if (mConstants.USE_WINDOW_ALARMS) {
mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
diff --git a/cmds/bootanimation/Android.bp b/cmds/bootanimation/Android.bp
index b2b66c27f795..3534624a58a2 100644
--- a/cmds/bootanimation/Android.bp
+++ b/cmds/bootanimation/Android.bp
@@ -71,7 +71,7 @@ cc_library_shared {
"libui",
"libjnigraphics",
"libEGL",
- "libGLESv1_CM",
+ "libGLESv2",
"libgui",
],
}
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 3109c5c1e075..6c8cab7783b6 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -52,9 +52,8 @@
#include <gui/DisplayEventReceiver.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
#include <EGL/eglext.h>
#include "BootAnimation.h"
@@ -108,6 +107,93 @@ static const char PROGRESS_PROP_NAME[] = "service.bootanim.progress";
static const char DISPLAYS_PROP_NAME[] = "persist.service.bootanim.displays";
static const int ANIM_ENTRY_NAME_MAX = ANIM_PATH_MAX + 1;
static constexpr size_t TEXT_POS_LEN_MAX = 16;
+static const int DYNAMIC_COLOR_COUNT = 4;
+static const char U_TEXTURE[] = "uTexture";
+static const char U_FADE[] = "uFade";
+static const char U_CROP_AREA[] = "uCropArea";
+static const char U_START_COLOR_PREFIX[] = "uStartColor";
+static const char U_END_COLOR_PREFIX[] = "uEndColor";
+static const char U_COLOR_PROGRESS[] = "uColorProgress";
+static const char A_UV[] = "aUv";
+static const char A_POSITION[] = "aPosition";
+static const char VERTEX_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ attribute vec4 aPosition;
+ attribute highp vec2 aUv;
+ varying highp vec2 vUv;
+ void main() {
+ gl_Position = aPosition;
+ vUv = aUv;
+ })";
+static const char IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ const float cWhiteMaskThreshold = 0.05;
+ uniform sampler2D uTexture;
+ uniform float uFade;
+ uniform float uColorProgress;
+ uniform vec4 uStartColor0;
+ uniform vec4 uStartColor1;
+ uniform vec4 uStartColor2;
+ uniform vec4 uStartColor3;
+ uniform vec4 uEndColor0;
+ uniform vec4 uEndColor1;
+ uniform vec4 uEndColor2;
+ uniform vec4 uEndColor3;
+ varying highp vec2 vUv;
+ void main() {
+ vec4 mask = texture2D(uTexture, vUv);
+ float r = mask.r;
+ float g = mask.g;
+ float b = mask.b;
+ float a = mask.a;
+ // If all channels have values, render pixel as a shade of white.
+ float useWhiteMask = step(cWhiteMaskThreshold, r)
+ * step(cWhiteMaskThreshold, g)
+ * step(cWhiteMaskThreshold, b)
+ * step(cWhiteMaskThreshold, a);
+ vec4 color = r * mix(uStartColor0, uEndColor0, uColorProgress)
+ + g * mix(uStartColor1, uEndColor1, uColorProgress)
+ + b * mix(uStartColor2, uEndColor2, uColorProgress)
+ + a * mix(uStartColor3, uEndColor3, uColorProgress);
+ color = mix(color, vec4(vec3((r + g + b + a) * 0.25), 1.0), useWhiteMask);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ })";
+static const char IMAGE_FRAG_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ uniform sampler2D uTexture;
+ uniform float uFade;
+ varying highp vec2 vUv;
+ void main() {
+ vec4 color = texture2D(uTexture, vUv);
+ gl_FragColor = vec4(color.x, color.y, color.z, (1.0 - uFade)) * color.a;
+ })";
+static const char TEXT_FRAG_SHADER_SOURCE[] = R"(
+ precision mediump float;
+ uniform sampler2D uTexture;
+ uniform vec4 uCropArea;
+ varying highp vec2 vUv;
+ void main() {
+ vec2 uv = vec2(mix(uCropArea.x, uCropArea.z, vUv.x),
+ mix(uCropArea.y, uCropArea.w, vUv.y));
+ gl_FragColor = texture2D(uTexture, uv);
+ })";
+
+static GLfloat quadPositions[] = {
+ -0.5f, -0.5f,
+ +0.5f, -0.5f,
+ +0.5f, +0.5f,
+ +0.5f, +0.5f,
+ -0.5f, +0.5f,
+ -0.5f, -0.5f
+};
+static GLfloat quadUVs[] = {
+ 0.0f, 1.0f,
+ 1.0f, 1.0f,
+ 1.0f, 0.0f,
+ 1.0f, 0.0f,
+ 0.0f, 0.0f,
+ 0.0f, 1.0f
+};
// ---------------------------------------------------------------------------
@@ -163,7 +249,8 @@ void BootAnimation::binderDied(const wp<IBinder>&) {
requestExit();
}
-static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo) {
+static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitmapInfo* outInfo,
+ bool premultiplyAlpha) {
AImageDecoder* decoder = nullptr;
AImageDecoder_createFromBuffer(encodedData, dataLength, &decoder);
if (!decoder) {
@@ -177,6 +264,10 @@ static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitm
outInfo->stride = AImageDecoder_getMinimumStride(decoder);
outInfo->flags = 0;
+ if (!premultiplyAlpha) {
+ AImageDecoder_setUnpremultipliedRequired(decoder, true);
+ }
+
const size_t size = outInfo->stride * outInfo->height;
void* pixels = malloc(size);
int result = AImageDecoder_decodeImage(decoder, pixels, outInfo->stride, size);
@@ -190,13 +281,14 @@ static void* decodeImage(const void* encodedData, size_t dataLength, AndroidBitm
}
status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
- const char* name) {
+ const char* name, bool premultiplyAlpha) {
Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
if (asset == nullptr)
return NO_INIT;
AndroidBitmapInfo bitmapInfo;
- void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo);
+ void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo,
+ premultiplyAlpha);
auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
asset->close();
@@ -209,7 +301,6 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
const int w = bitmapInfo.width;
const int h = bitmapInfo.height;
- GLint crop[4] = { 0, h, w, -h };
texture->w = w;
texture->h = h;
@@ -237,18 +328,19 @@ status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
break;
}
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return NO_ERROR;
}
-status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
+status_t BootAnimation::initTexture(FileMap* map, int* width, int* height,
+ bool premultiplyAlpha) {
AndroidBitmapInfo bitmapInfo;
- void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo);
+ void* pixels = decodeImage(map->getDataPtr(), map->getDataLength(), &bitmapInfo,
+ premultiplyAlpha);
auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };
// FileMap memory is never released until application exit.
@@ -263,7 +355,6 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
const int w = bitmapInfo.width;
const int h = bitmapInfo.height;
- GLint crop[4] = { 0, h, w, -h };
int tw = 1 << (31 - __builtin_clz(w));
int th = 1 << (31 - __builtin_clz(h));
if (tw < w) tw <<= 1;
@@ -297,7 +388,10 @@ status_t BootAnimation::initTexture(FileMap* map, int* width, int* height) {
break;
}
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
*width = w;
*height = h;
@@ -470,7 +564,9 @@ status_t BootAnimation::readyToRun() {
eglInitialize(display, nullptr, nullptr);
EGLConfig config = getEglConfig(display);
EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
- EGLContext context = eglCreateContext(display, config, nullptr, nullptr);
+ // Initialize egl context with client version number 2.0.
+ EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
+ EGLContext context = eglCreateContext(display, config, nullptr, contextAttributes);
EGLint w, h;
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
@@ -503,11 +599,6 @@ status_t BootAnimation::readyToRun() {
void BootAnimation::projectSceneToWindow() {
glViewport(0, 0, mWidth, mHeight);
glScissor(0, 0, mWidth, mHeight);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- glOrthof(0, static_cast<float>(mWidth), 0, static_cast<float>(mHeight), -1, 1);
- glMatrixMode(GL_MODELVIEW);
- glLoadIdentity();
}
void BootAnimation::resizeSurface(int newWidth, int newHeight) {
@@ -600,8 +691,76 @@ void BootAnimation::findBootAnimationFile() {
}
}
+GLuint compileShader(GLenum shaderType, const GLchar *source) {
+ GLuint shader = glCreateShader(shaderType);
+ glShaderSource(shader, 1, &source, 0);
+ glCompileShader(shader);
+ GLint isCompiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+ if (isCompiled == GL_FALSE) {
+ SLOGE("Compile shader failed. Shader type: %d", shaderType);
+ GLint maxLength = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+ std::vector<GLchar> errorLog(maxLength);
+ glGetShaderInfoLog(shader, maxLength, &maxLength, &errorLog[0]);
+ SLOGE("Shader compilation error: %s", &errorLog[0]);
+ return 0;
+ }
+ return shader;
+}
+
+GLuint linkShader(GLuint vertexShader, GLuint fragmentShader) {
+ GLuint program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glLinkProgram(program);
+ GLint isLinked = 0;
+ glGetProgramiv(program, GL_LINK_STATUS, (int *)&isLinked);
+ if (isLinked == GL_FALSE) {
+ SLOGE("Linking shader failed. Shader handles: vert %d, frag %d",
+ vertexShader, fragmentShader);
+ return 0;
+ }
+ return program;
+}
+
+void BootAnimation::initShaders() {
+ bool dynamicColoringEnabled = mAnimation != nullptr && mAnimation->dynamicColoringEnabled;
+ GLuint vertexShader = compileShader(GL_VERTEX_SHADER, (const GLchar *)VERTEX_SHADER_SOURCE);
+ GLuint imageFragmentShader =
+ compileShader(GL_FRAGMENT_SHADER, dynamicColoringEnabled
+ ? (const GLchar *)IMAGE_FRAG_DYNAMIC_COLORING_SHADER_SOURCE
+ : (const GLchar *)IMAGE_FRAG_SHADER_SOURCE);
+ GLuint textFragmentShader =
+ compileShader(GL_FRAGMENT_SHADER, (const GLchar *)TEXT_FRAG_SHADER_SOURCE);
+
+ // Initialize image shader.
+ mImageShader = linkShader(vertexShader, imageFragmentShader);
+ GLint positionLocation = glGetAttribLocation(mImageShader, A_POSITION);
+ GLint uvLocation = glGetAttribLocation(mImageShader, A_UV);
+ mImageTextureLocation = glGetUniformLocation(mImageShader, U_TEXTURE);
+ mImageFadeLocation = glGetUniformLocation(mImageShader, U_FADE);
+ glEnableVertexAttribArray(positionLocation);
+ glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions);
+ glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+ glEnableVertexAttribArray(uvLocation);
+
+ // Initialize text shader.
+ mTextShader = linkShader(vertexShader, textFragmentShader);
+ positionLocation = glGetAttribLocation(mTextShader, A_POSITION);
+ uvLocation = glGetAttribLocation(mTextShader, A_UV);
+ mTextTextureLocation = glGetUniformLocation(mTextShader, U_TEXTURE);
+ mTextCropAreaLocation = glGetUniformLocation(mTextShader, U_CROP_AREA);
+ glEnableVertexAttribArray(positionLocation);
+ glVertexAttribPointer(positionLocation, 2, GL_FLOAT, GL_FALSE, 0, quadPositions);
+ glVertexAttribPointer(uvLocation, 2, GL_FLOAT, GL_FALSE, 0, quadUVs);
+ glEnableVertexAttribArray(uvLocation);
+}
+
bool BootAnimation::threadLoop() {
bool result;
+ initShaders();
+
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZipFileName.isEmpty()) {
@@ -623,6 +782,8 @@ bool BootAnimation::threadLoop() {
}
bool BootAnimation::android() {
+ glActiveTexture(GL_TEXTURE0);
+
SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
@@ -631,19 +792,16 @@ bool BootAnimation::android() {
mCallbacks->init({});
// clear screen
- glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
+ glUseProgram(mImageShader);
+
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
- glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const nsecs_t startTime = systemTime();
do {
@@ -666,12 +824,12 @@ bool BootAnimation::android() {
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
- glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
- glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h);
+ drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
- glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
+ drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
@@ -766,6 +924,20 @@ static bool parseColor(const char str[7], float color[3]) {
return true;
}
+// Parse a color represented as a signed decimal int string.
+// E.g. "-2757722" (whose hex 2's complement is 0xFFD5EBA6).
+// If the input color string is empty, set color with values in defaultColor.
+static void parseColorDecimalString(const std::string& colorString,
+ float color[3], float defaultColor[3]) {
+ if (colorString == "") {
+ memcpy(color, defaultColor, sizeof(float) * 3);
+ return;
+ }
+ int colorInt = atoi(colorString.c_str());
+ color[0] = ((float)((colorInt >> 16) & 0xFF)) / 0xFF; // r
+ color[1] = ((float)((colorInt >> 8) & 0xFF)) / 0xFF; // g
+ color[2] = ((float)(colorInt & 0xFF)) / 0xFF; // b
+}
static bool readFile(ZipFileRO* zip, const char* name, String8& outString) {
ZipEntryRO entry = zip->findEntryByName(name);
@@ -798,10 +970,10 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) {
status = initTexture(font->map, &font->texture.w, &font->texture.h);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
} else if (fallback != nullptr) {
status = initTexture(&font->texture, mAssets, fallback);
} else {
@@ -816,40 +988,11 @@ status_t BootAnimation::initFont(Font* font, const char* fallback) {
return status;
}
-void BootAnimation::fadeFrame(const int frameLeft, const int frameBottom, const int frameWidth,
- const int frameHeight, const Animation::Part& part,
- const int fadedFramesCount) {
- glEnable(GL_BLEND);
- glEnableClientState(GL_VERTEX_ARRAY);
- glDisable(GL_TEXTURE_2D);
- // avoid creating a hole due to mixing result alpha with GL_REPLACE texture
- glBlendFuncSeparateOES(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
-
- const float alpha = static_cast<float>(fadedFramesCount) / part.framesToFadeCount;
- glColor4f(part.backgroundColor[0], part.backgroundColor[1], part.backgroundColor[2], alpha);
-
- const float frameStartX = static_cast<float>(frameLeft);
- const float frameStartY = static_cast<float>(frameBottom);
- const float frameEndX = frameStartX + frameWidth;
- const float frameEndY = frameStartY + frameHeight;
- const GLfloat frameRect[] = {
- frameStartX, frameStartY,
- frameEndX, frameStartY,
- frameEndX, frameEndY,
- frameStartX, frameEndY
- };
- glVertexPointer(2, GL_FLOAT, 0, frameRect);
- glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
-
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glEnable(GL_TEXTURE_2D);
- glDisableClientState(GL_VERTEX_ARRAY);
- glDisable(GL_BLEND);
-}
-
void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
glEnable(GL_BLEND); // Allow us to draw on top of the animation
glBindTexture(GL_TEXTURE_2D, font.texture.name);
+ glUseProgram(mTextShader);
+ glUniform1i(mTextTextureLocation, 0);
const int len = strlen(str);
const int strWidth = font.char_width * len;
@@ -865,8 +1008,6 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int*
*y = mHeight + *y - font.char_height;
}
- int cropRect[4] = { 0, 0, font.char_width, -font.char_height };
-
for (int i = 0; i < len; i++) {
char c = str[i];
@@ -878,13 +1019,13 @@ void BootAnimation::drawText(const char* str, const Font& font, bool bold, int*
const int charPos = (c - FONT_BEGIN_CHAR); // Position in the list of valid characters
const int row = charPos / FONT_NUM_COLS;
const int col = charPos % FONT_NUM_COLS;
- cropRect[0] = col * font.char_width; // Left of column
- cropRect[1] = row * font.char_height * 2; // Top of row
- // Move down to bottom of regular (one char_heigh) or bold (two char_heigh) line
- cropRect[1] += bold ? 2 * font.char_height : font.char_height;
- glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, cropRect);
-
- glDrawTexiOES(*x, *y, 0, font.char_width, font.char_height);
+ // Bold fonts are expected in the second half of each row.
+ float v0 = (row + (bold ? 0.5f : 0.0f)) / FONT_NUM_ROWS;
+ float u0 = ((float)col) / FONT_NUM_COLS;
+ float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2;
+ float u1 = u0 + 1.0f / FONT_NUM_COLS;
+ glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1);
+ drawTexturedQuad(*x, *y, font.char_width, font.char_height);
*x += font.char_width;
}
@@ -938,6 +1079,8 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
return false;
}
char const* s = desString.string();
+ std::string dynamicColoringPartName = "";
+ bool postDynamicColoring = false;
// Parse the description file
for (;;) {
@@ -952,11 +1095,19 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
int pause = 0;
int progress = 0;
int framesToFadeCount = 0;
+ int colorTransitionStart = 0;
+ int colorTransitionEnd = 0;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
+ char dynamicColoringPartNameBuffer[ANIM_ENTRY_NAME_MAX];
char pathType;
+ // start colors default to black if unspecified
+ char start_color_0[7] = "000000";
+ char start_color_1[7] = "000000";
+ char start_color_2[7] = "000000";
+ char start_color_3[7] = "000000";
int nextReadPos;
@@ -971,6 +1122,18 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
} else {
animation.progressEnabled = false;
}
+ } else if (sscanf(l, "dynamic_colors %" STRTO(ANIM_PATH_MAX) "s #%6s #%6s #%6s #%6s %d %d",
+ dynamicColoringPartNameBuffer,
+ start_color_0, start_color_1, start_color_2, start_color_3,
+ &colorTransitionStart, &colorTransitionEnd)) {
+ animation.dynamicColoringEnabled = true;
+ parseColor(start_color_0, animation.startColors[0]);
+ parseColor(start_color_1, animation.startColors[1]);
+ parseColor(start_color_2, animation.startColors[2]);
+ parseColor(start_color_3, animation.startColors[3]);
+ animation.colorTransitionStart = colorTransitionStart;
+ animation.colorTransitionEnd = colorTransitionEnd;
+ dynamicColoringPartName = std::string(dynamicColoringPartNameBuffer);
} else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
&pathType, &count, &pause, path, &nextReadPos) >= 4) {
if (pathType == 'f') {
@@ -983,6 +1146,16 @@ bool BootAnimation::parseAnimationDesc(Animation& animation) {
// "clockPos1=%s, clockPos2=%s",
// pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
Animation::Part part;
+ if (path == dynamicColoringPartName) {
+ // Part is specified to use dynamic coloring.
+ part.useDynamicColoring = true;
+ part.postDynamicColoring = false;
+ postDynamicColoring = true;
+ } else {
+ // Part does not use dynamic coloring.
+ part.useDynamicColoring = false;
+ part.postDynamicColoring = postDynamicColoring;
+ }
part.playUntilComplete = pathType == 'c';
part.framesToFadeCount = framesToFadeCount;
part.count = count;
@@ -1166,19 +1339,16 @@ bool BootAnimation::movie() {
// Blend required to draw time on top of animation frames.
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
- glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
- glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
bool clockFontInitialized = false;
if (mClockEnabled) {
clockFontInitialized =
@@ -1193,6 +1363,10 @@ bool BootAnimation::movie() {
mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
}
+ if (mAnimation != nullptr && mAnimation->dynamicColoringEnabled) {
+ initDynamicColors();
+ }
+
playAnimation(*mAnimation);
if (mTimeCheckThread != nullptr) {
@@ -1218,6 +1392,55 @@ bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part,
(lastDisplayedProgress == 0 || lastDisplayedProgress == 100);
}
+// Linear mapping from range <a1, a2> to range <b1, b2>
+float mapLinear(float x, float a1, float a2, float b1, float b2) {
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+}
+
+void BootAnimation::drawTexturedQuad(float xStart, float yStart, float width, float height) {
+ // Map coordinates from screen space to world space.
+ float x0 = mapLinear(xStart, 0, mWidth, -1, 1);
+ float y0 = mapLinear(yStart, 0, mHeight, -1, 1);
+ float x1 = mapLinear(xStart + width, 0, mWidth, -1, 1);
+ float y1 = mapLinear(yStart + height, 0, mHeight, -1, 1);
+ // Update quad vertex positions.
+ quadPositions[0] = x0;
+ quadPositions[1] = y0;
+ quadPositions[2] = x1;
+ quadPositions[3] = y0;
+ quadPositions[4] = x1;
+ quadPositions[5] = y1;
+ quadPositions[6] = x1;
+ quadPositions[7] = y1;
+ quadPositions[8] = x0;
+ quadPositions[9] = y1;
+ quadPositions[10] = x0;
+ quadPositions[11] = y0;
+ glDrawArrays(GL_TRIANGLES, 0,
+ sizeof(quadPositions) / sizeof(quadPositions[0]) / 2);
+}
+
+void BootAnimation::initDynamicColors() {
+ for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+ parseColorDecimalString(
+ android::base::GetProperty("persist.bootanim.color" + std::to_string(i + 1), ""),
+ mAnimation->endColors[i], mAnimation->startColors[i]);
+ }
+ glUseProgram(mImageShader);
+ SLOGI("[BootAnimation] Dynamically coloring boot animation.");
+ for (int i = 0; i < DYNAMIC_COLOR_COUNT; i++) {
+ float *startColor = mAnimation->startColors[i];
+ float *endColor = mAnimation->endColors[i];
+ glUniform4f(glGetUniformLocation(mImageShader,
+ (U_START_COLOR_PREFIX + std::to_string(i)).c_str()),
+ startColor[0], startColor[1], startColor[2], 1 /* alpha */);
+ glUniform4f(glGetUniformLocation(mImageShader,
+ (U_END_COLOR_PREFIX + std::to_string(i)).c_str()),
+ endColor[0], endColor[1], endColor[2], 1 /* alpha */);
+ }
+ mImageColorProgressLocation = glGetUniformLocation(mImageShader, U_COLOR_PROGRESS);
+}
+
bool BootAnimation::playAnimation(const Animation& animation) {
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
@@ -1230,7 +1453,6 @@ bool BootAnimation::playAnimation(const Animation& animation) {
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
- glBindTexture(GL_TEXTURE_2D, 0);
// Handle animation package
if (part.animation != nullptr) {
@@ -1261,6 +1483,19 @@ bool BootAnimation::playAnimation(const Animation& animation) {
for (size_t j=0 ; j<fcount ; j++) {
if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;
+ // Color progress is
+ // - the animation progress, normalized from
+ // [colorTransitionStart,colorTransitionEnd] to [0, 1] for the dynamic coloring
+ // part.
+ // - 0 for parts that come before,
+ // - 1 for parts that come after.
+ float colorProgress = part.useDynamicColoring
+ ? fmin(fmax(
+ ((float)j - animation.colorTransitionStart) /
+ fmax(animation.colorTransitionEnd -
+ animation.colorTransitionStart, 1.0f), 0.0f), 1.0f)
+ : (part.postDynamicColoring ? 1 : 0);
+
processDisplayEvents();
const int animationX = (mWidth - animation.width) / 2;
@@ -1272,44 +1507,38 @@ bool BootAnimation::playAnimation(const Animation& animation) {
if (r > 0) {
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
- if (part.count != 1) {
- glGenTextures(1, &frame.tid);
- glBindTexture(GL_TEXTURE_2D, frame.tid);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- }
+ glGenTextures(1, &frame.tid);
+ glBindTexture(GL_TEXTURE_2D, frame.tid);
int w, h;
- initTexture(frame.map, &w, &h);
+ // Set decoding option to alpha unpremultiplied so that the R, G, B channels
+ // of transparent pixels are preserved.
+ initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);
}
const int xc = animationX + frame.trimX;
const int yc = animationY + frame.trimY;
- Region clearReg(Rect(mWidth, mHeight));
- clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
- if (!clearReg.isEmpty()) {
- Region::const_iterator head(clearReg.begin());
- Region::const_iterator tail(clearReg.end());
- glEnable(GL_SCISSOR_TEST);
- while (head != tail) {
- const Rect& r2(*head++);
- glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
- glClear(GL_COLOR_BUFFER_BIT);
- }
- glDisable(GL_SCISSOR_TEST);
- }
+ glClear(GL_COLOR_BUFFER_BIT);
// specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
// which is equivalent to mHeight - (yc + frame.trimHeight)
const int frameDrawY = mHeight - (yc + frame.trimHeight);
- glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);
+ float fade = 0;
// if the part hasn't been stopped yet then continue fading if necessary
if (exitPending() && part.hasFadingPhase()) {
- fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
- ++fadedFramesCount);
+ fade = static_cast<float>(++fadedFramesCount) / part.framesToFadeCount;
if (fadedFramesCount >= part.framesToFadeCount) {
fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
}
}
+ glUseProgram(mImageShader);
+ glUniform1i(mImageTextureLocation, 0);
+ glUniform1f(mImageFadeLocation, fade);
+ if (animation.dynamicColoringEnabled) {
+ glUniform1f(mImageColorProgressLocation, colorProgress);
+ }
+ glEnable(GL_BLEND);
+ drawTexturedQuad(xc, frameDrawY, frame.trimWidth, frame.trimHeight);
+ glDisable(GL_BLEND);
if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index f8a31c6d8790..7a597da533ee 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -31,7 +31,7 @@
#include <binder/IBinder.h>
#include <EGL/egl.h>
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
namespace android {
@@ -53,7 +53,7 @@ public:
};
struct Font {
- FileMap* map;
+ FileMap* map = nullptr;
Texture texture;
int char_width;
int char_height;
@@ -62,7 +62,7 @@ public:
struct Animation {
struct Frame {
String8 name;
- FileMap* map;
+ FileMap* map = nullptr;
int trimX;
int trimY;
int trimWidth;
@@ -90,6 +90,10 @@ public:
uint8_t* audioData;
int audioLength;
Animation* animation;
+ // Controls if dynamic coloring is enabled for this part.
+ bool useDynamicColoring = false;
+ // Defines if this part is played after the dynamic coloring part.
+ bool postDynamicColoring = false;
bool hasFadingPhase() const {
return !playUntilComplete && framesToFadeCount > 0;
@@ -105,6 +109,12 @@ public:
ZipFileRO* zip;
Font clockFont;
Font progressFont;
+ // Controls if dynamic coloring is enabled for the whole animation.
+ bool dynamicColoringEnabled = false;
+ int colorTransitionStart = 0; // Start frame of dynamic color transition.
+ int colorTransitionEnd = 0; // End frame of dynamic color transition.
+ float startColors[4][3]; // Start colors of dynamic color transition.
+ float endColors[4][3]; // End colors of dynamic color transition.
};
// All callbacks will be called from this class's internal thread.
@@ -163,9 +173,12 @@ private:
int displayEventCallback(int fd, int events, void* data);
void processDisplayEvents();
- status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
- status_t initTexture(FileMap* map, int* width, int* height);
+ status_t initTexture(Texture* texture, AssetManager& asset, const char* name,
+ bool premultiplyAlpha = true);
+ status_t initTexture(FileMap* map, int* width, int* height,
+ bool premultiplyAlpha = true);
status_t initFont(Font* font, const char* fallback);
+ void initShaders();
bool android();
bool movie();
void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
@@ -173,6 +186,7 @@ private:
void drawProgress(int percent, const Font& font, const int xPos, const int yPos);
void fadeFrame(int frameLeft, int frameBottom, int frameWidth, int frameHeight,
const Animation::Part& part, int fadedFramesCount);
+ void drawTexturedQuad(float xStart, float yStart, float width, float height);
bool validClock(const Animation::Part& part);
Animation* loadAnimation(const String8&);
bool playAnimation(const Animation&);
@@ -192,6 +206,7 @@ private:
void checkExit();
void handleViewport(nsecs_t timestep);
+ void initDynamicColors();
sp<SurfaceComposerClient> mSession;
AssetManager mAssets;
@@ -218,6 +233,13 @@ private:
sp<TimeCheckThread> mTimeCheckThread = nullptr;
sp<Callbacks> mCallbacks;
Animation* mAnimation = nullptr;
+ GLuint mImageShader;
+ GLuint mTextShader;
+ GLuint mImageFadeLocation;
+ GLuint mImageTextureLocation;
+ GLuint mTextCropAreaLocation;
+ GLuint mTextTextureLocation;
+ GLuint mImageColorProgressLocation;
};
// ---------------------------------------------------------------------------
diff --git a/cmds/idmap2/OWNERS b/cmds/idmap2/OWNERS
index 062ffd41348a..def9f401e51f 100644
--- a/cmds/idmap2/OWNERS
+++ b/cmds/idmap2/OWNERS
@@ -1,4 +1,3 @@
set noparent
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4376d225e676..f53c5b6c9748 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -771,6 +771,11 @@ public class ActivityManager {
return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
}
+ /** @hide Should this process state be considered in the cache? */
+ public static final boolean isProcStateCached(int procState) {
+ return procState >= PROCESS_STATE_CACHED_ACTIVITY;
+ }
+
/** @hide Is this a foreground service type? */
public static boolean isForegroundService(int procState) {
return procState == PROCESS_STATE_FOREGROUND_SERVICE;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 0d68df48c316..4e8480c4e113 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -156,6 +156,7 @@ public abstract class ActivityManagerInternal {
/**
* Update information about which app IDs are on the temp allowlist.
* @param appids the updated list of appIds in temp allowlist.
+ * If null, it is to update only changingUid.
* @param changingUid uid to add or remove to temp allowlist.
* @param adding true to add to temp allowlist, false to remove from temp allowlist.
* @param durationMs when adding is true, the duration to be in temp allowlist.
@@ -165,7 +166,7 @@ public abstract class ActivityManagerInternal {
* @param callingUid the callingUid that setup this temp allowlist, only valid when param adding
* is true.
*/
- public abstract void updateDeviceIdleTempAllowlist(int[] appids, int changingUid,
+ public abstract void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
boolean adding, long durationMs, @TempAllowListType int type,
@ReasonCode int reasonCode,
@Nullable String reason, int callingUid);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index bfff27a82f5b..3b0a5f3e9bdd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4906,7 +4906,8 @@ public final class ActivityThread extends ClientTransactionHandler
Slog.w(TAG, "Activity top position already set to onTop=" + onTop);
return;
}
- throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+ // TODO(b/197484331): Remove this short-term workaround while fixing the binder failure.
+ Slog.e(TAG, "Activity top position already set to onTop=" + onTop);
}
r.isTopResumedActivity = onTop;
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index c36b585d3183..9272e45c3278 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2735,10 +2735,13 @@ class ContextImpl extends Context {
// need to override their display in ResourcesManager.
baseContext.mForceDisplayOverrideInResources = false;
baseContext.mContextType = CONTEXT_TYPE_WINDOW_CONTEXT;
- baseContext.mDisplay = display;
final Resources windowContextResources = createWindowContextResources(baseContext);
baseContext.setResources(windowContextResources);
+ // Associate the display with window context resources so that configuration update from
+ // the server side will also apply to the display's metrics.
+ baseContext.mDisplay = ResourcesManager.getInstance()
+ .getAdjustedDisplay(display.getDisplayId(), windowContextResources);
return baseContext;
}
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 5964f71d28db..babeb08caff7 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -135,6 +135,7 @@ public final class GameManager {
throw e.rethrowFromSystemServer();
}
}
+
/**
* Returns a list of supported game modes for a given package.
* <p>
@@ -151,4 +152,20 @@ public final class GameManager {
}
}
+ /**
+ * Returns if ANGLE is enabled for a given package.
+ * <p>
+ * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ *
+ * @hide
+ */
+ @UserHandleAware
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode boolean isAngleEnabled(@NonNull String packageName) {
+ try {
+ return mService.getAngleEnabled(packageName, mContext.getUserId());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/IGameManagerService.aidl b/core/java/android/app/IGameManagerService.aidl
index 4bf8a3f77bca..189f0a2fca23 100644
--- a/core/java/android/app/IGameManagerService.aidl
+++ b/core/java/android/app/IGameManagerService.aidl
@@ -23,4 +23,5 @@ interface IGameManagerService {
int getGameMode(String packageName, int userId);
void setGameMode(String packageName, int gameMode, int userId);
int[] getAvailableGameModes(String packageName);
+ boolean getAngleEnabled(String packageName, int userId);
} \ No newline at end of file
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4b054f49d910..61b1abe25ca2 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1884,6 +1884,14 @@ public class Notification implements Parcelable
* clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the
* activity itself.
*
+ * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or
+ * both are displayed or required, depends on where and how the action is used, and the
+ * {@link Style} applied to the Notification.
+ *
+ * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a
+ * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed
+ * with an altered in luminance to ensure proper contrast within the Notification.
+ *
* @param icon icon to show for this action
* @param title the title of the action
* @param intent the {@link PendingIntent} to fire when users trigger this action
@@ -5387,8 +5395,8 @@ public class Notification implements Parcelable
contentView.setInt(R.id.expand_button, "setDefaultPillColor", pillColor);
// Use different highlighted colors for conversations' unread count
if (p.mHighlightExpander) {
- pillColor = Colors.flattenAlpha(getPrimaryAccentColor(p), bgColor);
- textColor = Colors.flattenAlpha(bgColor, pillColor);
+ pillColor = Colors.flattenAlpha(getColors(p).getTertiaryAccentColor(), bgColor);
+ textColor = Colors.flattenAlpha(getColors(p).getOnAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
@@ -6121,21 +6129,22 @@ public class Notification implements Parcelable
if (emphasizedMode) {
// change the background bgColor
CharSequence title = action.title;
- ColorStateList[] outResultColor = new ColorStateList[1];
int buttonFillColor = getColors(p).getSecondaryAccentColor();
if (isLegacy()) {
title = ContrastColorUtil.clearColorSpans(title);
} else {
- int notifBackgroundColor = getColors(p).getBackgroundColor();
- title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor);
+ // Check for a full-length span color to use as the button fill color.
+ Integer fullLengthColor = getFullLengthSpanColor(title);
+ if (fullLengthColor != null) {
+ // Ensure the custom button fill has 1.3:1 contrast w/ notification bg.
+ int notifBackgroundColor = getColors(p).getBackgroundColor();
+ buttonFillColor = ensureButtonFillContrast(
+ fullLengthColor, notifBackgroundColor);
+ }
+ // Remove full-length color spans and ensure text contrast with the button fill.
+ title = ensureColorSpanContrast(title, buttonFillColor);
}
button.setTextViewText(R.id.action0, processTextSpans(title));
- boolean hasColorOverride = outResultColor[0] != null;
- if (hasColorOverride) {
- // There's a span spanning the full text, let's take it and use it as the
- // background color
- buttonFillColor = outResultColor[0].getDefaultColor();
- }
final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
buttonFillColor, mInNightMode);
button.setTextColor(R.id.action0, textColor);
@@ -6168,17 +6177,58 @@ public class Notification implements Parcelable
}
/**
- * Ensures contrast on color spans against a background color. also returns the color of the
- * text if a span was found that spans over the whole text.
+ * Extract the color from a full-length span from the text.
+ *
+ * @param charSequence the charSequence containing spans
+ * @return the raw color of the text's last full-length span containing a color, or null if
+ * no full-length span sets the text color.
+ * @hide
+ */
+ @VisibleForTesting
+ @Nullable
+ public static Integer getFullLengthSpanColor(CharSequence charSequence) {
+ // NOTE: this method preserves the functionality that for a CharSequence with multiple
+ // full-length spans, the color of the last one is used.
+ Integer result = null;
+ if (charSequence instanceof Spanned) {
+ Spanned ss = (Spanned) charSequence;
+ Object[] spans = ss.getSpans(0, ss.length(), Object.class);
+ // First read through all full-length spans to get the button fill color, which will
+ // be used as the background color for ensuring contrast of non-full-length spans.
+ for (Object span : spans) {
+ int spanStart = ss.getSpanStart(span);
+ int spanEnd = ss.getSpanEnd(span);
+ boolean fullLength = (spanEnd - spanStart) == charSequence.length();
+ if (!fullLength) {
+ continue;
+ }
+ if (span instanceof TextAppearanceSpan) {
+ TextAppearanceSpan originalSpan = (TextAppearanceSpan) span;
+ ColorStateList textColor = originalSpan.getTextColor();
+ if (textColor != null) {
+ result = textColor.getDefaultColor();
+ }
+ } else if (span instanceof ForegroundColorSpan) {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) span;
+ result = originalSpan.getForegroundColor();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Ensures contrast on color spans against a background color.
+ * Note that any full-length color spans will be removed instead of being contrasted.
*
* @param charSequence the charSequence on which the spans are
* @param background the background color to ensure the contrast against
- * @param outResultColor an array in which a color will be returned as the first element if
- * there exists a full length color span.
* @return the contrasted charSequence
+ * @hide
*/
- private static CharSequence ensureColorSpanContrast(CharSequence charSequence,
- int background, ColorStateList[] outResultColor) {
+ @VisibleForTesting
+ public static CharSequence ensureColorSpanContrast(CharSequence charSequence,
+ int background) {
if (charSequence instanceof Spanned) {
Spanned ss = (Spanned) charSequence;
Object[] spans = ss.getSpans(0, ss.length(), Object.class);
@@ -6195,19 +6245,19 @@ public class Notification implements Parcelable
TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan;
ColorStateList textColor = originalSpan.getTextColor();
if (textColor != null) {
- int[] colors = textColor.getColors();
- int[] newColors = new int[colors.length];
- for (int i = 0; i < newColors.length; i++) {
- boolean isBgDark = isColorDark(background);
- newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
- colors[i], background, isBgDark);
- }
- textColor = new ColorStateList(textColor.getStates().clone(),
- newColors);
if (fullLength) {
- outResultColor[0] = textColor;
// Let's drop the color from the span
textColor = null;
+ } else {
+ int[] colors = textColor.getColors();
+ int[] newColors = new int[colors.length];
+ for (int i = 0; i < newColors.length; i++) {
+ boolean isBgDark = isColorDark(background);
+ newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
+ colors[i], background, isBgDark);
+ }
+ textColor = new ColorStateList(textColor.getStates().clone(),
+ newColors);
}
resultSpan = new TextAppearanceSpan(
originalSpan.getFamily(),
@@ -6217,15 +6267,14 @@ public class Notification implements Parcelable
originalSpan.getLinkTextColor());
}
} else if (resultSpan instanceof ForegroundColorSpan) {
- ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
- int foregroundColor = originalSpan.getForegroundColor();
- boolean isBgDark = isColorDark(background);
- foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
- foregroundColor, background, isBgDark);
if (fullLength) {
- outResultColor[0] = ColorStateList.valueOf(foregroundColor);
resultSpan = null;
} else {
+ ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
+ int foregroundColor = originalSpan.getForegroundColor();
+ boolean isBgDark = isColorDark(background);
+ foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
+ foregroundColor, background, isBgDark);
resultSpan = new ForegroundColorSpan(foregroundColor);
}
} else {
@@ -6247,13 +6296,29 @@ public class Notification implements Parcelable
*
* @param color the color to check
* @return true if the color has higher contrast with white than black
+ * @hide
*/
- private static boolean isColorDark(int color) {
+ public static boolean isColorDark(int color) {
// as per ContrastColorUtil.shouldUseDark, this uses the color contrast midpoint.
return ContrastColorUtil.calculateLuminance(color) <= 0.17912878474;
}
/**
+ * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue
+ * as the original color, but is lightened or darkened depending on whether the background
+ * is dark or light.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static int ensureButtonFillContrast(int color, int bg) {
+ return isColorDark(bg)
+ ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3)
+ : ContrastColorUtil.findContrastColor(color, bg, true, 1.3);
+ }
+
+
+ /**
* @return Whether we are currently building a notification from a legacy (an app that
* doesn't create material notifications by itself) app.
*/
@@ -6441,25 +6506,34 @@ public class Notification implements Parcelable
if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N
&& !styleDisplaysCustomViewInline()) {
- if (mN.contentView == null) {
- mN.contentView = createContentView();
+ RemoteViews newContentView = mN.contentView;
+ RemoteViews newBigContentView = mN.bigContentView;
+ RemoteViews newHeadsUpContentView = mN.headsUpContentView;
+ if (newContentView == null) {
+ newContentView = createContentView();
mN.extras.putInt(EXTRA_REBUILD_CONTENT_VIEW_ACTION_COUNT,
- mN.contentView.getSequenceNumber());
+ newContentView.getSequenceNumber());
}
- if (mN.bigContentView == null) {
- mN.bigContentView = createBigContentView();
- if (mN.bigContentView != null) {
+ if (newBigContentView == null) {
+ newBigContentView = createBigContentView();
+ if (newBigContentView != null) {
mN.extras.putInt(EXTRA_REBUILD_BIG_CONTENT_VIEW_ACTION_COUNT,
- mN.bigContentView.getSequenceNumber());
+ newBigContentView.getSequenceNumber());
}
}
- if (mN.headsUpContentView == null) {
- mN.headsUpContentView = createHeadsUpContentView();
- if (mN.headsUpContentView != null) {
+ if (newHeadsUpContentView == null) {
+ newHeadsUpContentView = createHeadsUpContentView();
+ if (newHeadsUpContentView != null) {
mN.extras.putInt(EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT,
- mN.headsUpContentView.getSequenceNumber());
+ newHeadsUpContentView.getSequenceNumber());
}
}
+ // Don't set any of the content views until after they have all been generated,
+ // to avoid the generated .contentView triggering the logic which skips generating
+ // the .bigContentView.
+ mN.contentView = newContentView;
+ mN.bigContentView = newBigContentView;
+ mN.headsUpContentView = newHeadsUpContentView;
}
if ((mN.defaults & DEFAULT_LIGHTS) != 0) {
@@ -12305,6 +12379,8 @@ public class Notification implements Parcelable
private int mSecondaryTextColor = COLOR_INVALID;
private int mPrimaryAccentColor = COLOR_INVALID;
private int mSecondaryAccentColor = COLOR_INVALID;
+ private int mTertiaryAccentColor = COLOR_INVALID;
+ private int mOnAccentTextColor = COLOR_INVALID;
private int mErrorColor = COLOR_INVALID;
private int mContrastColor = COLOR_INVALID;
private int mRippleAlpha = 0x33;
@@ -12362,7 +12438,7 @@ public class Notification implements Parcelable
if (isColorized) {
if (rawColor == COLOR_DEFAULT) {
- int[] attrs = {R.attr.colorAccentTertiary};
+ int[] attrs = {R.attr.colorAccentSecondary};
try (TypedArray ta = obtainDayNightAttributes(ctx, attrs)) {
mBackgroundColor = getColor(ta, 0, Color.WHITE);
}
@@ -12379,6 +12455,8 @@ public class Notification implements Parcelable
mContrastColor = mPrimaryTextColor;
mPrimaryAccentColor = mPrimaryTextColor;
mSecondaryAccentColor = mSecondaryTextColor;
+ mTertiaryAccentColor = flattenAlpha(mPrimaryTextColor, mBackgroundColor);
+ mOnAccentTextColor = mBackgroundColor;
mErrorColor = mPrimaryTextColor;
mRippleAlpha = 0x33;
} else {
@@ -12389,6 +12467,8 @@ public class Notification implements Parcelable
R.attr.textColorSecondary,
R.attr.colorAccent,
R.attr.colorAccentSecondary,
+ R.attr.colorAccentTertiary,
+ R.attr.textColorOnAccent,
R.attr.colorError,
R.attr.colorControlHighlight
};
@@ -12399,8 +12479,10 @@ public class Notification implements Parcelable
mSecondaryTextColor = getColor(ta, 3, COLOR_INVALID);
mPrimaryAccentColor = getColor(ta, 4, COLOR_INVALID);
mSecondaryAccentColor = getColor(ta, 5, COLOR_INVALID);
- mErrorColor = getColor(ta, 6, COLOR_INVALID);
- mRippleAlpha = Color.alpha(getColor(ta, 7, 0x33ffffff));
+ mTertiaryAccentColor = getColor(ta, 6, COLOR_INVALID);
+ mOnAccentTextColor = getColor(ta, 7, COLOR_INVALID);
+ mErrorColor = getColor(ta, 8, COLOR_INVALID);
+ mRippleAlpha = Color.alpha(getColor(ta, 9, 0x33ffffff));
}
mContrastColor = calculateContrastColor(ctx, rawColor, mPrimaryAccentColor,
mBackgroundColor, nightMode);
@@ -12420,6 +12502,14 @@ public class Notification implements Parcelable
if (mSecondaryAccentColor == COLOR_INVALID) {
mSecondaryAccentColor = mContrastColor;
}
+ if (mTertiaryAccentColor == COLOR_INVALID) {
+ mTertiaryAccentColor = mContrastColor;
+ }
+ if (mOnAccentTextColor == COLOR_INVALID) {
+ mOnAccentTextColor = ColorUtils.setAlphaComponent(
+ ContrastColorUtil.resolvePrimaryColor(
+ ctx, mTertiaryAccentColor, nightMode), 0xFF);
+ }
if (mErrorColor == COLOR_INVALID) {
mErrorColor = mPrimaryTextColor;
}
@@ -12485,6 +12575,16 @@ public class Notification implements Parcelable
return mSecondaryAccentColor;
}
+ /** @return the theme's tertiary accent color for colored UI elements. */
+ public @ColorInt int getTertiaryAccentColor() {
+ return mTertiaryAccentColor;
+ }
+
+ /** @return the theme's text color to be used on the tertiary accent color. */
+ public @ColorInt int getOnAccentTextColor() {
+ return mOnAccentTextColor;
+ }
+
/**
* @return the contrast-adjusted version of the color provided by the app, or the
* primary text color when colorized.
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1837fb860b73..6553b61ebbc2 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -260,8 +260,6 @@ public final class NotificationChannel implements Parcelable {
private boolean mDemoted = false;
private boolean mImportantConvo = false;
private long mDeletedTime = DEFAULT_DELETION_TIME_MS;
- // If the sound for this channel is missing, e.g. after restore.
- private boolean mIsSoundMissing;
/**
* Creates a notification channel.
@@ -717,13 +715,6 @@ public final class NotificationChannel implements Parcelable {
}
/**
- * @hide
- */
- public boolean isSoundMissing() {
- return mIsSoundMissing;
- }
-
- /**
* Returns the audio attributes for sound played by notifications posted to this channel.
*/
public AudioAttributes getAudioAttributes() {
@@ -1007,9 +998,8 @@ public final class NotificationChannel implements Parcelable {
// according to the docs because canonicalize method has to handle canonical uris as well.
Uri canonicalizedUri = contentResolver.canonicalize(uri);
if (canonicalizedUri == null) {
- // We got a null because the uri in the backup does not exist here.
- mIsSoundMissing = true;
- return null;
+ // We got a null because the uri in the backup does not exist here, so we return default
+ return Settings.System.DEFAULT_NOTIFICATION_URI;
}
return contentResolver.uncanonicalize(canonicalizedUri);
}
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 8d332ab1d58b..6cfa39cd2337 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -559,6 +559,53 @@ public class WallpaperManager {
return null;
}
+ public Rect peekWallpaperDimensions(Context context, boolean returnDefault, int userId) {
+ if (mService != null) {
+ try {
+ if (!mService.isWallpaperSupported(context.getOpPackageName())) {
+ return new Rect();
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ Rect dimensions = null;
+ synchronized (this) {
+ try {
+ Bundle params = new Bundle();
+ // Let's peek user wallpaper first.
+ ParcelFileDescriptor pfd = mService.getWallpaperWithFeature(
+ context.getOpPackageName(), context.getAttributionTag(), this,
+ FLAG_SYSTEM, params, userId);
+ if (pfd != null) {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(), null, options);
+ dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+ }
+ } catch (RemoteException ex) {
+ Log.w(TAG, "peek wallpaper dimensions failed", ex);
+ }
+ }
+ // If user wallpaper is unavailable, may be the default one instead.
+ if ((dimensions == null || dimensions.width() == 0 || dimensions.height() == 0)
+ && returnDefault) {
+ InputStream is = openDefaultWallpaper(context, FLAG_SYSTEM);
+ if (is != null) {
+ try {
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(is, null, options);
+ dimensions = new Rect(0, 0, options.outWidth, options.outHeight);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
+ }
+ return dimensions;
+ }
+
void forgetLoadedWallpaper() {
synchronized (this) {
mCachedWallpaper = null;
@@ -1039,6 +1086,17 @@ public class WallpaperManager {
}
/**
+ * Peek the dimensions of system wallpaper of the user without decoding it.
+ *
+ * @return the dimensions of system wallpaper
+ * @hide
+ */
+ public Rect peekBitmapDimensions() {
+ return sGlobals.peekWallpaperDimensions(
+ mContext, true /* returnDefault */, mContext.getUserId());
+ }
+
+ /**
* Get an open, readable file descriptor to the given wallpaper image file.
* The caller is responsible for closing the file descriptor when done ingesting the file.
*
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0e04ad3768c7..0fe80c45ad2a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9734,6 +9734,27 @@ public class DevicePolicyManager {
}
/**
+ * @param userId The user for whom to retrieve information.
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Details of admin and user which enforced the restriction for the userId. If
+ * restriction is null, profile owner for the user or device owner info is returned.
+ * @hide
+ */
+ public @Nullable Bundle getEnforcingAdminAndUserDetails(int userId,
+ @Nullable String restriction) {
+ if (mService != null) {
+ try {
+ return mService.getEnforcingAdminAndUserDetails(userId, restriction);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Hide or unhide packages. When a package is hidden it is unavailable for use, but the data and
* actual package file remain. This function can be called by a device owner, profile owner, or
* by a delegate given the {@link #DELEGATION_PACKAGE_ACCESS} scope via
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index fdc4a165ffa3..d287437b143b 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -251,6 +251,7 @@ interface IDevicePolicyManager {
boolean isNotificationListenerServicePermitted(in String packageName, int userId);
Intent createAdminSupportIntent(in String restriction);
+ Bundle getEnforcingAdminAndUserDetails(int userId,String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden, boolean parent);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean parent);
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index dae565e12fd7..67f631f98f0b 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -281,6 +281,32 @@ public class BackupManager {
}
/**
+ * Convenience method for callers who need to indicate that some other package or
+ * some other user needs a backup pass. This can be useful in the case of groups of
+ * packages that share a uid and/or have user-specific data.
+ * <p>
+ * This method requires that the application hold the "android.permission.BACKUP"
+ * permission if the package named in the package argument does not run under the
+ * same uid as the caller. This method also requires that the application hold the
+ * "android.permission.INTERACT_ACROSS_USERS_FULL" if the user argument is not the
+ * same as the user the caller is running under.
+ * @param userId The user to back up
+ * @param packageName The package name identifying the application to back up.
+ *
+ * @hide
+ */
+ public static void dataChangedForUser(int userId, String packageName) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ sService.dataChangedForUser(userId, packageName);
+ } catch (RemoteException e) {
+ Log.e(TAG, "dataChanged(userId,pkg) couldn't connect");
+ }
+ }
+ }
+
+ /**
* @deprecated Applications shouldn't request a restore operation using this method. In Android
* P and later, this method is a no-op.
*
diff --git a/core/java/android/app/search/Query.java b/core/java/android/app/search/Query.java
index c64e10723ca6..f073b4e50555 100644
--- a/core/java/android/app/search/Query.java
+++ b/core/java/android/app/search/Query.java
@@ -70,7 +70,7 @@ public final class Query implements Parcelable {
@NonNull Bundle extras) {
mInput = input;
mTimestampMillis = timestampMillis;
- mExtras = extras == null ? extras : new Bundle();
+ mExtras = extras != null ? extras : new Bundle();
}
/**
diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java
index 063ba1174cdc..2e94dd1a47c4 100644
--- a/core/java/android/appwidget/AppWidgetProviderInfo.java
+++ b/core/java/android/appwidget/AppWidgetProviderInfo.java
@@ -143,7 +143,7 @@ public class AppWidgetProviderInfo implements Parcelable {
public ComponentName provider;
/**
- * The default height of the widget when added to a host, in dp. The widget will get
+ * The default height of the widget when added to a host, in px. The widget will get
* at least this width, and will often be given more, depending on the host.
*
* <p>This field corresponds to the <code>android:minWidth</code> attribute in
@@ -152,7 +152,7 @@ public class AppWidgetProviderInfo implements Parcelable {
public int minWidth;
/**
- * The default height of the widget when added to a host, in dp. The widget will get
+ * The default height of the widget when added to a host, in px. The widget will get
* at least this height, and will often be given more, depending on the host.
*
* <p>This field corresponds to the <code>android:minHeight</code> attribute in
@@ -161,7 +161,7 @@ public class AppWidgetProviderInfo implements Parcelable {
public int minHeight;
/**
- * Minimum width (in dp) which the widget can be resized to. This field has no effect if it
+ * Minimum width (in px) which the widget can be resized to. This field has no effect if it
* is greater than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:minResizeWidth</code> attribute in
@@ -170,7 +170,7 @@ public class AppWidgetProviderInfo implements Parcelable {
public int minResizeWidth;
/**
- * Minimum height (in dp) which the widget can be resized to. This field has no effect if it
+ * Minimum height (in px) which the widget can be resized to. This field has no effect if it
* is greater than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:minResizeHeight</code> attribute in
@@ -179,7 +179,7 @@ public class AppWidgetProviderInfo implements Parcelable {
public int minResizeHeight;
/**
- * Maximum width (in dp) which the widget can be resized to. This field has no effect if it is
+ * Maximum width (in px) which the widget can be resized to. This field has no effect if it is
* smaller than minWidth or if horizontal resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:maxResizeWidth</code> attribute in the
@@ -189,7 +189,7 @@ public class AppWidgetProviderInfo implements Parcelable {
public int maxResizeWidth;
/**
- * Maximum height (in dp) which the widget can be resized to. This field has no effect if it is
+ * Maximum height (in px) which the widget can be resized to. This field has no effect if it is
* smaller than minHeight or if vertical resizing isn't enabled (see {@link #resizeMode}).
*
* <p>This field corresponds to the <code>android:maxResizeHeight</code> attribute in the
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6885c10b4889..ca5b63fdeec9 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3286,7 +3286,7 @@ public abstract class Context {
* apps targeting SDK Version {@link android.os.Build.VERSION_CODES#O}
* or higher are not allowed to start background services from the background.
* See
- * <a href="{@docRoot}/about/versions/oreo/background">
+ * <a href="/about/versions/oreo/background">
* Background Execution Limits</a>
* for more details.
*
@@ -3295,7 +3295,7 @@ public abstract class Context {
* apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
* or higher are not allowed to start foreground services from the background.
* See
- * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * <a href="/about/versions/12/behavior-changes-12">
* Behavior changes: Apps targeting Android 12
* </a>
* for more details.
@@ -3349,7 +3349,7 @@ public abstract class Context {
* apps targeting SDK Version {@link android.os.Build.VERSION_CODES#S}
* or higher are not allowed to start foreground services from the background.
* See
- * <a href="{@docRoot}/about/versions/12/behavior-changes-12">
+ * <a href="/about/versions/12/behavior-changes-12">
* Behavior changes: Apps targeting Android 12
* </a>
* for more details.
@@ -6026,6 +6026,10 @@ public abstract class Context {
* more general access to the URI's content provider then this check will
* always fail.
*
+ * <strong>Note:</strong> On SDK Version {@link android.os.Build.VERSION_CODES#S},
+ * calling this method from a secondary-user's context will incorrectly return
+ * {@link PackageManager#PERMISSION_DENIED} for all {code uris}.
+ *
* @param uris The list of URIs that is being checked.
* @param pid The process ID being checked against. Must be &gt; 0.
* @param uid The user ID being checked against. A uid of 0 is the root
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e781c2fce2b2..1552dbd00c92 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2742,6 +2742,22 @@ public class Intent implements Parcelable, Cloneable {
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_PACKAGES_UNSUSPENDED = "android.intent.action.PACKAGES_UNSUSPENDED";
+ /**
+ * Broadcast Action: One of the suspend conditions have been modified for the packages.
+ * <p>Includes the following extras:
+ * <ul>
+ * <li> {@link #EXTRA_CHANGED_PACKAGE_LIST} is the set of packages which have been modified
+ * <li> {@link #EXTRA_CHANGED_UID_LIST} is the set of uids which have been modified
+ * </ul>
+ *
+ * <p class="note">This is a protected intent that can only be sent
+ * by the system. It is only sent to registered receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_PACKAGES_SUSPENSION_CHANGED =
+ "android.intent.action.PACKAGES_SUSPENSION_CHANGED";
/**
* Broadcast Action: Distracting packages have been changed.
diff --git a/core/java/android/content/integrity/OWNERS b/core/java/android/content/integrity/OWNERS
index 20c758aedd67..a1fe59c862d2 100644
--- a/core/java/android/content/integrity/OWNERS
+++ b/core/java/android/content/integrity/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 722021
-toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/om/OWNERS b/core/java/android/content/om/OWNERS
index 3669817e9844..9c473360a46a 100644
--- a/core/java/android/content/om/OWNERS
+++ b/core/java/android/content/om/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 568631
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 4e674f6ec9a8..128bfb99ccd4 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -1,11 +1,9 @@
# Bug component: 36137
-toddke@android.com
-toddke@google.com
patb@google.com
per-file PackageParser.java = set noparent
-per-file PackageParser.java = chiuwinson@google.com,patb@google.com,toddke@google.com
+per-file PackageParser.java = chiuwinson@google.com,patb@google.com
per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file AppSearchPerson.java = file:/core/java/android/content/pm/SHORTCUT_OWNERS
per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
diff --git a/core/java/android/content/pm/dex/OWNERS b/core/java/android/content/pm/dex/OWNERS
index 267e5d58f9a6..b590f659a5d4 100644
--- a/core/java/android/content/pm/dex/OWNERS
+++ b/core/java/android/content/pm/dex/OWNERS
@@ -1,7 +1,5 @@
# Bug component: 86431
-toddke@android.com
-toddke@google.com
patb@google.com
calin@google.com
ngeoffray@google.com
diff --git a/core/java/android/content/pm/parsing/OWNERS b/core/java/android/content/pm/parsing/OWNERS
index 8049d5cb7fa2..445a8330037b 100644
--- a/core/java/android/content/pm/parsing/OWNERS
+++ b/core/java/android/content/pm/parsing/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com
diff --git a/core/java/android/content/pm/permission/OWNERS b/core/java/android/content/pm/permission/OWNERS
index cf7e6890876a..f9c51dd2ae58 100644
--- a/core/java/android/content/pm/permission/OWNERS
+++ b/core/java/android/content/pm/permission/OWNERS
@@ -2,7 +2,5 @@
include platform/frameworks/base:/core/java/android/permission/OWNERS
-toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/pm/split/OWNERS b/core/java/android/content/pm/split/OWNERS
index 3d126d297e60..b8fa1a93ed78 100644
--- a/core/java/android/content/pm/split/OWNERS
+++ b/core/java/android/content/pm/split/OWNERS
@@ -1,5 +1,3 @@
# Bug component: 36137
-toddke@android.com
-toddke@google.com
patb@google.com
diff --git a/core/java/android/content/pm/verify/domain/OWNERS b/core/java/android/content/pm/verify/domain/OWNERS
index c669112e0512..445a8330037b 100644
--- a/core/java/android/content/pm/verify/domain/OWNERS
+++ b/core/java/android/content/pm/verify/domain/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com \ No newline at end of file
diff --git a/core/java/android/content/res/OWNERS b/core/java/android/content/res/OWNERS
index d12d920b2a54..7460a14182e5 100644
--- a/core/java/android/content/res/OWNERS
+++ b/core/java/android/content/res/OWNERS
@@ -1,6 +1,4 @@
# Bug component: 568761
-toddke@android.com
-toddke@google.com
patb@google.com
zyy@google.com
diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS
index 2b4e4a106cec..75b42d4b64e1 100644
--- a/core/java/android/hardware/OWNERS
+++ b/core/java/android/hardware/OWNERS
@@ -1,5 +1,5 @@
# Camera
-per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,yinchiayeh@google.com,zhijunhe@google.com,jchowdhary@google.com
+per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com
# Sensor Privacy
per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 3f3db29ed0fd..c8c122da4ab8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -438,9 +438,16 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelAuthentication();
+ Log.d(TAG, "Cancel BP authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
@@ -853,10 +860,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
* @param userId The user to authenticate
* @param operationId The keystore operation associated with authentication
*
+ * @return A requestId that can be used to cancel this operation.
+ *
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public void authenticateUserForOperation(
+ public long authenticateUserForOperation(
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@NonNull AuthenticationCallback callback,
@@ -871,7 +880,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
if (callback == null) {
throw new IllegalArgumentException("Must supply a callback");
}
- authenticateInternal(operationId, cancel, executor, callback, userId);
+
+ return authenticateInternal(operationId, cancel, executor, callback, userId);
}
/**
@@ -1002,10 +1012,10 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
}
- private void cancelAuthentication() {
+ private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
Log.e(TAG, "Unable to cancel authentication", e);
}
@@ -1024,7 +1034,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
authenticateInternal(operationId, cancel, executor, callback, userId);
}
- private void authenticateInternal(
+ private long authenticateInternal(
long operationId,
@NonNull CancellationSignal cancel,
@NonNull @CallbackExecutor Executor executor,
@@ -1040,9 +1050,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
try {
if (cancel.isCanceled()) {
Log.w(TAG, "Authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener());
+ return -1;
}
mExecutor = executor;
@@ -1065,14 +1073,16 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
promptInfo = mPromptInfo;
}
- mService.authenticate(mToken, operationId, userId, mBiometricServiceReceiver,
- mContext.getOpPackageName(), promptInfo);
-
+ final long authId = mService.authenticate(mToken, operationId, userId,
+ mBiometricServiceReceiver, mContext.getOpPackageName(), promptInfo);
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ return authId;
} catch (RemoteException e) {
Log.e(TAG, "Remote exception while authenticating", e);
mExecutor.execute(() -> callback.onAuthenticationError(
BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
mContext.getString(R.string.biometric_error_hw_unavailable)));
+ return -1;
}
}
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 4c2a9ae10cbd..91f794c161bf 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -41,13 +41,14 @@ interface IAuthService {
// Retrieve the package where BIometricOrompt's UI is implemented
String getUiPackage();
- // Requests authentication. The service choose the appropriate biometric to use, and show
- // the corresponding BiometricDialog.
- void authenticate(IBinder token, long sessionId, int userId,
+ // Requests authentication. The service chooses the appropriate biometric to use, and shows
+ // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+ // this operation.
+ long authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
// TODO(b/141025588): Make userId the first arg to be consistent with hasEnrolledBiometrics.
// Checks if biometrics can be used.
diff --git a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
index 876513f266e8..addd622eef35 100644
--- a/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricAuthenticator.aidl
@@ -48,13 +48,13 @@ interface IBiometricAuthenticator {
// startPreparedClient().
void prepareForAuthentication(boolean requireConfirmation, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication);
+ long requestId, int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int cookie);
- // Cancels authentication.
- void cancelAuthenticationFromService(IBinder token, String opPackageName);
+ // Cancels authentication for the given requestId.
+ void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId);
// Determine if HAL is loaded and ready
boolean isHardwareDetected(String opPackageName);
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 64b51183a170..2c3c8c353e8c 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -36,13 +36,14 @@ interface IBiometricService {
// Retrieve static sensor properties for all biometric sensors
List<SensorPropertiesInternal> getSensorProperties(String opPackageName);
- // Requests authentication. The service choose the appropriate biometric to use, and show
- // the corresponding BiometricDialog.
- void authenticate(IBinder token, long operationId, int userId,
+ // Requests authentication. The service chooses the appropriate biometric to use, and shows
+ // the corresponding BiometricDialog. A requestId is returned that can be used to cancel
+ // this operation.
+ long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, in PromptInfo promptInfo);
- // Cancel authentication for the given session.
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
// Checks if biometrics can be used.
int canAuthenticate(String opPackageName, int userId, int callingUserId, int authenticators);
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index 5cfba3d945cd..395c655ddf7c 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -263,12 +263,12 @@ public final class CameraExtensionCharacteristics {
@Override
public void onServiceConnected(ComponentName component, IBinder binder) {
mProxy = ICameraExtensionsProxyService.Stub.asInterface(binder);
- mInitFuture.setStatus(true);
try {
mSupportsAdvancedExtensions = mProxy.advancedExtensionsSupported();
} catch (RemoteException e) {
Log.e(TAG, "Remote IPC failed!");
}
+ mInitFuture.setStatus(true);
}
};
ctx.bindService(intent, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT |
diff --git a/core/java/android/hardware/camera2/CaptureFailure.java b/core/java/android/hardware/camera2/CaptureFailure.java
index 20ca4a338f01..032ed7e4db62 100644
--- a/core/java/android/hardware/camera2/CaptureFailure.java
+++ b/core/java/android/hardware/camera2/CaptureFailure.java
@@ -59,7 +59,7 @@ public class CaptureFailure {
private final CaptureRequest mRequest;
private final int mReason;
- private final boolean mDropped;
+ private final boolean mWasImageCaptured;
private final int mSequenceId;
private final long mFrameNumber;
private final String mErrorPhysicalCameraId;
@@ -68,10 +68,11 @@ public class CaptureFailure {
* @hide
*/
public CaptureFailure(CaptureRequest request, int reason,
- boolean dropped, int sequenceId, long frameNumber, String errorPhysicalCameraId) {
+ boolean wasImageCaptured, int sequenceId, long frameNumber,
+ String errorPhysicalCameraId) {
mRequest = request;
mReason = reason;
- mDropped = dropped;
+ mWasImageCaptured = wasImageCaptured;
mSequenceId = sequenceId;
mFrameNumber = frameNumber;
mErrorPhysicalCameraId = errorPhysicalCameraId;
@@ -141,7 +142,7 @@ public class CaptureFailure {
* @return boolean True if the image was captured, false otherwise.
*/
public boolean wasImageCaptured() {
- return !mDropped;
+ return mWasImageCaptured;
}
/**
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 8da6551f3d15..b8443fb6d14b 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -873,21 +873,19 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
@Override
public int submitBurst(List<Request> requests, IRequestCallback callback) {
int seqId = -1;
- synchronized (mInterfaceLock) {
- try {
- CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
- ArrayList<CaptureRequest> captureRequests = new ArrayList<>();
- for (Request request : requests) {
- captureRequests.add(initializeCaptureRequest(mCameraDevice, request,
- mCameraConfigMap));
- }
- seqId = mCaptureSession.captureBurstRequests(captureRequests,
- new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed to submit capture requests!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
+ try {
+ CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
+ ArrayList<CaptureRequest> captureRequests = new ArrayList<>();
+ for (Request request : requests) {
+ captureRequests.add(initializeCaptureRequest(mCameraDevice, request,
+ mCameraConfigMap));
}
+ seqId = mCaptureSession.captureBurstRequests(captureRequests,
+ new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to submit capture requests!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
return seqId;
@@ -896,18 +894,16 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
@Override
public int setRepeating(Request request, IRequestCallback callback) {
int seqId = -1;
- synchronized (mInterfaceLock) {
- try {
- CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice,
- request, mCameraConfigMap);
- CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
- seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
- new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed to enable repeating request!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ CaptureRequest repeatingRequest = initializeCaptureRequest(mCameraDevice,
+ request, mCameraConfigMap);
+ CaptureCallbackHandler captureCallback = new CaptureCallbackHandler(callback);
+ seqId = mCaptureSession.setSingleRepeatingRequest(repeatingRequest,
+ new CameraExtensionUtils.HandlerExecutor(mHandler), captureCallback);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed to enable repeating request!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
return seqId;
@@ -915,27 +911,23 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes
@Override
public void abortCaptures() {
- synchronized (mInterfaceLock) {
- try {
- mCaptureSession.abortCaptures();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed during capture abort!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ mCaptureSession.abortCaptures();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed during capture abort!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
}
@Override
public void stopRepeating() {
- synchronized (mInterfaceLock) {
- try {
- mCaptureSession.stopRepeating();
- } catch (CameraAccessException e) {
- Log.e(TAG, "Failed during repeating capture stop!");
- } catch (IllegalStateException e) {
- Log.e(TAG, "Capture session closed!");
- }
+ try {
+ mCaptureSession.stopRepeating();
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Failed during repeating capture stop!");
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "Capture session closed!");
}
}
}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index fc728a22ed5a..88649392c23c 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -104,6 +104,9 @@ public class CameraDeviceImpl extends CameraDevice
private SparseArray<CaptureCallbackHolder> mCaptureCallbackMap =
new SparseArray<CaptureCallbackHolder>();
+ /** map request IDs which have batchedOutputs to requestCount*/
+ private HashMap<Integer, Integer> mBatchOutputMap = new HashMap<>();
+
private int mRepeatingRequestId = REQUEST_ID_NONE;
// Latest repeating request list's types
private int[] mRepeatingRequestTypes;
@@ -973,6 +976,7 @@ public class CameraDeviceImpl extends CameraDevice
mConfiguredInput = new SimpleEntry<Integer, InputConfiguration>(REQUEST_ID_NONE, null);
mIdle = true;
mCaptureCallbackMap = new SparseArray<CaptureCallbackHolder>();
+ mBatchOutputMap = new HashMap<>();
mFrameNumberTracker = new FrameNumberTracker();
mCurrentSession.closeWithoutDraining();
@@ -1179,6 +1183,41 @@ public class CameraDeviceImpl extends CameraDevice
return requestTypes;
}
+ private boolean hasBatchedOutputs(List<CaptureRequest> requestList) {
+ boolean hasBatchedOutputs = true;
+ for (int i = 0; i < requestList.size(); i++) {
+ CaptureRequest request = requestList.get(i);
+ if (!request.isPartOfCRequestList()) {
+ hasBatchedOutputs = false;
+ break;
+ }
+ if (i == 0) {
+ Collection<Surface> targets = request.getTargets();
+ if (targets.size() != 2) {
+ hasBatchedOutputs = false;
+ break;
+ }
+ }
+ }
+ return hasBatchedOutputs;
+ }
+
+ private void updateTracker(int requestId, long frameNumber,
+ int requestType, CaptureResult result, boolean isPartialResult) {
+ int requestCount = 1;
+ // If the request has batchedOutputs update each frame within the batch.
+ if (mBatchOutputMap.containsKey(requestId)) {
+ requestCount = mBatchOutputMap.get(requestId);
+ for (int i = 0; i < requestCount; i++) {
+ mFrameNumberTracker.updateTracker(frameNumber - (requestCount - 1 - i),
+ result, isPartialResult, requestType);
+ }
+ } else {
+ mFrameNumberTracker.updateTracker(frameNumber, result,
+ isPartialResult, requestType);
+ }
+ }
+
private int submitCaptureRequest(List<CaptureRequest> requestList, CaptureCallback callback,
Executor executor, boolean repeating) throws CameraAccessException {
@@ -1224,6 +1263,14 @@ public class CameraDeviceImpl extends CameraDevice
request.recoverStreamIdToSurface();
}
+ // If the request has batched outputs, then store the
+ // requestCount and requestId in the map.
+ boolean hasBatchedOutputs = hasBatchedOutputs(requestList);
+ if (hasBatchedOutputs) {
+ int requestCount = requestList.size();
+ mBatchOutputMap.put(requestInfo.getRequestId(), requestCount);
+ }
+
if (callback != null) {
mCaptureCallbackMap.put(requestInfo.getRequestId(),
new CaptureCallbackHolder(
@@ -1820,7 +1867,7 @@ public class CameraDeviceImpl extends CameraDevice
final CaptureFailure failure = new CaptureFailure(
request,
reason,
- /*dropped*/ mayHaveBuffers,
+ mayHaveBuffers,
requestId,
frameNumber,
errorPhysicalCameraId);
@@ -1839,8 +1886,18 @@ public class CameraDeviceImpl extends CameraDevice
if (DEBUG) {
Log.v(TAG, String.format("got error frame %d", frameNumber));
}
- mFrameNumberTracker.updateTracker(frameNumber,
- /*error*/true, request.getRequestType());
+
+ // Update FrameNumberTracker for every frame during HFR mode.
+ if (mBatchOutputMap.containsKey(requestId)) {
+ for (int i = 0; i < mBatchOutputMap.get(requestId); i++) {
+ mFrameNumberTracker.updateTracker(frameNumber - (subsequenceId - i),
+ /*error*/true, request.getRequestType());
+ }
+ } else {
+ mFrameNumberTracker.updateTracker(frameNumber,
+ /*error*/true, request.getRequestType());
+ }
+
checkAndFireSequenceComplete();
// Dispatch the failure callback
@@ -2023,7 +2080,6 @@ public class CameraDeviceImpl extends CameraDevice
public void onResultReceived(CameraMetadataNative result,
CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
throws RemoteException {
-
int requestId = resultExtras.getRequestId();
long frameNumber = resultExtras.getFrameNumber();
@@ -2064,8 +2120,8 @@ public class CameraDeviceImpl extends CameraDevice
+ frameNumber);
}
- mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, /*result*/null,
+ isPartialResult);
return;
}
@@ -2077,8 +2133,9 @@ public class CameraDeviceImpl extends CameraDevice
+ frameNumber);
}
- mFrameNumberTracker.updateTracker(frameNumber, /*result*/null, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, /*result*/null,
+ isPartialResult);
+
return;
}
@@ -2184,9 +2241,7 @@ public class CameraDeviceImpl extends CameraDevice
Binder.restoreCallingIdentity(ident);
}
- // Collect the partials for a total result; or mark the frame as totally completed
- mFrameNumberTracker.updateTracker(frameNumber, finalResult, isPartialResult,
- requestType);
+ updateTracker(requestId, frameNumber, requestType, finalResult, isPartialResult);
// Fire onCaptureSequenceCompleted
if (!isPartialResult) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index 3b1cb94a6619..425f22c31306 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -58,7 +58,7 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
private static final class JpegParameters {
public HashSet<Long> mTimeStamps = new HashSet<>();
- public int mRotation = JPEG_DEFAULT_ROTATION; // CCW multiple of 90 degrees
+ public int mRotation = JPEG_DEFAULT_ROTATION; // CW multiple of 90 degrees
public int mQuality = JPEG_DEFAULT_QUALITY; // [0..100]
}
@@ -100,7 +100,8 @@ public class CameraExtensionJpegProcessor implements ICaptureProcessorImpl {
Integer orientation = captureBundles.get(0).captureResult.get(
CaptureResult.JPEG_ORIENTATION);
if (orientation != null) {
- ret.mRotation = orientation / 90;
+ // The jpeg encoder expects CCW rotation, convert from CW
+ ret.mRotation = (360 - (orientation % 360)) / 90;
} else {
Log.w(TAG, "No jpeg rotation set, using default: " + JPEG_DEFAULT_ROTATION);
}
diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java
index c5d37c2d0b90..0dc8f92967fb 100644
--- a/core/java/android/hardware/display/BrightnessInfo.java
+++ b/core/java/android/hardware/display/BrightnessInfo.java
@@ -60,12 +60,18 @@ public final class BrightnessInfo implements Parcelable {
/** Brightness */
public final float brightness;
+ /** Brightness after {@link DisplayPowerController} adjustments */
+ public final float adjustedBrightness;
+
/** Current minimum supported brightness. */
public final float brightnessMinimum;
/** Current maximum supported brightness. */
public final float brightnessMaximum;
+ /** Brightness values greater than this point are only used in High Brightness Mode. */
+ public final float highBrightnessTransitionPoint;
+
/**
* Current state of high brightness mode.
* Can be any of HIGH_BRIGHTNESS_MODE_* values.
@@ -73,11 +79,20 @@ public final class BrightnessInfo implements Parcelable {
public final int highBrightnessMode;
public BrightnessInfo(float brightness, float brightnessMinimum, float brightnessMaximum,
- @HighBrightnessMode int highBrightnessMode) {
+ @HighBrightnessMode int highBrightnessMode, float highBrightnessTransitionPoint) {
+ this(brightness, brightness, brightnessMinimum, brightnessMaximum, highBrightnessMode,
+ highBrightnessTransitionPoint);
+ }
+
+ public BrightnessInfo(float brightness, float adjustedBrightness, float brightnessMinimum,
+ float brightnessMaximum, @HighBrightnessMode int highBrightnessMode,
+ float highBrightnessTransitionPoint) {
this.brightness = brightness;
+ this.adjustedBrightness = adjustedBrightness;
this.brightnessMinimum = brightnessMinimum;
this.brightnessMaximum = brightnessMaximum;
this.highBrightnessMode = highBrightnessMode;
+ this.highBrightnessTransitionPoint = highBrightnessTransitionPoint;
}
/**
@@ -103,9 +118,11 @@ public final class BrightnessInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeFloat(brightness);
+ dest.writeFloat(adjustedBrightness);
dest.writeFloat(brightnessMinimum);
dest.writeFloat(brightnessMaximum);
dest.writeInt(highBrightnessMode);
+ dest.writeFloat(highBrightnessTransitionPoint);
}
public static final @android.annotation.NonNull Creator<BrightnessInfo> CREATOR =
@@ -123,9 +140,11 @@ public final class BrightnessInfo implements Parcelable {
private BrightnessInfo(Parcel source) {
brightness = source.readFloat();
+ adjustedBrightness = source.readFloat();
brightnessMinimum = source.readFloat();
brightnessMaximum = source.readFloat();
highBrightnessMode = source.readInt();
+ highBrightnessTransitionPoint = source.readFloat();
}
}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index e13a7b6eac65..fc8337ac3155 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -1257,6 +1257,23 @@ public final class DisplayManager {
*/
String KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS =
"fixed_refresh_rate_high_ambient_brightness_thresholds";
+
+ /**
+ * Key for refresh rate when the device is in high brightness mode for sunlight visility.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.integer#config_defaultRefreshRateInHbmSunlight
+ */
+ String KEY_REFRESH_RATE_IN_HBM_SUNLIGHT = "refresh_rate_in_hbm_sunlight";
+
+ /**
+ * Key for refresh rate when the device is in high brightness mode for HDR.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.integer#config_defaultRefreshRateInHbmHdr
+ */
+ String KEY_REFRESH_RATE_IN_HBM_HDR = "refresh_rate_in_hbm_hdr";
+
/**
* Key for default peak refresh rate
*
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index a9b95fce8777..6c3936569c28 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -361,6 +361,11 @@ public final class DisplayManagerGlobal {
for (int i = 0; i < numListeners; i++) {
mask |= mDisplayListeners.get(i).mEventsMask;
}
+ if (mDispatchNativeCallbacks) {
+ mask |= DisplayManager.EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED;
+ }
return mask;
}
@@ -1047,12 +1052,17 @@ public final class DisplayManagerGlobal {
private static native void nSignalNativeCallbacks(float refreshRate);
- // Called from AChoreographer via JNI.
- // Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
- private void registerNativeChoreographerForRefreshRateCallbacks() {
+ /**
+ * Called from AChoreographer via JNI.
+ * Registers AChoreographer so that refresh rate callbacks can be dispatched from DMS.
+ * Public for unit testing to be able to call this method.
+ */
+ @VisibleForTesting
+ public void registerNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
- registerCallbackIfNeededLocked();
mDispatchNativeCallbacks = true;
+ registerCallbackIfNeededLocked();
+ updateCallbackIfNeededLocked();
DisplayInfo display = getDisplayInfoLocked(Display.DEFAULT_DISPLAY);
if (display != null) {
// We need to tell AChoreographer instances the current refresh rate so that apps
@@ -1063,11 +1073,16 @@ public final class DisplayManagerGlobal {
}
}
- // Called from AChoreographer via JNI.
- // Unregisters AChoreographer from receiving refresh rate callbacks.
- private void unregisterNativeChoreographerForRefreshRateCallbacks() {
+ /**
+ * Called from AChoreographer via JNI.
+ * Unregisters AChoreographer from receiving refresh rate callbacks.
+ * Public for unit testing to be able to call this method.
+ */
+ @VisibleForTesting
+ public void unregisterNativeChoreographerForRefreshRateCallbacks() {
synchronized (mLock) {
mDispatchNativeCallbacks = false;
+ updateCallbackIfNeededLocked();
}
}
}
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 385ad2d3577f..56f81423db4e 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -58,7 +58,7 @@ import java.util.List;
public class FaceManager implements BiometricAuthenticator, BiometricFaceConstants {
private static final String TAG = "FaceManager";
- private static final boolean DEBUG = true;
+
private static final int MSG_ENROLL_RESULT = 100;
private static final int MSG_ACQUIRED = 101;
private static final int MSG_AUTHENTICATION_SUCCEEDED = 102;
@@ -207,13 +207,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
throw new IllegalArgumentException("Must supply an authentication callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "authentication already canceled");
+ return;
}
if (mService != null) {
@@ -223,17 +219,18 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
Trace.beginSection("FaceManager#authenticate");
- mService.authenticate(mToken, operationId, userId, mServiceReceiver,
- mContext.getOpPackageName(), isKeyguardBypassEnabled);
+ final long authId = mService.authenticate(mToken, operationId, userId,
+ mServiceReceiver, mContext.getOpPackageName(), isKeyguardBypassEnabled);
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
- if (callback != null) {
- // Though this may not be a hardware issue, it will cause apps to give up or
- // try again later.
- callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
- getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */));
- }
+ // Though this may not be a hardware issue, it will cause apps to give up or
+ // try again later.
+ callback.onAuthenticationError(FACE_ERROR_HW_UNAVAILABLE,
+ getErrorString(mContext, FACE_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */));
} finally {
Trace.endSection();
}
@@ -255,14 +252,14 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
- } else {
- cancel.setOnCancelListener(new OnFaceDetectionCancelListener());
}
mFaceDetectionCallback = callback;
try {
- mService.detectFace(mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ final long authId = mService.detectFace(
+ mToken, userId, mServiceReceiver, mContext.getOpPackageName());
+ cancel.setOnCancelListener(new OnFaceDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
@@ -726,23 +723,23 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
}
- private void cancelAuthentication(CryptoObject cryptoObject) {
+ private void cancelAuthentication(long requestId) {
if (mService != null) {
try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
- private void cancelFaceDetect() {
+ private void cancelFaceDetect(long requestId) {
if (mService == null) {
return;
}
try {
- mService.cancelFaceDetect(mToken, mContext.getOpPackageName());
+ mService.cancelFaceDetect(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -794,9 +791,9 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FACE_ERROR_VENDOR, but
// warn and use the default if all else fails.
- // TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
- return "";
+ return context.getString(
+ com.android.internal.R.string.face_error_vendor_unknown);
}
/**
@@ -1110,22 +1107,30 @@ public class FaceManager implements BiometricAuthenticator, BiometricFaceConstan
}
private class OnAuthenticationCancelListener implements OnCancelListener {
- private final CryptoObject mCrypto;
+ private final long mAuthRequestId;
- OnAuthenticationCancelListener(CryptoObject crypto) {
- mCrypto = crypto;
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
}
@Override
public void onCancel() {
- cancelAuthentication(mCrypto);
+ Slog.d(TAG, "Cancel face authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
private class OnFaceDetectionCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnFaceDetectionCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelFaceDetect();
+ Slog.d(TAG, "Cancel face detect requested for: " + mAuthRequestId);
+ cancelFaceDetect(mAuthRequestId);
}
}
diff --git a/core/java/android/hardware/face/IFaceService.aidl b/core/java/android/hardware/face/IFaceService.aidl
index db02a0ef2a10..e9198246dee3 100644
--- a/core/java/android/hardware/face/IFaceService.aidl
+++ b/core/java/android/hardware/face/IFaceService.aidl
@@ -44,34 +44,36 @@ interface IFaceService {
// Retrieve static sensor properties for the specified sensor
FaceSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
- // Authenticate the given sessionId with a face
- void authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
+ // Authenticate with a face. A requestId is returned that can be used to cancel this operation.
+ long authenticate(IBinder token, long operationId, int userId, IFaceServiceReceiver receiver,
String opPackageName, boolean isKeyguardBypassEnabled);
// Uses the face hardware to detect for the presence of a face, without giving details
- // about accept/reject/lockout.
- void detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
+ // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+ // operation.
+ long detectFace(IBinder token, int userId, IFaceServiceReceiver receiver, String opPackageName);
// This method prepares the service to start authenticating, but doesn't start authentication.
// This is protected by the MANAGE_BIOMETRIC signatuer permission. This method should only be
// called from BiometricService. The additional uid, pid, userId arguments should be determined
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
- void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token, long operationId,
- int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication);
+ void prepareForAuthentication(int sensorId, boolean requireConfirmation, IBinder token,
+ long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
+ String opPackageName, long requestId, int cookie,
+ boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int sensorId, int cookie);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
- // Cancel face detection
- void cancelFaceDetect(IBinder token, String opPackageName);
+ // Cancel face detection for the given requestId.
+ void cancelFaceDetect(IBinder token, String opPackageName, long requestId);
// Same as above, with extra arguments.
- void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+ void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start face enrollment
void enroll(int userId, IBinder token, in byte [] hardwareAuthToken, IFaceServiceReceiver receiver,
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 87d45b9de745..a3d595c23095 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -146,6 +146,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private CryptoObject mCryptoObject;
@Nullable private RemoveTracker mRemoveTracker;
private Handler mHandler;
+ @Nullable private float[] mEnrollStageThresholds;
/**
* Retrieves a list of properties for all fingerprint sensors on the device.
@@ -189,22 +190,30 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
private class OnAuthenticationCancelListener implements OnCancelListener {
- private android.hardware.biometrics.CryptoObject mCrypto;
+ private final long mAuthRequestId;
- public OnAuthenticationCancelListener(android.hardware.biometrics.CryptoObject crypto) {
- mCrypto = crypto;
+ OnAuthenticationCancelListener(long id) {
+ mAuthRequestId = id;
}
@Override
public void onCancel() {
- cancelAuthentication(mCrypto);
+ Slog.d(TAG, "Cancel fingerprint authentication requested for: " + mAuthRequestId);
+ cancelAuthentication(mAuthRequestId);
}
}
private class OnFingerprintDetectionCancelListener implements OnCancelListener {
+ private final long mAuthRequestId;
+
+ OnFingerprintDetectionCancelListener(long id) {
+ mAuthRequestId = id;
+ }
+
@Override
public void onCancel() {
- cancelFingerprintDetect();
+ Slog.d(TAG, "Cancel fingerprint detect requested for: " + mAuthRequestId);
+ cancelFingerprintDetect(mAuthRequestId);
}
}
@@ -552,13 +561,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
throw new IllegalArgumentException("Must supply an authentication callback");
}
- if (cancel != null) {
- if (cancel.isCanceled()) {
- Slog.w(TAG, "authentication already canceled");
- return;
- } else {
- cancel.setOnCancelListener(new OnAuthenticationCancelListener(crypto));
- }
+ if (cancel != null && cancel.isCanceled()) {
+ Slog.w(TAG, "authentication already canceled");
+ return;
}
if (mService != null) {
@@ -567,8 +572,11 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
mAuthenticationCallback = callback;
mCryptoObject = crypto;
final long operationId = crypto != null ? crypto.getOpId() : 0;
- mService.authenticate(mToken, operationId, sensorId, userId, mServiceReceiver,
- mContext.getOpPackageName());
+ final long authId = mService.authenticate(mToken, operationId, sensorId, userId,
+ mServiceReceiver, mContext.getOpPackageName());
+ if (cancel != null) {
+ cancel.setOnCancelListener(new OnAuthenticationCancelListener(authId));
+ }
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception while authenticating: ", e);
// Though this may not be a hardware issue, it will cause apps to give up or try
@@ -595,15 +603,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
if (cancel.isCanceled()) {
Slog.w(TAG, "Detection already cancelled");
return;
- } else {
- cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener());
}
mFingerprintDetectionCallback = callback;
try {
- mService.detectFingerprint(mToken, userId, mServiceReceiver,
+ final long authId = mService.detectFingerprint(mToken, userId, mServiceReceiver,
mContext.getOpPackageName());
+ cancel.setOnCancelListener(new OnFingerprintDetectionCancelListener(authId));
} catch (RemoteException e) {
Slog.w(TAG, "Remote exception when requesting finger detect", e);
}
@@ -844,26 +851,6 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
- * Checks if the specified user has enrollments in any of the specified sensors.
- * @hide
- */
- @RequiresPermission(USE_BIOMETRIC_INTERNAL)
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors) {
- if (mService == null) {
- Slog.w(TAG, "hasEnrolledTemplatesForAnySensor: no fingerprint service");
- return false;
- }
-
- try {
- return mService.hasEnrolledTemplatesForAnySensor(userId, sensors,
- mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* @hide
*/
@RequiresPermission(USE_BIOMETRIC_INTERNAL)
@@ -1320,21 +1307,21 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
}
- private void cancelAuthentication(android.hardware.biometrics.CryptoObject cryptoObject) {
+ private void cancelAuthentication(long requestId) {
if (mService != null) try {
- mService.cancelAuthentication(mToken, mContext.getOpPackageName());
+ mService.cancelAuthentication(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- private void cancelFingerprintDetect() {
+ private void cancelFingerprintDetect(long requestId) {
if (mService == null) {
return;
}
try {
- mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName());
+ mService.cancelFingerprintDetect(mToken, mContext.getOpPackageName(), requestId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1343,6 +1330,46 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
/**
* @hide
*/
+ public int getEnrollStageCount() {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+ return mEnrollStageThresholds.length + 1;
+ }
+
+ /**
+ * @hide
+ */
+ public float getEnrollStageThreshold(int index) {
+ if (mEnrollStageThresholds == null) {
+ mEnrollStageThresholds = createEnrollStageThresholds(mContext);
+ }
+
+ if (index < 0 || index > mEnrollStageThresholds.length) {
+ Slog.w(TAG, "Unsupported enroll stage index: " + index);
+ return index < 0 ? 0f : 1f;
+ }
+
+ // The implicit threshold for the final stage is always 1.
+ return index == mEnrollStageThresholds.length ? 1f : mEnrollStageThresholds[index];
+ }
+
+ @NonNull
+ private static float[] createEnrollStageThresholds(@NonNull Context context) {
+ // TODO(b/200604947): Fetch this value from FingerprintService, rather than internal config
+ final String[] enrollStageThresholdStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.config_udfps_enroll_stage_thresholds);
+
+ final float[] enrollStageThresholds = new float[enrollStageThresholdStrings.length];
+ for (int i = 0; i < enrollStageThresholds.length; i++) {
+ enrollStageThresholds[i] = Float.parseFloat(enrollStageThresholdStrings[i]);
+ }
+ return enrollStageThresholds;
+ }
+
+ /**
+ * @hide
+ */
public static String getErrorString(Context context, int errMsg, int vendorCode) {
switch (errMsg) {
case FINGERPRINT_ERROR_HW_UNAVAILABLE:
@@ -1390,9 +1417,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
// This is used as a last resort in case a vendor string is missing
// It should not happen for anything other than FINGERPRINT_ERROR_VENDOR, but
// warn and use the default if all else fails.
- // TODO(b/196639965): update string
Slog.w(TAG, "Invalid error message: " + errMsg + ", " + vendorCode);
- return "";
+ return context.getString(
+ com.android.internal.R.string.fingerprint_error_vendor_unknown);
}
/**
diff --git a/core/java/android/hardware/fingerprint/FingerprintStateListener.java b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
index 6e607a269cb2..cf914c5acd1a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintStateListener.java
+++ b/core/java/android/hardware/fingerprint/FingerprintStateListener.java
@@ -49,5 +49,10 @@ public abstract class FingerprintStateListener extends IFingerprintStateListener
* Defines behavior in response to state update
* @param newState new state of fingerprint sensor
*/
- public abstract void onStateChanged(@FingerprintStateListener.State int newState);
+ public void onStateChanged(@FingerprintStateListener.State int newState) {};
+
+ /**
+ * Invoked when enrollment state changes for the specified user
+ */
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {};
}
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 3979afe80d17..de94b2fbb5b5 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -48,15 +48,16 @@ interface IFingerprintService {
// Retrieve static sensor properties for the specified sensor
FingerprintSensorPropertiesInternal getSensorProperties(int sensorId, String opPackageName);
- // Authenticate the given sessionId with a fingerprint. This is protected by
- // USE_FINGERPRINT/USE_BIOMETRIC permission. This is effectively deprecated, since it only comes
- // through FingerprintManager now.
- void authenticate(IBinder token, long operationId, int sensorId, int userId,
+ // Authenticate with a fingerprint. This is protected by USE_FINGERPRINT/USE_BIOMETRIC
+ // permission. This is effectively deprecated, since it only comes through FingerprintManager
+ // now. A requestId is returned that can be used to cancel this operation.
+ long authenticate(IBinder token, long operationId, int sensorId, int userId,
IFingerprintServiceReceiver receiver, String opPackageName);
// Uses the fingerprint hardware to detect for the presence of a finger, without giving details
- // about accept/reject/lockout.
- void detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
+ // about accept/reject/lockout. A requestId is returned that can be used to cancel this
+ // operation.
+ long detectFingerprint(IBinder token, int userId, IFingerprintServiceReceiver receiver,
String opPackageName);
// This method prepares the service to start authenticating, but doesn't start authentication.
@@ -65,21 +66,21 @@ interface IFingerprintService {
// by BiometricService. To start authentication after the clients are ready, use
// startPreparedClient().
void prepareForAuthentication(int sensorId, IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
- boolean allowBackgroundAuthentication);
+ IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ int cookie, boolean allowBackgroundAuthentication);
// Starts authentication with the previously prepared client.
void startPreparedClient(int sensorId, int cookie);
- // Cancel authentication for the given sessionId
- void cancelAuthentication(IBinder token, String opPackageName);
+ // Cancel authentication for the given requestId.
+ void cancelAuthentication(IBinder token, String opPackageName, long requestId);
- // Cancel finger detection
- void cancelFingerprintDetect(IBinder token, String opPackageName);
+ // Cancel finger detection for the given requestId.
+ void cancelFingerprintDetect(IBinder token, String opPackageName, long requestId);
// Same as above, except this is protected by the MANAGE_BIOMETRIC signature permission. Takes
// an additional uid, pid, userid.
- void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName);
+ void cancelAuthenticationFromService(int sensorId, IBinder token, String opPackageName, long requestId);
// Start fingerprint enrollment
void enroll(IBinder token, in byte [] hardwareAuthToken, int userId, IFingerprintServiceReceiver receiver,
@@ -119,9 +120,6 @@ interface IFingerprintService {
// Determine if a user has at least one enrolled fingerprint.
boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName);
- // Determine if a user has at least one enrolled fingerprint in any of the specified sensors
- boolean hasEnrolledTemplatesForAnySensor(int userId, in List<FingerprintSensorPropertiesInternal> sensors, String opPackageName);
-
// Return the LockoutTracker status for the specified user
int getLockoutModeForUser(int sensorId, int userId);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
index 56dba7ea1d9a..1aa6fa197066 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintStateListener.aidl
@@ -24,4 +24,5 @@ import android.hardware.fingerprint.Fingerprint;
*/
oneway interface IFingerprintStateListener {
void onStateChanged(int newState);
+ void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 881e0cfb58d7..74cb42db7858 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1749,12 +1749,12 @@ public class InputMethodService extends AbstractInputMethodService {
if (config.orientation != Configuration.ORIENTATION_LANDSCAPE) {
return false;
}
- if ((mInputEditorInfo != null
- && (mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0)
+ if (mInputEditorInfo != null
+ && ((mInputEditorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN) != 0
// If app window has portrait orientation, regardless of what display orientation
// is, IME shouldn't use fullscreen-mode.
|| (mInputEditorInfo.internalImeOptions
- & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0) {
+ & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT) != 0)) {
return false;
}
return true;
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index f48375246616..0f94cbef3886 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -271,6 +271,16 @@ public final class BatteryUsageStats implements Parcelable {
}
/**
+ * Returns the names of custom power components in order, so the first name in the array
+ * corresponds to the custom componentId
+ * {@link BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID}.
+ */
+ @NonNull
+ public String[] getCustomPowerComponentNames() {
+ return mCustomPowerComponentNames;
+ }
+
+ /**
* Returns an iterator for {@link android.os.BatteryStats.HistoryItem}'s.
*/
@NonNull
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index 3d466a0bf007..c6466235e5d1 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -74,7 +74,7 @@ public final class BinderProxy implements IBinder {
private static final int MAIN_INDEX_SIZE = 1 << LOG_MAIN_INDEX_SIZE;
private static final int MAIN_INDEX_MASK = MAIN_INDEX_SIZE - 1;
// Debuggable builds will throw an AssertionError if the number of map entries exceeds:
- private static final int CRASH_AT_SIZE = 20_000;
+ private static final int CRASH_AT_SIZE = 25_000;
/**
* We next warn when we exceed this bucket size.
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index be21fea1d0df..1cceea30868c 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -17,6 +17,7 @@
package android.os;
import android.app.Activity;
+import android.app.GameManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,8 +27,6 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
@@ -37,9 +36,6 @@ import dalvik.system.VMRuntime;
import java.io.BufferedReader;
import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
@@ -88,9 +84,6 @@ public class GraphicsEnvironment {
private static final String UPDATABLE_DRIVER_ALLOWLIST_ALL = "*";
private static final String UPDATABLE_DRIVER_SPHAL_LIBRARIES_FILENAME = "sphal_libraries.txt";
- // ANGLE related properties.
- private static final String ANGLE_RULES_FILE = "a4a_rules.json";
- private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
private static final String ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE =
"android.app.action.ANGLE_FOR_ANDROID_TOAST_MESSAGE";
@@ -121,6 +114,7 @@ public class GraphicsEnvironment {
private ClassLoader mClassLoader;
private String mLibrarySearchPaths;
private String mLibraryPermittedPaths;
+ private GameManager mGameManager;
private int mAngleOptInIndex = -1;
@@ -133,6 +127,8 @@ public class GraphicsEnvironment {
final ApplicationInfo appInfoWithMetaData =
getAppInfoWithMetadata(context, pm, packageName);
+ mGameManager = context.getSystemService(GameManager.class);
+
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
@@ -151,6 +147,23 @@ public class GraphicsEnvironment {
}
/**
+ * Query to determine if the Game Mode has enabled ANGLE.
+ */
+ private boolean isAngleEnabledByGameMode(Context context, String packageName) {
+ try {
+ final boolean gameModeEnabledAngle =
+ (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
+ Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
+ return gameModeEnabledAngle;
+ } catch (SecurityException e) {
+ Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
+ + "for package: " + packageName);
+ }
+
+ return false;
+ }
+
+ /**
* Query to determine if ANGLE should be used
*/
private boolean shouldUseAngle(Context context, Bundle coreSettings,
@@ -164,21 +177,16 @@ public class GraphicsEnvironment {
Log.v(TAG, "ANGLE Developer option for '" + packageName + "' "
+ "set to: '" + devOptIn + "'");
- // We only want to use ANGLE if the app is in the allowlist, or the developer has
- // explicitly chosen something other than default driver.
- // The allowlist will be generated by the ANGLE APK at both boot time and
- // ANGLE update time. It will only include apps mentioned in the rules file.
- final boolean allowed = checkAngleAllowlist(context, coreSettings, packageName);
+ // We only want to use ANGLE if the developer has explicitly chosen something other than
+ // default driver.
final boolean requested = devOptIn.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE);
-
- if (allowed) {
- Log.v(TAG, "ANGLE allowlist includes " + packageName);
- }
if (requested) {
Log.v(TAG, "ANGLE developer option for " + packageName + ": " + devOptIn);
}
- return allowed || requested;
+ final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+
+ return requested || gameModeEnabledAngle;
}
private int getVulkanVersion(PackageManager pm) {
@@ -475,117 +483,6 @@ public class GraphicsEnvironment {
}
/**
- * Attempt to setup ANGLE with a temporary rules file.
- * True: Temporary rules file was loaded.
- * False: Temporary rules file was *not* loaded.
- */
- private boolean setupAngleWithTempRulesFile(Context context,
- String packageName,
- String paths,
- String devOptIn) {
- /**
- * We only want to load a temp rules file for:
- * - apps that are marked 'debuggable' in their manifest
- * - devices that are running a userdebug build (ro.debuggable) or can inject libraries for
- * debugging (PR_SET_DUMPABLE).
- */
- if (!isDebuggable()) {
- Log.v(TAG, "Skipping loading temporary rules file");
- return false;
- }
-
- final String angleTempRules = SystemProperties.get(ANGLE_TEMP_RULES);
-
- if (TextUtils.isEmpty(angleTempRules)) {
- Log.v(TAG, "System property '" + ANGLE_TEMP_RULES + "' is not set or is empty");
- return false;
- }
-
- Log.i(TAG, "Detected system property " + ANGLE_TEMP_RULES + ": " + angleTempRules);
-
- final File tempRulesFile = new File(angleTempRules);
- if (tempRulesFile.exists()) {
- Log.i(TAG, angleTempRules + " exists, loading file.");
- try {
- final FileInputStream stream = new FileInputStream(angleTempRules);
-
- try {
- final FileDescriptor rulesFd = stream.getFD();
- final long rulesOffset = 0;
- final long rulesLength = stream.getChannel().size();
- Log.i(TAG, "Loaded temporary ANGLE rules from " + angleTempRules);
-
- setAngleInfo(paths, packageName, devOptIn, null,
- rulesFd, rulesOffset, rulesLength);
-
- stream.close();
-
- // We successfully setup ANGLE, so return with good status
- return true;
- } catch (IOException e) {
- Log.w(TAG, "Hit IOException thrown by FileInputStream: " + e);
- }
- } catch (FileNotFoundException e) {
- Log.w(TAG, "Temp ANGLE rules file not found: " + e);
- } catch (SecurityException e) {
- Log.w(TAG, "Temp ANGLE rules file not accessible: " + e);
- }
- }
-
- return false;
- }
-
- /**
- * Attempt to setup ANGLE with a rules file loaded from the ANGLE APK.
- * True: APK rules file was loaded.
- * False: APK rules file was *not* loaded.
- */
- private boolean setupAngleRulesApk(String anglePkgName,
- ApplicationInfo angleInfo,
- PackageManager pm,
- String packageName,
- String paths,
- String devOptIn,
- String[] features) {
- // Pass the rules file to loader for ANGLE decisions
- try {
- final AssetManager angleAssets = pm.getResourcesForApplication(angleInfo).getAssets();
-
- try {
- final AssetFileDescriptor assetsFd = angleAssets.openFd(ANGLE_RULES_FILE);
-
- setAngleInfo(paths, packageName, devOptIn, features, assetsFd.getFileDescriptor(),
- assetsFd.getStartOffset(), assetsFd.getLength());
-
- assetsFd.close();
-
- return true;
- } catch (IOException e) {
- Log.w(TAG, "Failed to get AssetFileDescriptor for " + ANGLE_RULES_FILE
- + " from '" + anglePkgName + "': " + e);
- }
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to get AssetManager for '" + anglePkgName + "': " + e);
- }
-
- return false;
- }
-
- /**
- * Pull ANGLE allowlist from GlobalSettings and compare against current package
- */
- private boolean checkAngleAllowlist(Context context, Bundle bundle, String packageName) {
- final ContentResolver contentResolver = context.getContentResolver();
- final List<String> angleAllowlist =
- getGlobalSettingsString(contentResolver, bundle,
- Settings.Global.ANGLE_ALLOWLIST);
-
- if (DEBUG) Log.v(TAG, "ANGLE allowlist: " + angleAllowlist);
-
- return angleAllowlist.contains(packageName);
- }
-
- /**
* Pass ANGLE details down to trigger enable logic
*
* @param context
@@ -647,28 +544,21 @@ public class GraphicsEnvironment {
if (DEBUG) Log.v(TAG, "ANGLE package libs: " + paths);
- // If the user has set the developer option to something other than default,
- // we need to call setupAngleRulesApk() with the package name and the developer
- // option value (native/angle/other). Then later when we are actually trying to
- // load a driver, GraphicsEnv::getShouldUseAngle() has seen the package name before
- // and can confidently answer yes/no based on the previously set developer
- // option value.
- final String devOptIn = getDriverForPackage(context, bundle, packageName);
-
- if (setupAngleWithTempRulesFile(context, packageName, paths, devOptIn)) {
- // We setup ANGLE with a temp rules file, so we're done here.
- return true;
- }
-
- String[] features = getAngleEglFeatures(context, bundle);
-
- if (setupAngleRulesApk(
- anglePkgName, angleInfo, pm, packageName, paths, devOptIn, features)) {
- // ANGLE with rules is set up from the APK, hence return.
- return true;
+ // We need to call setAngleInfo() with the package name and the developer option value
+ //(native/angle/other). Then later when we are actually trying to load a driver,
+ //GraphicsEnv::getShouldUseAngle() has seen the package name before and can confidently
+ //answer yes/no based on the previously set developer option value.
+ final String devOptIn;
+ final String[] features = getAngleEglFeatures(context, bundle);
+ final boolean gameModeEnabledAngle = isAngleEnabledByGameMode(context, packageName);
+ if (gameModeEnabledAngle) {
+ devOptIn = ANGLE_GL_DRIVER_CHOICE_ANGLE;
+ } else {
+ devOptIn = getDriverForPackage(context, bundle, packageName);
}
+ setAngleInfo(paths, packageName, devOptIn, features);
- return false;
+ return true;
}
/**
@@ -956,7 +846,7 @@ public class GraphicsEnvironment {
private static native void setGpuStats(String driverPackageName, String driverVersionName,
long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion);
private static native void setAngleInfo(String path, String appPackage, String devOptIn,
- String[] features, FileDescriptor rulesFd, long rulesOffset, long rulesLength);
+ String[] features);
private static native boolean getShouldUseAngle(String packageName);
private static native boolean setInjectLayersPrSetDumpable();
diff --git a/core/java/android/os/incremental/OWNERS b/core/java/android/os/incremental/OWNERS
index 47eee6406206..363486905c93 100644
--- a/core/java/android/os/incremental/OWNERS
+++ b/core/java/android/os/incremental/OWNERS
@@ -1,6 +1,5 @@
# Bug component: 554432
alexbuy@google.com
schfan@google.com
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java
index 54905ec6eaeb..8928a423c6cb 100644
--- a/core/java/android/os/storage/StorageManagerInternal.java
+++ b/core/java/android/os/storage/StorageManagerInternal.java
@@ -18,6 +18,7 @@ package android.os.storage;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.os.IVold;
import java.util.List;
@@ -135,4 +136,19 @@ public abstract class StorageManagerInternal {
* {@link VolumeInfo#isPrimary()}
*/
public abstract List<String> getPrimaryVolumeIds();
+
+ /**
+ * Tells StorageManager that CE storage for this user has been prepared.
+ *
+ * @param userId userId for which CE storage has been prepared
+ */
+ public abstract void markCeStoragePrepared(@UserIdInt int userId);
+
+ /**
+ * Returns true when CE storage for this user has been prepared.
+ *
+ * When the user key is unlocked and CE storage has been prepared,
+ * it's ok to access and modify CE directories on volumes for this user.
+ */
+ public abstract boolean isCeStoragePrepared(@UserIdInt int userId);
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ac520e8b3dec..00abc759278f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2242,6 +2242,21 @@ public final class Settings {
public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
/**
+ * Activity Action: Show screen that lets user configure wifi tethering.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: Nothing
+ * <p>
+ * Output: Nothing
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_WIFI_TETHER_SETTING =
+ "com.android.settings.WIFI_TETHER_SETTINGS";
+
+ /**
* Broadcast to trigger notification of asking user to enable MMS.
* Need to specify {@link #EXTRA_ENABLE_MMS_DATA_REQUEST_REASON} and {@link #EXTRA_SUB_ID}.
*
@@ -11845,6 +11860,12 @@ public final class Settings {
public static final String WIFI_MIGRATION_COMPLETED = "wifi_migration_completed";
/**
+ * Whether UWB should be enabled.
+ * @hide
+ */
+ public static final String UWB_ENABLED = "uwb_enabled";
+
+ /**
* Value to specify whether network quality scores and badging should be shown in the UI.
*
* Type: int (0 for false, 1 for true)
@@ -13670,13 +13691,6 @@ public final class Settings {
"angle_gl_driver_selection_values";
/**
- * List of package names that should check ANGLE rules
- * @hide
- */
- @Readable
- public static final String ANGLE_ALLOWLIST = "angle_allowlist";
-
- /**
* Lists of ANGLE EGL features for debugging.
* Each list of features is separated by a comma, each feature in each list is separated by
* a colon.
@@ -14915,6 +14929,16 @@ public final class Settings {
"power_button_long_press";
/**
+ * Override internal R.integer.config_longPressOnPowerDurationMs. It determines the length
+ * of power button press to be considered a long press in milliseconds.
+ * Used by PhoneWindowManager.
+ * @hide
+ */
+ @Readable
+ public static final String POWER_BUTTON_LONG_PRESS_DURATION_MS =
+ "power_button_long_press_duration_ms";
+
+ /**
* Overrides internal R.integer.config_veryLongPressOnPowerBehavior.
* Allowable values detailed in frameworks/base/core/res/res/values/config.xml.
* Used by PhoneWindowManager.
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index 8e4a68e52697..40041486f6a6 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -436,7 +436,7 @@ public class StatusBarNotification implements Parcelable {
try {
ApplicationInfo ai = context.getPackageManager()
.getApplicationInfoAsUser(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES,
- getUserId());
+ getNormalizedUserId());
mContext = context.createApplicationContext(ai,
Context.CONTEXT_RESTRICTED);
} catch (PackageManager.NameNotFoundException e) {
diff --git a/core/java/android/service/translation/ITranslationService.aidl b/core/java/android/service/translation/ITranslationService.aidl
index e9dd2c3bd70b..4cc732ab5a30 100644
--- a/core/java/android/service/translation/ITranslationService.aidl
+++ b/core/java/android/service/translation/ITranslationService.aidl
@@ -24,7 +24,7 @@ import com.android.internal.os.IResultReceiver;
/**
* System-wide on-device translation service.
*
- * <p>Services requests to translate text between different languages. The primary use case for this
+ * <p>Services requests to translate data between different languages. The primary use case for this
* service is automatic translation of text and web views, when the auto Translate feature is
* enabled.
*
diff --git a/core/java/android/service/translation/TranslationService.java b/core/java/android/service/translation/TranslationService.java
index 93c006aff435..d454c393a7ce 100644
--- a/core/java/android/service/translation/TranslationService.java
+++ b/core/java/android/service/translation/TranslationService.java
@@ -48,6 +48,7 @@ import android.view.translation.TranslationManager;
import android.view.translation.TranslationRequest;
import android.view.translation.TranslationResponse;
import android.view.translation.TranslationSpec;
+import android.view.translation.Translator;
import com.android.internal.os.IResultReceiver;
@@ -81,7 +82,10 @@ public abstract class TranslationService extends Service {
* android.R.styleable#TranslationService translation-service}&gt;</code> tag.
*
* <p>Here's an example of how to use it on {@code AndroidManifest.xml}:
- * TODO: fill in doc example (check CCService/AFService).
+ * <pre> &lt;translation-service
+ * android:settingsActivity="foo.bar.SettingsActivity"
+ * . . .
+ * /&gt;</pre>
*/
public static final String SERVICE_META_DATA = "android.translation_service";
@@ -148,7 +152,6 @@ public abstract class TranslationService extends Service {
void onTranslationSuccess(@NonNull TranslationResponse response);
/**
- * TODO: implement javadoc
* @removed use {@link #onTranslationSuccess} with an error response instead.
*/
@Deprecated
@@ -225,7 +228,7 @@ public abstract class TranslationService extends Service {
* should call back with {@code false}.</p>
*
* @param translationContext the {@link TranslationContext} of the session being created.
- * @param sessionId the int id of the session.
+ * @param sessionId the id of the session.
* @param callback {@link Consumer} to notify whether the session was successfully created.
*/
// TODO(b/176464808): the session id won't be unique cross client/server process. Need to find
@@ -234,8 +237,6 @@ public abstract class TranslationService extends Service {
int sessionId, @NonNull Consumer<Boolean> callback);
/**
- * TODO: fill in javadoc.
- *
* @removed use {@link #onCreateTranslationSession(TranslationContext, int, Consumer)}
* instead.
*/
@@ -246,19 +247,16 @@ public abstract class TranslationService extends Service {
}
/**
- * TODO: fill in javadoc.
+ * Called when a translation session is finished.
+ *
+ * <p>The translation session is finished when the client calls {@link Translator#destroy()} on
+ * the corresponding translator.
*
- * @param sessionId
+ * @param sessionId id of the session that finished.
*/
public abstract void onFinishTranslationSession(int sessionId);
/**
- * TODO: fill in javadoc.
- *
- * @param request
- * @param sessionId
- * @param callback
- * @param cancellationSignal
* @removed use
* {@link #onTranslationRequest(TranslationRequest, int, CancellationSignal, Consumer)} instead.
*/
@@ -276,23 +274,29 @@ public abstract class TranslationService extends Service {
* {@link TranslationRequest#FLAG_PARTIAL_RESPONSES} was set, the service may call
* {@code callback.accept()} multiple times with partial responses.</p>
*
- * @param request
- * @param sessionId
- * @param callback
- * @param cancellationSignal
+ * @param request The translation request containing the data to be translated.
+ * @param sessionId id of the session that sent the translation request.
+ * @param cancellationSignal A {@link CancellationSignal} that notifies when a client has
+ * cancelled the operation in progress.
+ * @param callback {@link Consumer} to pass back the translation response.
*/
public abstract void onTranslationRequest(@NonNull TranslationRequest request, int sessionId,
@Nullable CancellationSignal cancellationSignal,
@NonNull Consumer<TranslationResponse> callback);
/**
- * TODO: fill in javadoc
+ * Called to request a set of {@link TranslationCapability}s that are supported by the service.
+ *
+ * <p>The set of translation capabilities are limited to those supporting the source and target
+ * {@link TranslationSpec.DataFormat}. e.g. Calling this with
+ * {@link TranslationSpec#DATA_FORMAT_TEXT} as source and target returns only capabilities that
+ * translates text to text.</p>
*
* <p>Must call {@code callback.accept} to pass back the set of translation capabilities.</p>
*
- * @param sourceFormat
- * @param targetFormat
- * @param callback
+ * @param sourceFormat data format restriction of the translation source spec.
+ * @param targetFormat data format restriction of the translation target spec.
+ * @param callback {@link Consumer} to pass back the set of translation capabilities.
*/
public abstract void onTranslationCapabilitiesRequest(
@TranslationSpec.DataFormat int sourceFormat,
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index f4770727e31f..c9a0121936dc 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -96,6 +96,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
@@ -152,6 +153,7 @@ public abstract class WallpaperService extends Service {
private static final int MSG_REQUEST_WALLPAPER_COLORS = 10050;
private static final int MSG_ZOOM = 10100;
private static final int MSG_SCALE_PREVIEW = 10110;
+ private static final int MSG_REPORT_SHOWN = 10150;
private static final List<Float> PROHIBITED_STEPS = Arrays.asList(0f, Float.POSITIVE_INFINITY,
Float.NEGATIVE_INFINITY);
@@ -527,6 +529,39 @@ public abstract class WallpaperService extends Service {
}
/**
+ * This will be called in the end of {@link #updateSurface(boolean, boolean, boolean)}.
+ * If true is returned, the engine will not report shown until rendering finished is
+ * reported. Otherwise, the engine will report shown immediately right after redraw phase
+ * in {@link #updateSurface(boolean, boolean, boolean)}.
+ *
+ * @hide
+ */
+ public boolean shouldWaitForEngineShown() {
+ return false;
+ }
+
+ /**
+ * Reports the rendering is finished, stops waiting, then invokes
+ * {@link IWallpaperEngineWrapper#reportShown()}.
+ *
+ * @hide
+ */
+ public void reportEngineShown(boolean waitForEngineShown) {
+ if (mIWallpaperEngine.mShownReported) return;
+ if (!waitForEngineShown) {
+ Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+ mCaller.removeMessages(MSG_REPORT_SHOWN);
+ mCaller.sendMessage(message);
+ } else {
+ // if we are already waiting, no need to reset the timeout.
+ if (!mCaller.hasMessages(MSG_REPORT_SHOWN)) {
+ Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
+ mCaller.sendMessageDelayed(message, TimeUnit.SECONDS.toMillis(5));
+ }
+ }
+ }
+
+ /**
* Control whether this wallpaper will receive raw touch events
* from the window manager as the user interacts with the window
* that is currently displaying the wallpaper. By default they
@@ -930,7 +965,7 @@ public abstract class WallpaperService extends Service {
void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) {
if (mDestroyed) {
- Log.w(TAG, "Ignoring updateSurface: destroyed");
+ Log.w(TAG, "Ignoring updateSurface due to destroyed");
}
boolean fixedSize = false;
@@ -1197,7 +1232,6 @@ public abstract class WallpaperService extends Service {
+ this);
onVisibilityChanged(false);
}
-
} finally {
mIsCreating = false;
mSurfaceCreated = true;
@@ -1207,7 +1241,7 @@ public abstract class WallpaperService extends Service {
processLocalColors(mPendingXOffset, mPendingXOffsetStep);
}
reposition();
- mIWallpaperEngine.reportShown();
+ reportEngineShown(shouldWaitForEngineShown());
}
} catch (RemoteException ex) {
}
@@ -2048,6 +2082,8 @@ public abstract class WallpaperService extends Service {
mShownReported = true;
try {
mConnection.engineShown(this);
+ Log.d(TAG, "Wallpaper has updated the surface:"
+ + mWallpaperManager.getWallpaperInfo());
} catch (RemoteException e) {
Log.w(TAG, "Wallpaper host disappeared", e);
return;
@@ -2201,6 +2237,9 @@ public abstract class WallpaperService extends Service {
// Connection went away, nothing to do in here.
}
} break;
+ case MSG_REPORT_SHOWN: {
+ reportShown();
+ } break;
default :
Log.w(TAG, "Unknown message type " + message.what);
}
diff --git a/core/java/android/speech/RecognitionService.java b/core/java/android/speech/RecognitionService.java
index 362ea8c87f7f..5e647a4531bc 100644
--- a/core/java/android/speech/RecognitionService.java
+++ b/core/java/android/speech/RecognitionService.java
@@ -115,7 +115,7 @@ public abstract class RecognitionService extends Service {
@NonNull AttributionSource attributionSource) {
try {
if (mCurrentCallback == null) {
- boolean preflightPermissionCheckPassed = checkPermissionForPreflight(
+ boolean preflightPermissionCheckPassed = checkPermissionForPreflightNotHardDenied(
attributionSource);
if (preflightPermissionCheckPassed) {
if (DBG) {
@@ -470,10 +470,11 @@ public abstract class RecognitionService extends Service {
return mStartedDataDelivery;
}
- private boolean checkPermissionForPreflight(AttributionSource attributionSource) {
- return PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
- Manifest.permission.RECORD_AUDIO, attributionSource)
- == PermissionChecker.PERMISSION_GRANTED;
+ private boolean checkPermissionForPreflightNotHardDenied(AttributionSource attributionSource) {
+ int result = PermissionChecker.checkPermissionForPreflight(RecognitionService.this,
+ Manifest.permission.RECORD_AUDIO, attributionSource);
+ return result == PermissionChecker.PERMISSION_GRANTED
+ || result == PermissionChecker.PERMISSION_SOFT_DENIED;
}
void finishDataDelivery() {
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1f11d10052fe..1a7ec7f99c95 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -782,7 +782,7 @@ public class TextLine {
int spanStart = runStart;
int spanLimit;
- if (mSpanned == null) {
+ if (mSpanned == null || runStart == runLimit) {
spanLimit = runLimit;
} else {
int target = after ? offset + 1 : offset;
diff --git a/core/java/android/text/method/TranslationTransformationMethod.java b/core/java/android/text/method/TranslationTransformationMethod.java
index 80387aa8d66d..43d186ee9d21 100644
--- a/core/java/android/text/method/TranslationTransformationMethod.java
+++ b/core/java/android/text/method/TranslationTransformationMethod.java
@@ -62,6 +62,13 @@ public class TranslationTransformationMethod implements TransformationMethod2 {
return mOriginalTranslationMethod;
}
+ /**
+ * Returns the {@link TextView}'s {@link ViewTranslationResponse}.
+ */
+ public ViewTranslationResponse getViewTranslationResponse() {
+ return mTranslationResponse;
+ }
+
@Override
public CharSequence getTransformation(CharSequence source, View view) {
if (!mAllowLengthChanges) {
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 802163617b3b..a7ecf1f2a81d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -814,9 +814,10 @@ interface IWindowManager
* @param displayId The display associated with the window context
* @param options A bundle used to pass window-related options and choose the right DisplayArea
*
- * @return {@code true} if the WindowContext is attached to the DisplayArea successfully.
+ * @return the DisplayArea's {@link android.app.res.Configuration} if the WindowContext is
+ * attached to the DisplayArea successfully. {@code null}, otherwise.
*/
- boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
+ Configuration attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
in Bundle options);
/**
diff --git a/core/java/android/view/ScrollCaptureResponse.java b/core/java/android/view/ScrollCaptureResponse.java
index 8808827b248a..758f9ab935cf 100644
--- a/core/java/android/view/ScrollCaptureResponse.java
+++ b/core/java/android/view/ScrollCaptureResponse.java
@@ -53,6 +53,10 @@ public class ScrollCaptureResponse implements Parcelable {
@Nullable
private String mWindowTitle = null;
+ /** The package which owns the window. */
+ @Nullable
+ private String mPackageName = null;
+
/** Carries additional logging and debugging information when enabled. */
@NonNull
@DataClass.PluralOf("message")
@@ -77,7 +81,7 @@ public class ScrollCaptureResponse implements Parcelable {
- // Code below generated by codegen v1.0.22.
+ // Code below generated by codegen v1.0.23.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
@@ -97,6 +101,7 @@ public class ScrollCaptureResponse implements Parcelable {
@Nullable Rect windowBounds,
@Nullable Rect boundsInWindow,
@Nullable String windowTitle,
+ @Nullable String packageName,
@NonNull ArrayList<String> messages) {
this.mDescription = description;
com.android.internal.util.AnnotationValidations.validate(
@@ -105,6 +110,7 @@ public class ScrollCaptureResponse implements Parcelable {
this.mWindowBounds = windowBounds;
this.mBoundsInWindow = boundsInWindow;
this.mWindowTitle = windowTitle;
+ this.mPackageName = packageName;
this.mMessages = messages;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessages);
@@ -153,6 +159,14 @@ public class ScrollCaptureResponse implements Parcelable {
}
/**
+ * The package name of the process the window is owned by.
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* Carries additional logging and debugging information when enabled.
*/
@DataClass.Generated.Member
@@ -172,6 +186,7 @@ public class ScrollCaptureResponse implements Parcelable {
"windowBounds = " + mWindowBounds + ", " +
"boundsInWindow = " + mBoundsInWindow + ", " +
"windowTitle = " + mWindowTitle + ", " +
+ "packageName = " + mPackageName + ", " +
"messages = " + mMessages +
" }";
}
@@ -187,12 +202,14 @@ public class ScrollCaptureResponse implements Parcelable {
if (mWindowBounds != null) flg |= 0x4;
if (mBoundsInWindow != null) flg |= 0x8;
if (mWindowTitle != null) flg |= 0x10;
+ if (mPackageName != null) flg |= 0x20;
dest.writeByte(flg);
dest.writeString(mDescription);
if (mConnection != null) dest.writeStrongInterface(mConnection);
if (mWindowBounds != null) dest.writeTypedObject(mWindowBounds, flags);
if (mBoundsInWindow != null) dest.writeTypedObject(mBoundsInWindow, flags);
if (mWindowTitle != null) dest.writeString(mWindowTitle);
+ if (mPackageName != null) dest.writeString(mPackageName);
dest.writeStringList(mMessages);
}
@@ -213,6 +230,7 @@ public class ScrollCaptureResponse implements Parcelable {
Rect windowBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
Rect boundsInWindow = (flg & 0x8) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR);
String windowTitle = (flg & 0x10) == 0 ? null : in.readString();
+ String packageName = (flg & 0x20) == 0 ? null : in.readString();
ArrayList<String> messages = new ArrayList<>();
in.readStringList(messages);
@@ -223,6 +241,7 @@ public class ScrollCaptureResponse implements Parcelable {
this.mWindowBounds = windowBounds;
this.mBoundsInWindow = boundsInWindow;
this.mWindowTitle = windowTitle;
+ this.mPackageName = packageName;
this.mMessages = messages;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mMessages);
@@ -256,6 +275,7 @@ public class ScrollCaptureResponse implements Parcelable {
private @Nullable Rect mWindowBounds;
private @Nullable Rect mBoundsInWindow;
private @Nullable String mWindowTitle;
+ private @Nullable String mPackageName;
private @NonNull ArrayList<String> mMessages;
private long mBuilderFieldsSet = 0L;
@@ -319,12 +339,23 @@ public class ScrollCaptureResponse implements Parcelable {
}
/**
+ * The package name of the process the window is owned by.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x20;
+ mPackageName = value;
+ return this;
+ }
+
+ /**
* Carries additional logging and debugging information when enabled.
*/
@DataClass.Generated.Member
public @NonNull Builder setMessages(@NonNull ArrayList<String> value) {
checkNotUsed();
- mBuilderFieldsSet |= 0x20;
+ mBuilderFieldsSet |= 0x40;
mMessages = value;
return this;
}
@@ -340,7 +371,7 @@ public class ScrollCaptureResponse implements Parcelable {
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull ScrollCaptureResponse build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x40; // Mark builder used
+ mBuilderFieldsSet |= 0x80; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mDescription = "";
@@ -358,6 +389,9 @@ public class ScrollCaptureResponse implements Parcelable {
mWindowTitle = null;
}
if ((mBuilderFieldsSet & 0x20) == 0) {
+ mPackageName = null;
+ }
+ if ((mBuilderFieldsSet & 0x40) == 0) {
mMessages = new ArrayList<>();
}
ScrollCaptureResponse o = new ScrollCaptureResponse(
@@ -366,12 +400,13 @@ public class ScrollCaptureResponse implements Parcelable {
mWindowBounds,
mBoundsInWindow,
mWindowTitle,
+ mPackageName,
mMessages);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x40) != 0) {
+ if ((mBuilderFieldsSet & 0x80) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -379,10 +414,10 @@ public class ScrollCaptureResponse implements Parcelable {
}
@DataClass.Generated(
- time = 1614833185795L,
- codegenVersion = "1.0.22",
+ time = 1628630366187L,
+ codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/ScrollCaptureResponse.java",
- inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
+ inputSignatures = "private @android.annotation.NonNull java.lang.String mDescription\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.MaySetToNull android.view.IScrollCaptureConnection mConnection\nprivate @android.annotation.Nullable android.graphics.Rect mWindowBounds\nprivate @android.annotation.Nullable android.graphics.Rect mBoundsInWindow\nprivate @android.annotation.Nullable java.lang.String mWindowTitle\nprivate @android.annotation.Nullable java.lang.String mPackageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"message\") java.util.ArrayList<java.lang.String> mMessages\npublic boolean isConnected()\npublic void close()\nclass ScrollCaptureResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genGetters=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ff2d2eb3d334..fa7330fb84eb 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -98,6 +98,7 @@ public class Surface implements Parcelable {
private static native int nativeSetFrameRate(
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
+ private static native void nativeDestroy(long nativeObject);
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@@ -339,6 +340,9 @@ public class Surface implements Parcelable {
*/
@UnsupportedAppUsage
public void destroy() {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ }
release();
}
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index c1956e45653b..4b8b607de089 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -903,7 +903,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
mSurfaceAlpha = 1f;
synchronized (mSurfaceControlLock) {
- mSurface.release();
+ mSurface.destroy();
if (mBlastBufferQueue != null) {
mBlastBufferQueue.destroy();
mBlastBufferQueue = null;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f4223fb467f5..cf12955787b1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -20830,13 +20830,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
mPrivateFlags &= ~PFLAG_SCROLL_CONTAINER_ADDED;
}
+ notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
+
mAttachInfo = null;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchDetachedFromWindow();
}
notifyEnterOrExitForAutoFillIfNeeded(false);
- notifyAppearedOrDisappearedForContentCaptureIfNeeded(false);
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 3550a31f9038..0ecd76f82a8a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2836,8 +2836,13 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ final boolean surfaceControlChanged =
+ (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
+ == RELAYOUT_RES_SURFACE_CHANGED;
+
if (mSurfaceControl.isValid()) {
- updateOpacity(mWindowAttributes, dragResizing);
+ updateOpacity(mWindowAttributes, dragResizing,
+ surfaceControlChanged /*forceUpdate */);
}
if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
@@ -2872,9 +2877,7 @@ public final class ViewRootImpl implements ViewParent,
// RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
// SurfaceControl.
surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
- || (relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
- == RELAYOUT_RES_SURFACE_CHANGED)
- && mSurface.isValid();
+ || surfaceControlChanged) && mSurface.isValid();
if (surfaceReplaced) {
mSurfaceSequenceId++;
}
@@ -7824,7 +7827,8 @@ public final class ViewRootImpl implements ViewParent,
return relayoutResult;
}
- private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing) {
+ private void updateOpacity(WindowManager.LayoutParams params, boolean dragResizing,
+ boolean forceUpdate) {
boolean opaque = false;
if (!PixelFormat.formatHasAlpha(params.format)
@@ -7840,7 +7844,7 @@ public final class ViewRootImpl implements ViewParent,
opaque = true;
}
- if (mIsSurfaceOpaque == opaque) {
+ if (!forceUpdate && mIsSurfaceOpaque == opaque) {
return;
}
@@ -9493,6 +9497,7 @@ public final class ViewRootImpl implements ViewParent,
ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
response.setWindowTitle(getTitle().toString());
+ response.setPackageName(mContext.getPackageName());
StringWriter writer = new StringWriter();
IndentingPrintWriter pw = new IndentingPrintWriter(writer);
diff --git a/core/java/android/view/animation/TranslateAnimation.java b/core/java/android/view/animation/TranslateAnimation.java
index ec55a0273999..3365c70b5b34 100644
--- a/core/java/android/view/animation/TranslateAnimation.java
+++ b/core/java/android/view/animation/TranslateAnimation.java
@@ -57,6 +57,9 @@ public class TranslateAnimation extends Animation {
/** @hide */
protected float mToYDelta;
+ private int mWidth;
+ private int mParentWidth;
+
/**
* Constructor used when a TranslateAnimation is loaded from a resource.
*
@@ -179,5 +182,60 @@ public class TranslateAnimation extends Animation {
mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth);
mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight);
mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight);
+
+ mWidth = width;
+ mParentWidth = parentWidth;
+ }
+
+ /**
+ * Checks whether or not the translation is exclusively an x axis translation.
+ *
+ * @hide
+ */
+ public boolean isXAxisTransition() {
+ return mFromXDelta - mToXDelta != 0 && mFromYDelta - mToYDelta == 0;
+ }
+
+ /**
+ * Checks whether or not the translation is a full width x axis slide in or out translation.
+ *
+ * @hide
+ */
+ public boolean isFullWidthTranslate() {
+ boolean isXAxisSlideTransition =
+ isSlideInLeft() || isSlideOutRight() || isSlideInRight() || isSlideOutLeft();
+ return mWidth == mParentWidth && isXAxisSlideTransition;
+ }
+
+ private boolean isSlideInLeft() {
+ boolean startsOutOfParentOnLeft = mFromXDelta <= -mWidth;
+ return startsOutOfParentOnLeft && endsXEnclosedWithinParent();
+ }
+
+ private boolean isSlideOutRight() {
+ boolean endOutOfParentOnRight = mToXDelta >= mParentWidth;
+ return startsXEnclosedWithinParent() && endOutOfParentOnRight;
+ }
+
+ private boolean isSlideInRight() {
+ boolean startsOutOfParentOnRight = mFromXDelta >= mParentWidth;
+ return startsOutOfParentOnRight && endsXEnclosedWithinParent();
+ }
+
+ private boolean isSlideOutLeft() {
+ boolean endOutOfParentOnLeft = mToXDelta <= -mWidth;
+ return startsXEnclosedWithinParent() && endOutOfParentOnLeft;
+ }
+
+ private boolean endsXEnclosedWithinParent() {
+ return mWidth <= mParentWidth
+ && mToXDelta + mWidth <= mParentWidth
+ && mToXDelta >= 0;
+ }
+
+ private boolean startsXEnclosedWithinParent() {
+ return mWidth <= mParentWidth
+ && mFromXDelta + mWidth <= mParentWidth
+ && mFromXDelta >= 0;
}
}
diff --git a/core/java/android/view/translation/TranslationCapability.java b/core/java/android/view/translation/TranslationCapability.java
index 65b749add1b2..b7e13dda9ff6 100644
--- a/core/java/android/view/translation/TranslationCapability.java
+++ b/core/java/android/view/translation/TranslationCapability.java
@@ -40,15 +40,18 @@ import java.util.function.Consumer;
public final class TranslationCapability implements Parcelable {
/**
- * TODO: fill in javadoc
+ * The translation service supports translation between the source and target specs, and it is
+ * ready to be downloaded onto the device.
*/
public static final @ModelState int STATE_AVAILABLE_TO_DOWNLOAD = 1;
/**
- * TODO: fill in javadoc
+ * The translation service supports translation between the source and target specs, and it is
+ * being downloaded onto the device currently.
*/
public static final @ModelState int STATE_DOWNLOADING = 2;
/**
- * TODO: fill in javadoc
+ * The translation service supports translation between the source and target specs, and it is
+ * downloaded and ready to use on device.
*/
public static final @ModelState int STATE_ON_DEVICE = 3;
/**
@@ -305,7 +308,7 @@ public final class TranslationCapability implements Parcelable {
};
@DataClass.Generated(
- time = 1624307114468L,
+ time = 1629158466039L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/translation/TranslationCapability.java",
inputSignatures = "public static final @android.view.translation.TranslationCapability.ModelState int STATE_AVAILABLE_TO_DOWNLOAD\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_DOWNLOADING\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_ON_DEVICE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_NOT_AVAILABLE\npublic static final @android.view.translation.TranslationCapability.ModelState int STATE_REMOVED_AND_AVAILABLE\nprivate final @android.view.translation.TranslationCapability.ModelState int mState\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mSourceSpec\nprivate final @android.annotation.NonNull android.view.translation.TranslationSpec mTargetSpec\nprivate final boolean mUiTranslationEnabled\nprivate final @android.view.translation.TranslationContext.TranslationFlag int mSupportedTranslationFlags\nclass TranslationCapability extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstDefs=true, genToString=true, genConstructor=false)")
diff --git a/core/java/android/view/translation/TranslationRequest.java b/core/java/android/view/translation/TranslationRequest.java
index df4836ebde46..0d41851ca704 100644
--- a/core/java/android/view/translation/TranslationRequest.java
+++ b/core/java/android/view/translation/TranslationRequest.java
@@ -39,12 +39,16 @@ public final class TranslationRequest implements Parcelable {
public static final @RequestFlags int FLAG_TRANSLATION_RESULT = 0x1;
/**
* Indicates this request wants to receive the dictionary result.
- * TODO: describe the structure of the result.
+ *
+ * <p>See {@link TranslationResponseValue#EXTRA_DEFINITIONS} for more detail on the structure
+ * of the returned data.
*/
public static final @RequestFlags int FLAG_DICTIONARY_RESULT = 0x2;
/**
* Indicates this request wants to receive the transliteration result.
- * TODO: describe the structure of the result.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
+ * See {@link TranslationResponseValue#getTransliteration()}.
*/
public static final @RequestFlags int FLAG_TRANSLITERATION_RESULT = 0x4;
/**
@@ -327,7 +331,8 @@ public final class TranslationRequest implements Parcelable {
return this;
}
- /** @see #setTranslationRequestValues
+ /**
+ * @see #setTranslationRequestValues
* @removed
*/
@DataClass.Generated.Member
@@ -352,7 +357,8 @@ public final class TranslationRequest implements Parcelable {
return this;
}
- /** @see #setViewTranslationRequests
+ /**
+ * @see #setViewTranslationRequests
* @removed
*/
@DataClass.Generated.Member
@@ -394,7 +400,7 @@ public final class TranslationRequest implements Parcelable {
}
@DataClass.Generated(
- time = 1620429997487L,
+ time = 1629159107226L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/translation/TranslationRequest.java",
inputSignatures = "public static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_DICTIONARY_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_TRANSLITERATION_RESULT\npublic static final @android.view.translation.TranslationRequest.RequestFlags int FLAG_PARTIAL_RESPONSES\nprivate final @android.view.translation.TranslationRequest.RequestFlags int mFlags\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"translationRequestValue\") java.util.List<android.view.translation.TranslationRequestValue> mTranslationRequestValues\nprivate final @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"viewTranslationRequest\") java.util.List<android.view.translation.ViewTranslationRequest> mViewTranslationRequests\nprivate static int defaultFlags()\nprivate static java.util.List<android.view.translation.TranslationRequestValue> defaultTranslationRequestValues()\nprivate static java.util.List<android.view.translation.ViewTranslationRequest> defaultViewTranslationRequests()\nclass TranslationRequest extends java.lang.Object implements [android.os.Parcelable]\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genBuilder=true)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addTranslationRequestValue(android.view.translation.TranslationRequestValue)\npublic abstract @java.lang.Deprecated android.view.translation.TranslationRequest.Builder addViewTranslationRequest(android.view.translation.ViewTranslationRequest)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/view/translation/TranslationResponseValue.java b/core/java/android/view/translation/TranslationResponseValue.java
index a24dbc3ae4f8..9dff2d56322b 100644
--- a/core/java/android/view/translation/TranslationResponseValue.java
+++ b/core/java/android/view/translation/TranslationResponseValue.java
@@ -93,9 +93,11 @@ public final class TranslationResponseValue implements Parcelable {
@NonNull
private final Bundle mExtras;
+ // TODO: Add example of transliteration.
/**
* The transliteration result of the translated text.
- * TODO: Describe the result structure.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
*/
@Nullable
private final CharSequence mTransliteration;
@@ -223,7 +225,8 @@ public final class TranslationResponseValue implements Parcelable {
/**
* The transliteration result of the translated text.
- * TODO: Describe the result structure.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
*/
@DataClass.Generated.Member
public @Nullable CharSequence getTransliteration() {
@@ -407,7 +410,8 @@ public final class TranslationResponseValue implements Parcelable {
/**
* The transliteration result of the translated text.
- * TODO: Describe the result structure.
+ *
+ * <p>This returns a CharSequence representation of the transliteration of the translated text.
*/
@DataClass.Generated.Member
public @NonNull Builder setTransliteration(@NonNull CharSequence value) {
@@ -448,7 +452,7 @@ public final class TranslationResponseValue implements Parcelable {
}
@DataClass.Generated(
- time = 1622133051937L,
+ time = 1631057245846L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/view/translation/TranslationResponseValue.java",
inputSignatures = "public static final int STATUS_SUCCESS\npublic static final int STATUS_ERROR\npublic static final java.lang.String EXTRA_DEFINITIONS\nprivate final @android.view.translation.TranslationResponseValue.Status int mStatusCode\nprivate final @android.annotation.Nullable java.lang.CharSequence mText\nprivate final @android.annotation.NonNull android.os.Bundle mExtras\nprivate final @android.annotation.Nullable java.lang.CharSequence mTransliteration\npublic static @android.annotation.NonNull android.view.translation.TranslationResponseValue forError()\nprivate static java.lang.CharSequence defaultText()\nprivate static android.os.Bundle defaultExtras()\nprivate boolean extrasEquals(android.os.Bundle)\nprivate static java.lang.CharSequence defaultTransliteration()\nclass TranslationResponseValue extends java.lang.Object implements [android.os.Parcelable]\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)\nclass BaseBuilder extends java.lang.Object implements []")
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index a8335918cc84..d078c2cfbfd1 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -356,7 +356,11 @@ public class UiTranslationController {
}
for (int i = 0; i < translatedResult.size(); i++) {
final AutofillId autofillId = new AutofillId(translatedResult.keyAt(i));
- final View view = mViews.get(autofillId).get();
+ final WeakReference<View> viewRef = mViews.get(autofillId);
+ if (viewRef == null) {
+ continue;
+ }
+ final View view = viewRef.get();
if (view == null) {
Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
+ " may be gone.");
@@ -416,22 +420,30 @@ public class UiTranslationController {
Log.w(TAG, "No AutofillId is set in ViewTranslationResponse");
continue;
}
- final View view = mViews.get(autofillId).get();
+ final WeakReference<View> viewRef = mViews.get(autofillId);
+ if (viewRef == null) {
+ continue;
+ }
+ final View view = viewRef.get();
if (view == null) {
Log.w(TAG, "onTranslationCompleted: the view for autofill id " + autofillId
+ " may be gone.");
continue;
}
mActivity.runOnUiThread(() -> {
+ ViewTranslationCallback callback = view.getViewTranslationCallback();
if (view.getViewTranslationResponse() != null
&& view.getViewTranslationResponse().equals(response)) {
- if (DEBUG) {
- Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
- + ". Ignoring.");
+ if (callback instanceof TextViewTranslationCallback) {
+ if (((TextViewTranslationCallback) callback).isShowingTranslation()) {
+ if (DEBUG) {
+ Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId
+ + ". Ignoring.");
+ }
+ return;
+ }
}
- return;
}
- ViewTranslationCallback callback = view.getViewTranslationCallback();
if (callback == null) {
if (view instanceof TextView) {
// developer doesn't provide their override, we set the default TextView
@@ -590,9 +602,8 @@ public class UiTranslationController {
final View rootView = roots.get(rootNum).getView();
if (rootView instanceof ViewGroup) {
findViewsTraversalByAutofillIds((ViewGroup) rootView, sourceViewIds);
- } else {
- addViewIfNeeded(sourceViewIds, rootView);
}
+ addViewIfNeeded(sourceViewIds, rootView);
}
}
@@ -603,9 +614,8 @@ public class UiTranslationController {
final View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup) {
findViewsTraversalByAutofillIds((ViewGroup) child, sourceViewIds);
- } else {
- addViewIfNeeded(sourceViewIds, child);
}
+ addViewIfNeeded(sourceViewIds, child);
}
}
diff --git a/core/java/android/view/translation/UiTranslationManager.java b/core/java/android/view/translation/UiTranslationManager.java
index b9ed32ce248b..3012e9344a1b 100644
--- a/core/java/android/view/translation/UiTranslationManager.java
+++ b/core/java/android/view/translation/UiTranslationManager.java
@@ -33,6 +33,7 @@ import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.view.autofill.AutofillId;
+import android.widget.TextView;
import com.android.internal.annotations.GuardedBy;
@@ -42,11 +43,50 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
-// TODO(b/178044703): Describe what UI Translation is.
/**
- * The {@link UiTranslationManager} class provides ways for apps to use the ui translation
+ * <p>The {@link UiTranslationManager} class provides ways for apps to use the ui translation
* function in framework.
+ *
+ * <p> The UI translation provides ways for apps to support inline translation for the views. For
+ * example the system supports text translation for {@link TextView}. To support UI translation for
+ * your views, you should override the following methods to provide the content to be translated
+ * and deal with the translated result. Here is an example for {@link TextView}-like views:
+ *
+ * <pre><code>
+ * public class MyTextView extends View {
+ * public MyTextView(...) {
+ * // implements how to show the translated result in your View in
+ * // ViewTranslationCallback and set it by setViewTranslationCallback()
+ * setViewTranslationCallback(new MyViewTranslationCallback());
+ * }
+ *
+ * public void onCreateViewTranslationRequest(int[] supportedFormats,
+ * Consumer<ViewTranslationRequest> requestsCollector) {
+ * // collect the information that needs to be translated
+ * ViewTranslationRequest.Builder requestBuilder =
+ * new ViewTranslationRequest.Builder(getAutofillId());
+ * requestBuilder.setValue(ViewTranslationRequest.ID_TEXT,
+ * TranslationRequestValue.forText(etText()));
+ * requestsCollector.accept(requestBuilder.build());
+ * }
+ *
+ * public void onProvideContentCaptureStructure(
+ * ViewStructure structure, int flags) {
+ * // set ViewTranslationResponse
+ * super.onViewTranslationResponse(response);
+ * }
+ * }
+ * </code></pre>
+ *
+ * <p>If your view provides its own virtual hierarchy (for example, if it's a browser that draws the
+ * HTML using {@link android.graphics.Canvas} or native libraries in a different render process),
+ * you must override {@link View#onCreateVirtualViewTranslationRequests(long[], int[], Consumer)} to
+ * provide the content to be translated and implement
+ * {@link View#onVirtualViewTranslationResponses(android.util.LongSparseArray)} for the translated
+ * result. You also need to implement {@link android.view.translation.ViewTranslationCallback} to
+ * handle the translated information show or hide in your {@link View}.
*/
public final class UiTranslationManager {
@@ -248,14 +288,14 @@ public final class UiTranslationManager {
}
}
- // TODO(b/178044703): Fix the View API link when it becomes public.
/**
* Register for notifications of UI Translation state changes on the foreground activity. This
* is available to the owning application itself and also the current input method.
* <p>
* The application whose UI is being translated can use this to customize the UI Translation
* behavior in ways that aren't made easy by methods like
- * View#onCreateTranslationRequest().
+ * {@link View#onCreateViewTranslationRequest(int[], Consumer)}.
+ *
* <p>
* Input methods can use this to offer complementary features to UI Translation; for example,
* enabling outgoing message translation when the system is translating incoming messages in a
diff --git a/core/java/android/view/translation/ViewTranslationCallback.java b/core/java/android/view/translation/ViewTranslationCallback.java
index 6efd621c4caa..66c028b48dc6 100644
--- a/core/java/android/view/translation/ViewTranslationCallback.java
+++ b/core/java/android/view/translation/ViewTranslationCallback.java
@@ -19,9 +19,17 @@ package android.view.translation;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.view.View;
+import android.view.contentcapture.ContentCaptureSession;
/**
- * Callback for handling the translated information show or hide in the {@link View}.
+ * <p> Callback for handling the translated information show or hide in the {@link View}.
+ *
+ * <p> When the platform intelligence starts translation of an app's ui, the system will call
+ * {@link View#dispatchCreateViewTranslationRequest} to collect the {@link ViewTranslationRequest}s
+ * for translation purpose by traversing the hierarchy then send to translation service. After
+ * receiving the {@link ViewTranslationResponse}, the system will call
+ * {@link ViewTranslationCallback#onShowTranslation(View)} to show the translated information for
+ * the {@link View}.
*/
@UiThread
public interface ViewTranslationCallback {
@@ -33,13 +41,24 @@ public interface ViewTranslationCallback {
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onVirtualViewTranslationResponses}.
*
+ * <p> NOTE: It is possible the user changes text that causes a new
+ * {@link ViewTranslationResponse} returns to show the new translation. If you cache the
+ * {@link ViewTranslationResponse} here, you should remember to keep the cached value up
+ * to date.
+ *
+ * <p> NOTE: For TextView implementation, {@link ContentCaptureSession#notifyViewTextChanged}
+ * shouldn't be called with the translated text, simply calling setText() here will trigger the
+ * method. You should either override {@code View#onProvideContentCaptureStructure()} to report
+ * the original text instead of the translated text or use a different approach to display the
+ * translated text.
+ *
* See {@link View#onViewTranslationResponse} for how to get the translated information.
*
* @return {@code true} if the View handles showing the translation.
*/
boolean onShowTranslation(@NonNull View view);
/**
- * Called when the user wants to show the original text instead of the translated text. This
+ * Called when user wants to view the original content instead of the translated content. This
* method will not be called before {@link View#onViewTranslationResponse} or
* {@link View#onViewTranslationResponse}.
*
@@ -47,7 +66,8 @@ public interface ViewTranslationCallback {
*/
boolean onHideTranslation(@NonNull View view);
/**
- * Called when the user finish the Ui translation and no longer to show the translated text.
+ * Called when the translation state is no longer needed. It should restore the original content
+ * and clear all saved states.
*
* @return {@code true} if the View handles clearing the translation.
*/
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 91fc5a56d979..fe5eb085dc5c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -740,6 +740,16 @@ public class RemoteViews implements Parcelable, Filter {
}
@Override
+ public UserHandle getUser() {
+ return mContextForResources.getUser();
+ }
+
+ @Override
+ public int getUserId() {
+ return mContextForResources.getUserId();
+ }
+
+ @Override
public boolean isRestricted() {
// Override isRestricted and direct to resource's implementation. The isRestricted is
// used for determining the risky resources loading, e.g. fonts, thus direct to context
diff --git a/core/java/android/widget/TextViewTranslationCallback.java b/core/java/android/widget/TextViewTranslationCallback.java
index 9d60009031f9..4a78f3ee6fac 100644
--- a/core/java/android/widget/TextViewTranslationCallback.java
+++ b/core/java/android/widget/TextViewTranslationCallback.java
@@ -64,13 +64,24 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
*/
@Override
public boolean onShowTranslation(@NonNull View view) {
+ if (mIsShowingTranslation) {
+ if (DEBUG) {
+ Log.d(TAG, view + " is already showing translated text.");
+ }
+ return false;
+ }
ViewTranslationResponse response = view.getViewTranslationResponse();
if (response == null) {
Log.e(TAG, "onShowTranslation() shouldn't be called before "
+ "onViewTranslationResponse().");
return false;
}
- if (mTranslationTransformation == null) {
+ // It is possible user changes text and new translation response returns, system should
+ // update the translation response to keep the result up to date.
+ // Because TextView.setTransformationMethod() will skip the same TransformationMethod
+ // instance, we should create a new one to let new translation can work.
+ if (mTranslationTransformation == null
+ || !response.equals(mTranslationTransformation.getViewTranslationResponse())) {
TransformationMethod originalTranslationMethod =
((TextView) view).getTransformationMethod();
mTranslationTransformation = new TranslationTransformationMethod(response,
@@ -147,7 +158,7 @@ public class TextViewTranslationCallback implements ViewTranslationCallback {
return true;
}
- boolean isShowingTranslation() {
+ public boolean isShowingTranslation() {
return mIsShowingTranslation;
}
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index 8b8dba89ea67..69bc1b5f7763 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -88,4 +88,9 @@ oneway interface ITaskOrganizer {
* user has pressed back on the root activity of a task controlled by the task organizer.
*/
void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
+
+ /**
+ * Called when the IME has drawn on the organized task.
+ */
+ void onImeDrawnOnTask(int taskId);
}
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index c7c91cdd0941..d8723a821a22 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -144,6 +144,10 @@ public class TaskOrganizer extends WindowOrganizer {
@BinderThread
public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
+ /** @hide */
+ @BinderThread
+ public void onImeDrawnOnTask(int taskId) {}
+
/**
* Creates a persistent root task in WM for a particular windowing-mode.
* @param displayId The display to create the root task on.
@@ -287,6 +291,11 @@ public class TaskOrganizer extends WindowOrganizer {
public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) {
mExecutor.execute(() -> TaskOrganizer.this.onBackPressedOnTaskRoot(info));
}
+
+ @Override
+ public void onImeDrawnOnTask(int taskId) {
+ mExecutor.execute(() -> TaskOrganizer.this.onImeDrawnOnTask(taskId));
+ }
};
private ITaskOrganizerController getController() {
diff --git a/core/java/android/window/WindowContext.java b/core/java/android/window/WindowContext.java
index 901625b0732c..6d0a6bd559ae 100644
--- a/core/java/android/window/WindowContext.java
+++ b/core/java/android/window/WindowContext.java
@@ -26,7 +26,6 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Configuration;
import android.os.Bundle;
-import android.os.IBinder;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
@@ -67,7 +66,7 @@ public class WindowContext extends ContextWrapper {
mType = type;
mOptions = options;
mWindowManager = createWindowContextWindowManager(this);
- IBinder token = getWindowContextToken();
+ WindowTokenClient token = (WindowTokenClient) getWindowContextToken();
mController = new WindowContextController(token);
Reference.reachabilityFence(this);
diff --git a/core/java/android/window/WindowContextController.java b/core/java/android/window/WindowContextController.java
index d84f571931fd..505b45008663 100644
--- a/core/java/android/window/WindowContextController.java
+++ b/core/java/android/window/WindowContextController.java
@@ -19,6 +19,7 @@ package android.window;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -46,7 +47,7 @@ public class WindowContextController {
@VisibleForTesting
public boolean mAttachedToDisplayArea;
@NonNull
- private final IBinder mToken;
+ private final WindowTokenClient mToken;
/**
* Window Context Controller constructor
@@ -54,14 +55,13 @@ public class WindowContextController {
* @param token The token used to attach to a window manager node. It is usually from
* {@link Context#getWindowContextToken()}.
*/
- public WindowContextController(@NonNull IBinder token) {
- mToken = token;
- mWms = WindowManagerGlobal.getWindowManagerService();
+ public WindowContextController(@NonNull WindowTokenClient token) {
+ this(token, WindowManagerGlobal.getWindowManagerService());
}
/** Used for test only. DO NOT USE it in production code. */
@VisibleForTesting
- public WindowContextController(@NonNull IBinder token, IWindowManager mockWms) {
+ public WindowContextController(@NonNull WindowTokenClient token, IWindowManager mockWms) {
mToken = token;
mWms = mockWms;
}
@@ -81,8 +81,14 @@ public class WindowContextController {
+ "a DisplayArea once.");
}
try {
- mAttachedToDisplayArea = mWms.attachWindowContextToDisplayArea(mToken, type, displayId,
- options);
+ final Configuration configuration = mWms.attachWindowContextToDisplayArea(mToken, type,
+ displayId, options);
+ if (configuration != null) {
+ mAttachedToDisplayArea = true;
+ // Send the DisplayArea's configuration to WindowContext directly instead of
+ // waiting for dispatching from WMS.
+ mToken.onConfigurationChanged(configuration, displayId);
+ }
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6abf5575d353..4dcd2e74a53f 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -24,6 +24,8 @@ import android.content.res.Configuration;
import android.os.Bundle;
import android.os.IBinder;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.ref.WeakReference;
/**
@@ -33,7 +35,7 @@ import java.lang.ref.WeakReference;
* {@link Context#getWindowContextToken() the token of non-Activity UI Contexts}.
*
* @see WindowContext
- * @see android.view.IWindowManager#registerWindowContextListener(IBinder, int, int, Bundle)
+ * @see android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int, Bundle)
*
* @hide
*/
@@ -50,8 +52,8 @@ public class WindowTokenClient extends IWindowToken.Stub {
* Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient}
* can only attach one {@link Context}.
* <p>This method must be called before invoking
- * {@link android.view.IWindowManager#registerWindowContextListener(IBinder, int, int,
- * Bundle, boolean)}.<p/>
+ * {@link android.view.IWindowManager#attachWindowContextToDisplayArea(IBinder, int, int,
+ * Bundle)}.<p/>
*
* @param context context to be attached
* @throws IllegalStateException if attached context has already existed.
@@ -63,6 +65,13 @@ public class WindowTokenClient extends IWindowToken.Stub {
mContextRef = new WeakReference<>(context);
}
+ /**
+ * Called when {@link Configuration} updates from the server side receive.
+ *
+ * @param newConfig the updated {@link Configuration}
+ * @param newDisplayId the updated {@link android.view.Display} ID
+ */
+ @VisibleForTesting
@Override
public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
final Context context = mContextRef.get();
diff --git a/core/java/com/android/internal/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java
index 84354d90b5f3..ec224e50eb8d 100644
--- a/core/java/com/android/internal/app/SuspendedAppActivity.java
+++ b/core/java/com/android/internal/app/SuspendedAppActivity.java
@@ -72,17 +72,19 @@ public class SuspendedAppActivity extends AlertActivity
private Resources mSuspendingAppResources;
private SuspendDialogInfo mSuppliedDialogInfo;
private Bundle mOptions;
- private BroadcastReceiver mUnsuspendReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver mSuspendModifiedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_PACKAGES_UNSUSPENDED.equals(intent.getAction())) {
- final String[] unsuspended = intent.getStringArrayExtra(
+ if (Intent.ACTION_PACKAGES_SUSPENSION_CHANGED.equals(intent.getAction())) {
+ // Suspension conditions were modified, dismiss any related visible dialogs.
+ final String[] modified = intent.getStringArrayExtra(
Intent.EXTRA_CHANGED_PACKAGE_LIST);
- if (ArrayUtils.contains(unsuspended, mSuspendedPackage)) {
+ if (ArrayUtils.contains(modified, mSuspendedPackage)) {
if (!isFinishing()) {
- Slog.w(TAG, "Package " + mSuspendedPackage
- + " got unsuspended while the dialog was visible. Finishing.");
+ Slog.w(TAG, "Package " + mSuspendedPackage + " has modified"
+ + " suspension conditions while dialog was visible. Finishing.");
SuspendedAppActivity.this.finish();
+ // TODO (b/198201994): reload the suspend dialog to show most relevant info
}
}
}
@@ -245,15 +247,16 @@ public class SuspendedAppActivity extends AlertActivity
setupAlert();
- final IntentFilter unsuspendFilter = new IntentFilter(Intent.ACTION_PACKAGES_UNSUSPENDED);
- registerReceiverAsUser(mUnsuspendReceiver, UserHandle.of(mUserId), unsuspendFilter, null,
- null);
+ final IntentFilter suspendModifiedFilter =
+ new IntentFilter(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED);
+ registerReceiverAsUser(mSuspendModifiedReceiver, UserHandle.of(mUserId),
+ suspendModifiedFilter, null, null);
}
@Override
protected void onDestroy() {
super.onDestroy();
- unregisterReceiver(mUnsuspendReceiver);
+ unregisterReceiver(mSuspendModifiedReceiver);
}
private void requestDismissKeyguardIfNeeded(CharSequence dismissMessage) {
diff --git a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
index ce75f45d0897..068b882eb4f7 100644
--- a/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
+++ b/core/java/com/android/internal/app/chooser/SelectableTargetInfo.java
@@ -165,7 +165,7 @@ public final class SelectableTargetInfo implements ChooserTargetInfo {
// Now fetch app icon and raster with no badging even in work profile
Bitmap appIcon = mSelectableTargetInfoCommunicator.makePresentationGetter(info)
- .getIconBitmap(android.os.Process.myUserHandle());
+ .getIconBitmap(mContext.getUser());
// Raster target drawable with appIcon as a badge
SimpleIconFactory sif = SimpleIconFactory.obtain(mContext);
diff --git a/core/java/com/android/internal/infra/OWNERS b/core/java/com/android/internal/infra/OWNERS
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/core/java/com/android/internal/infra/OWNERS
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 8e7fae7aa061..d12c870d2591 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -50,6 +50,7 @@ import com.android.internal.util.FrameworkStatsLog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.TimeUnit;
/**
* A class that allows the app to get the frame metrics from HardwareRendererObserver.
@@ -103,6 +104,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
private boolean mCancelled = false;
private FrameTrackerListener mListener;
private boolean mTracingStarted = false;
+ private Runnable mWaitForFinishTimedOut;
private static class JankInfo {
long frameVsyncId;
@@ -263,10 +265,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
if (mListener != null) {
mListener.onCujEvents(mSession, ACTION_SESSION_END);
}
+ // We don't remove observer here,
+ // will remove it when all the frame metrics in this duration are called back.
+ // See onFrameMetricsAvailable for the logic of removing the observer.
+ // Waiting at most 10 seconds for all callbacks to finish.
+ mWaitForFinishTimedOut = () -> {
+ Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
+ finish(mJankInfos.size() - 1);
+ };
+ mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
}
- // We don't remove observer here,
- // will remove it when all the frame metrics in this duration are called back.
- // See onFrameMetricsAvailable for the logic of removing the observer.
}
/**
@@ -396,7 +404,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
private void finish(int indexOnOrAfterEnd) {
-
+ mHandler.removeCallbacks(mWaitForFinishTimedOut);
+ mWaitForFinishTimedOut = null;
mMetricsFinalized = true;
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
@@ -481,7 +490,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
}
if (DEBUG) {
- Log.i(TAG, "FrameTracker: CUJ=" + mSession.getName()
+ Log.i(TAG, "finish: CUJ=" + mSession.getName()
+ " (" + mBeginVsyncId + "," + mEndVsyncId + ")"
+ " totalFrames=" + totalFramesCount
+ " missedAppFrames=" + missedAppFramesCount
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index aabcd7f82ac7..610cd7339001 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -103,7 +103,7 @@ public class InteractionJankMonitor {
private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName();
private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
- private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5L);
+ private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
private static final String SETTINGS_ENABLED_KEY = "enabled";
private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 630851d126cc..0f1c6f3a150a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -707,6 +707,10 @@ public class BatteryStatsImpl extends BatteryStats {
* Mapping isolated uids to the actual owning app uid.
*/
final SparseIntArray mIsolatedUids = new SparseIntArray();
+ /**
+ * Internal reference count of isolated uids.
+ */
+ final SparseIntArray mIsolatedUidRefCounts = new SparseIntArray();
/**
* The statistics we have collected organized by uids.
@@ -3897,6 +3901,7 @@ public class BatteryStatsImpl extends BatteryStats {
public void addIsolatedUidLocked(int isolatedUid, int appUid,
long elapsedRealtimeMs, long uptimeMs) {
mIsolatedUids.put(isolatedUid, appUid);
+ mIsolatedUidRefCounts.put(isolatedUid, 1);
final Uid u = getUidStatsLocked(appUid, elapsedRealtimeMs, uptimeMs);
u.addIsolatedUid(isolatedUid);
}
@@ -3915,19 +3920,51 @@ public class BatteryStatsImpl extends BatteryStats {
}
/**
- * This should only be called after the cpu times have been read.
+ * Isolated uid should only be removed after all wakelocks associated with the uid are stopped
+ * and the cpu time-in-state has been read one last time for the uid.
+ *
* @see #scheduleRemoveIsolatedUidLocked(int, int)
+ *
+ * @return true if the isolated uid is actually removed.
*/
@GuardedBy("this")
- public void removeIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs, long uptimeMs) {
+ public boolean maybeRemoveIsolatedUidLocked(int isolatedUid, long elapsedRealtimeMs,
+ long uptimeMs) {
+ final int refCount = mIsolatedUidRefCounts.get(isolatedUid, 0) - 1;
+ if (refCount > 0) {
+ // Isolated uid is still being tracked
+ mIsolatedUidRefCounts.put(isolatedUid, refCount);
+ return false;
+ }
+
final int idx = mIsolatedUids.indexOfKey(isolatedUid);
if (idx >= 0) {
final int ownerUid = mIsolatedUids.valueAt(idx);
final Uid u = getUidStatsLocked(ownerUid, elapsedRealtimeMs, uptimeMs);
u.removeIsolatedUid(isolatedUid);
mIsolatedUids.removeAt(idx);
+ mIsolatedUidRefCounts.delete(isolatedUid);
+ } else {
+ Slog.w(TAG, "Attempted to remove untracked isolated uid (" + isolatedUid + ")");
}
mPendingRemovedUids.add(new UidToRemove(isolatedUid, elapsedRealtimeMs));
+
+ return true;
+ }
+
+ /**
+ * Increment the ref count for an isolated uid.
+ * call #maybeRemoveIsolatedUidLocked to decrement.
+ */
+ public void incrementIsolatedUidRefCount(int uid) {
+ final int refCount = mIsolatedUidRefCounts.get(uid, 0);
+ if (refCount <= 0) {
+ // Uid is not mapped or referenced
+ Slog.w(TAG,
+ "Attempted to increment ref counted of untracked isolated uid (" + uid + ")");
+ return;
+ }
+ mIsolatedUidRefCounts.put(uid, refCount + 1);
}
public int mapUid(int uid) {
@@ -4287,7 +4324,7 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteStartWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
int type, boolean unimportantForLogging, long elapsedRealtimeMs, long uptimeMs) {
- uid = mapUid(uid);
+ final int mappedUid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
// Only care about partial wake locks, since full wake locks
// will be canceled when the user puts the screen to sleep.
@@ -4297,9 +4334,9 @@ public class BatteryStatsImpl extends BatteryStats {
}
if (mRecordAllHistory) {
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_START, historyName,
- uid, 0)) {
+ mappedUid, 0)) {
addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_WAKE_LOCK_START, historyName, uid);
+ HistoryItem.EVENT_WAKE_LOCK_START, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
@@ -4308,7 +4345,7 @@ public class BatteryStatsImpl extends BatteryStats {
+ Integer.toHexString(mHistoryCur.states));
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
- mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
mWakeLockImportant = !unimportantForLogging;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
} else if (!mWakeLockImportant && !unimportantForLogging
@@ -4318,14 +4355,19 @@ public class BatteryStatsImpl extends BatteryStats {
mHistoryLastWritten.wakelockTag = null;
mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
mHistoryCur.wakelockTag.string = mInitialAcquireWakeName = historyName;
- mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = uid;
+ mHistoryCur.wakelockTag.uid = mInitialAcquireWakeUid = mappedUid;
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
mWakeLockImportant = true;
}
mWakeLockNesting++;
}
- if (uid >= 0) {
+ if (mappedUid >= 0) {
+ if (mappedUid != uid) {
+ // Prevent the isolated uid mapping from being removed while the wakelock is
+ // being held.
+ incrementIsolatedUidRefCount(uid);
+ }
if (mOnBatteryScreenOffTimeBase.isRunning()) {
// We only update the cpu time when a wake lock is acquired if the screen is off.
// If the screen is on, we don't distribute the power amongst partial wakelocks.
@@ -4335,7 +4377,7 @@ public class BatteryStatsImpl extends BatteryStats {
requestWakelockCpuUpdate();
}
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
.noteStartWakeLocked(pid, name, type, elapsedRealtimeMs);
if (wc != null) {
@@ -4343,8 +4385,8 @@ public class BatteryStatsImpl extends BatteryStats {
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
} else {
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
- null, getPowerManagerWakeLockLevel(type), name,
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ mappedUid, null, getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__ACQUIRE);
}
}
@@ -4358,7 +4400,7 @@ public class BatteryStatsImpl extends BatteryStats {
public void noteStopWakeLocked(int uid, int pid, WorkChain wc, String name, String historyName,
int type, long elapsedRealtimeMs, long uptimeMs) {
- uid = mapUid(uid);
+ final int mappedUid = mapUid(uid);
if (type == WAKE_TYPE_PARTIAL) {
mWakeLockNesting--;
if (mRecordAllHistory) {
@@ -4366,9 +4408,9 @@ public class BatteryStatsImpl extends BatteryStats {
historyName = name;
}
if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
- uid, 0)) {
+ mappedUid, 0)) {
addHistoryEventLocked(elapsedRealtimeMs, uptimeMs,
- HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, uid);
+ HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName, mappedUid);
}
}
if (mWakeLockNesting == 0) {
@@ -4380,7 +4422,7 @@ public class BatteryStatsImpl extends BatteryStats {
addHistoryRecordLocked(elapsedRealtimeMs, uptimeMs);
}
}
- if (uid >= 0) {
+ if (mappedUid >= 0) {
if (mOnBatteryScreenOffTimeBase.isRunning()) {
if (DEBUG_ENERGY_CPU) {
Slog.d(TAG, "Updating cpu time because of -wake_lock");
@@ -4388,17 +4430,22 @@ public class BatteryStatsImpl extends BatteryStats {
requestWakelockCpuUpdate();
}
- getUidStatsLocked(uid, elapsedRealtimeMs, uptimeMs)
+ getUidStatsLocked(mappedUid, elapsedRealtimeMs, uptimeMs)
.noteStopWakeLocked(pid, name, type, elapsedRealtimeMs);
if (wc != null) {
FrameworkStatsLog.write(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, wc.getUids(),
wc.getTags(), getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
} else {
- FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED, uid,
- null, getPowerManagerWakeLockLevel(type), name,
+ FrameworkStatsLog.write_non_chained(FrameworkStatsLog.WAKELOCK_STATE_CHANGED,
+ mappedUid, null, getPowerManagerWakeLockLevel(type), name,
FrameworkStatsLog.WAKELOCK_STATE_CHANGED__STATE__RELEASE);
}
+
+ if (mappedUid != uid) {
+ // Decrement the ref count for the isolated uid and delete the mapping if uneeded.
+ maybeRemoveIsolatedUidLocked(uid, elapsedRealtimeMs, uptimeMs);
+ }
}
}
@@ -8571,7 +8618,7 @@ public class BatteryStatsImpl extends BatteryStats {
* inactive so can be dropped.
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public boolean reset(long uptimeUs, long realtimeUs) {
+ public boolean reset(long uptimeUs, long realtimeUs, int resetReason) {
boolean active = false;
mOnBatteryBackgroundTimeBase.init(uptimeUs, realtimeUs);
@@ -8641,7 +8688,11 @@ public class BatteryStatsImpl extends BatteryStats {
resetIfNotNull(mBluetoothControllerActivity, false, realtimeUs);
resetIfNotNull(mModemControllerActivity, false, realtimeUs);
- MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+ if (resetReason == RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE) {
+ mUidMeasuredEnergyStats = null;
+ } else {
+ MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+ }
resetIfNotNull(mUserCpuTime, false, realtimeUs);
resetIfNotNull(mSystemCpuTime, false, realtimeUs);
@@ -11324,7 +11375,7 @@ public class BatteryStatsImpl extends BatteryStats {
mNumConnectivityChange = 0;
for (int i=0; i<mUidStats.size(); i++) {
- if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs)) {
+ if (mUidStats.valueAt(i).reset(uptimeUs, elapsedRealtimeUs, resetReason)) {
mUidStats.valueAt(i).detachFromTimeBase();
mUidStats.remove(mUidStats.keyAt(i));
i--;
@@ -16761,6 +16812,15 @@ public class BatteryStatsImpl extends BatteryStats {
pw.print("UIDs removed since the later of device start or stats reset: ");
pw.println(mNumUidsRemoved);
+ pw.println("Currently mapped isolated uids:");
+ final int numIsolatedUids = mIsolatedUids.size();
+ for (int i = 0; i < numIsolatedUids; i++) {
+ final int isolatedUid = mIsolatedUids.keyAt(i);
+ final int ownerUid = mIsolatedUids.valueAt(i);
+ final int refCount = mIsolatedUidRefCounts.get(isolatedUid);
+ pw.println(" " + isolatedUid + "->" + ownerUid + " (ref count = " + refCount + ")");
+ }
+
pw.println();
dumpConstantsLocked(pw);
diff --git a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
index 00385793b62b..980aec196079 100644
--- a/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
+++ b/core/java/com/android/internal/os/BatteryUsageStatsProvider.java
@@ -29,6 +29,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -234,8 +235,9 @@ public class BatteryUsageStatsProvider {
final boolean includePowerModels = (query.getFlags()
& BatteryUsageStatsQuery.FLAG_BATTERY_USAGE_STATS_INCLUDE_POWER_MODELS) != 0;
+ final String[] customEnergyConsumerNames = mStats.getCustomEnergyConsumerNames();
final BatteryUsageStats.Builder builder = new BatteryUsageStats.Builder(
- mStats.getCustomEnergyConsumerNames(), includePowerModels);
+ customEnergyConsumerNames, includePowerModels);
if (mBatteryUsageStatsStore == null) {
Log.e(TAG, "BatteryUsageStatsStore is unavailable");
return builder.build();
@@ -247,7 +249,14 @@ public class BatteryUsageStatsProvider {
final BatteryUsageStats snapshot =
mBatteryUsageStatsStore.loadBatteryUsageStats(timestamp);
if (snapshot != null) {
- builder.add(snapshot);
+ if (Arrays.equals(snapshot.getCustomPowerComponentNames(),
+ customEnergyConsumerNames)) {
+ builder.add(snapshot);
+ } else {
+ Log.w(TAG, "Ignoring older BatteryUsageStats snapshot, which has different "
+ + "custom power components: "
+ + Arrays.toString(snapshot.getCustomPowerComponentNames()));
+ }
}
}
}
diff --git a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
index 9b51a8ef6410..bb307a0d29d8 100644
--- a/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
+++ b/core/java/com/android/internal/os/CustomMeasuredPowerCalculator.java
@@ -21,13 +21,18 @@ import android.os.BatteryStats;
import android.os.BatteryUsageStats;
import android.os.BatteryUsageStatsQuery;
import android.os.UidBatteryConsumer;
+import android.util.Slog;
import android.util.SparseArray;
+import java.util.Arrays;
+
/**
* Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
* {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
*/
public class CustomMeasuredPowerCalculator extends PowerCalculator {
+ private static final String TAG = "CustomMeasuredPowerCalc";
+
public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
}
@@ -76,9 +81,9 @@ public class CustomMeasuredPowerCalculator extends PowerCalculator {
if (totalPowerMah == null) {
newTotalPowerMah = new double[customMeasuredPowerMah.length];
} else if (totalPowerMah.length != customMeasuredPowerMah.length) {
- newTotalPowerMah = new double[customMeasuredPowerMah.length];
- System.arraycopy(totalPowerMah, 0, newTotalPowerMah, 0,
- customMeasuredPowerMah.length);
+ Slog.wtf(TAG, "Number of custom energy components is not the same for all apps: "
+ + totalPowerMah.length + ", " + customMeasuredPowerMah.length);
+ newTotalPowerMah = Arrays.copyOf(totalPowerMah, customMeasuredPowerMah.length);
} else {
newTotalPowerMah = totalPowerMah;
}
diff --git a/core/java/com/android/internal/os/WifiPowerCalculator.java b/core/java/com/android/internal/os/WifiPowerCalculator.java
index 776a70545df4..3915b0e01e7f 100644
--- a/core/java/com/android/internal/os/WifiPowerCalculator.java
+++ b/core/java/com/android/internal/os/WifiPowerCalculator.java
@@ -215,6 +215,9 @@ public class WifiPowerCalculator extends PowerCalculator {
+ "ms tx=" + txTime + "ms power=" + formatCharge(
powerDurationAndTraffic.powerMah));
}
+ } else {
+ powerDurationAndTraffic.durationMs = 0;
+ powerDurationAndTraffic.powerMah = 0;
}
} else {
final long wifiRunningTime = u.getWifiRunningTime(rawRealtimeUs, statsType) / 1000;
diff --git a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
index b321ac08912b..a09c8236b47d 100644
--- a/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
+++ b/core/java/com/android/internal/policy/PhoneFallbackEventHandler.java
@@ -124,7 +124,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
- sendCloseSystemWindows();
mContext.startActivity(intent);
} catch (ActivityNotFoundException e) {
startCallActivity();
@@ -147,7 +146,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
dispatcher.performedLongPress(event);
if (isUserSetupComplete()) {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
// Broadcast an intent that the Camera button was longpressed
Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -178,7 +176,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- sendCloseSystemWindows();
getSearchManager().stopSearch();
mContext.startActivity(intent);
// Only clear this if we successfully start the
@@ -272,7 +269,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
@UnsupportedAppUsage
void startCallActivity() {
- sendCloseSystemWindows();
Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
@@ -319,10 +315,6 @@ public class PhoneFallbackEventHandler implements FallbackEventHandler {
return mMediaSessionManager;
}
- void sendCloseSystemWindows() {
- PhoneWindow.sendCloseSystemWindows(mContext, null);
- }
-
private void handleVolumeKeyEvent(KeyEvent keyEvent) {
getMediaSessionManager().dispatchVolumeKeyEventAsSystemService(keyEvent,
AudioManager.USE_DEFAULT_STREAM_TYPE);
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 10f14b42ae42..ed6415d749a3 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -148,7 +148,7 @@ oneway interface IStatusBar
*/
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId,
- String opPackageName, long operationId, int multiSensorConfig);
+ long operationId, String opPackageName, long requestId, int multiSensorConfig);
/**
* Used to notify the authentication dialog that a biometric has been authenticated.
*/
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index e7d6d6cf8936..b3499db94c88 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -110,7 +110,8 @@ interface IStatusBarService
// Used to show the authentication dialog (Biometrics, Device Credential)
void showAuthenticationDialog(in PromptInfo promptInfo, IBiometricSysuiReceiver sysuiReceiver,
in int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId, int multiSensorConfig);
+ int userId, long operationId, String opPackageName, long requestId,
+ int multiSensorConfig);
// Used to notify the authentication dialog that a biometric has been authenticated
void onBiometricAuthenticated();
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 8b3c1337c0c8..7a712e50e6a8 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -291,10 +291,10 @@ public class ContrastColorUtil {
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
- * @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
- * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param other the color to ensure contrast against. Assumed to be lighter than {@code color}
+ * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * @return a color with the same hue as {@code color}, potentially darkened to meet the
* contrast ratio.
*/
public static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
@@ -331,7 +331,7 @@ public class ContrastColorUtil {
* @param color the color to start searching from.
* @param backgroundColor the color to ensure contrast against.
* @param minRatio the minimum contrast ratio required.
- * @return the same color as {@param color} with potentially modified alpha to meet contrast
+ * @return the same color as {@code color} with potentially modified alpha to meet contrast
*/
public static int findAlphaToMeetContrast(int color, int backgroundColor, double minRatio) {
int fg = color;
@@ -361,10 +361,10 @@ public class ContrastColorUtil {
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
- * @param other the color to ensure contrast against. Assumed to be darker than {@param color}
- * @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
+ * @param other the color to ensure contrast against. Assumed to be darker than {@code color}
+ * @param findFg if true, we assume {@code color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
- * @return a color with the same hue as {@param color}, potentially darkened to meet the
+ * @return a color with the same hue as {@code color}, potentially lightened to meet the
* contrast ratio.
*/
public static int findContrastColorAgainstDark(int color, int other, boolean findFg,
@@ -393,7 +393,8 @@ public class ContrastColorUtil {
low = l;
}
}
- return findFg ? fg : bg;
+ hsl[2] = high;
+ return ColorUtilsFromCompat.HSLToColor(hsl);
}
public static int ensureTextContrastOnBlack(int color) {
@@ -452,7 +453,7 @@ public class ContrastColorUtil {
}
/**
- * Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
+ * Resolves {@code color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
*/
public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
if (color == Notification.COLOR_DEFAULT) {
diff --git a/core/java/com/android/internal/util/OWNERS b/core/java/com/android/internal/util/OWNERS
index 100a605dc175..2b7f8b2749fd 100644
--- a/core/java/com/android/internal/util/OWNERS
+++ b/core/java/com/android/internal/util/OWNERS
@@ -4,4 +4,3 @@ per-file *Notification* = file:/services/core/java/com/android/server/notificati
per-file *ContrastColor* = file:/services/core/java/com/android/server/notification/OWNERS
per-file Protocol* = etancohen@google.com, lorenzo@google.com
per-file State* = jchalard@google.com, lorenzo@google.com, satk@google.com
-per-file DataClass* = eugenesusla@google.com \ No newline at end of file
diff --git a/core/java/com/android/internal/util/function/pooled/OWNERS b/core/java/com/android/internal/util/function/pooled/OWNERS
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/core/java/com/android/internal/util/function/pooled/OWNERS
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 498505cd46ff..cd1bbb6bc6fe 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
@@ -1272,6 +1273,14 @@ public class LockPatternUtils {
}
/**
+ * Whether the user is not allowed to set any credentials via PASSWORD_QUALITY_MANAGED.
+ */
+ public boolean isCredentialsDisabledForUser(int userId) {
+ return getDevicePolicyManager().getPasswordQuality(/* admin= */ null, userId)
+ == PASSWORD_QUALITY_MANAGED;
+ }
+
+ /**
* @see StrongAuthTracker#isTrustAllowedForUser
*/
public boolean isTrustAllowedForUser(int userId) {
diff --git a/core/java/com/android/internal/widget/NotificationActionListLayout.java b/core/java/com/android/internal/widget/NotificationActionListLayout.java
index 0c2d2a9bcf41..3191e23a9883 100644
--- a/core/java/com/android/internal/widget/NotificationActionListLayout.java
+++ b/core/java/com/android/internal/widget/NotificationActionListLayout.java
@@ -53,6 +53,8 @@ public class NotificationActionListLayout extends LinearLayout {
private int mEmphasizedHeight;
private int mRegularHeight;
@DimenRes private int mCollapsibleIndentDimen = R.dimen.notification_actions_padding_start;
+ int mNumNotGoneChildren;
+ int mNumPriorityChildren;
public NotificationActionListLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -76,15 +78,14 @@ public class NotificationActionListLayout extends LinearLayout {
&& ((EmphasizedNotificationButton) actionView).isPriority();
}
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- final int N = getChildCount();
+ private void countAndRebuildMeasureOrder() {
+ final int numChildren = getChildCount();
int textViews = 0;
int otherViews = 0;
- int notGoneChildren = 0;
- int priorityChildren = 0;
+ mNumNotGoneChildren = 0;
+ mNumPriorityChildren = 0;
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numChildren; i++) {
View c = getChildAt(i);
if (c instanceof TextView) {
textViews++;
@@ -92,9 +93,9 @@ public class NotificationActionListLayout extends LinearLayout {
otherViews++;
}
if (c.getVisibility() != GONE) {
- notGoneChildren++;
+ mNumNotGoneChildren++;
if (isPriority(c)) {
- priorityChildren++;
+ mNumPriorityChildren++;
}
}
}
@@ -119,17 +120,20 @@ public class NotificationActionListLayout extends LinearLayout {
if (needRebuild) {
rebuildMeasureOrder(textViews, otherViews);
}
+ }
+ private int measureAndGetUsedWidth(int widthMeasureSpec, int heightMeasureSpec, int innerWidth,
+ boolean collapsePriorityActions) {
+ final int numChildren = getChildCount();
final boolean constrained =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED;
-
- final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
final int otherSize = mMeasureOrderOther.size();
int usedWidth = 0;
+ int maxPriorityWidth = 0;
int measuredChildren = 0;
int measuredPriorityChildren = 0;
- for (int i = 0; i < N; i++) {
+ for (int i = 0; i < numChildren; i++) {
// Measure shortest children first. To avoid measuring twice, we approximate by looking
// at the text length.
final boolean isPriority;
@@ -154,12 +158,20 @@ public class NotificationActionListLayout extends LinearLayout {
// measure in the order of (approx.) size, a large view can still take more than its
// share if the others are small.
int availableWidth = innerWidth - usedWidth;
- int unmeasuredChildren = notGoneChildren - measuredChildren;
+ int unmeasuredChildren = mNumNotGoneChildren - measuredChildren;
int maxWidthForChild = availableWidth / unmeasuredChildren;
- if (isPriority) {
+ if (isPriority && collapsePriorityActions) {
+ // Collapsing the actions to just the width required to show the icon.
+ if (maxPriorityWidth == 0) {
+ maxPriorityWidth = getResources().getDimensionPixelSize(
+ R.dimen.notification_actions_collapsed_priority_width);
+ }
+ maxWidthForChild = maxPriorityWidth + lp.leftMargin + lp.rightMargin;
+ } else if (isPriority) {
// Priority children get a larger maximum share of the total space:
// maximum priority share = (nPriority + 1) / (MAX + 1)
- int unmeasuredPriorityChildren = priorityChildren - measuredPriorityChildren;
+ int unmeasuredPriorityChildren = mNumPriorityChildren
+ - measuredPriorityChildren;
int unmeasuredOtherChildren = unmeasuredChildren - unmeasuredPriorityChildren;
int widthReservedForOtherChildren = innerWidth * unmeasuredOtherChildren
/ (Notification.MAX_ACTION_BUTTONS + 1);
@@ -187,6 +199,19 @@ public class NotificationActionListLayout extends LinearLayout {
} else {
mExtraStartPadding = 0;
}
+ return usedWidth;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ countAndRebuildMeasureOrder();
+ final int innerWidth = MeasureSpec.getSize(widthMeasureSpec) - mPaddingLeft - mPaddingRight;
+ int usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ false /* collapsePriorityButtons */);
+ if (mNumPriorityChildren != 0 && usedWidth >= innerWidth) {
+ usedWidth = measureAndGetUsedWidth(widthMeasureSpec, heightMeasureSpec, innerWidth,
+ true /* collapsePriorityButtons */);
+ }
mTotalWidth = usedWidth + mPaddingRight + mPaddingLeft + mExtraStartPadding;
setMeasuredDimension(resolveSize(getSuggestedMinimumWidth(), widthMeasureSpec),
diff --git a/core/java/com/android/server/OWNERS b/core/java/com/android/server/OWNERS
index 554e27890476..81bad6a4ba69 100644
--- a/core/java/com/android/server/OWNERS
+++ b/core/java/com/android/server/OWNERS
@@ -1 +1 @@
-per-file SystemConfig.java = toddke@google.com,patb@google.com
+per-file SystemConfig.java = patb@google.com
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 78787e52dbaa..eef705420173 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -3,7 +3,7 @@ per-file *Camera*,*camera* = cychen@google.com, epeev@google.com, etalvala@googl
per-file *Camera*,*camera* = shuzhenwang@google.com, zhijunhe@google.com
# Connectivity
-per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
+per-file android_net_* = jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
# CPU
per-file *Cpu* = file:/core/java/com/android/internal/os/CPU_OWNERS
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index b40491a49b14..f44e829d49d7 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -50,8 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName,
}
void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appName,
- jstring devOptIn, jobjectArray featuresObj, jobject rulesFd,
- jlong rulesOffset, jlong rulesLength) {
+ jstring devOptIn, jobjectArray featuresObj) {
ScopedUtfChars pathChars(env, path);
ScopedUtfChars appNameChars(env, appName);
ScopedUtfChars devOptInChars(env, devOptIn);
@@ -74,11 +73,8 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring appNa
}
}
- int rulesFd_native = jniGetFDFromFileDescriptor(env, rulesFd);
-
android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), appNameChars.c_str(),
- devOptInChars.c_str(), features,
- rulesFd_native, rulesOffset, rulesLength);
+ devOptInChars.c_str(), features);
}
bool shouldUseAngle_native(JNIEnv* env, jobject clazz, jstring appName) {
@@ -124,8 +120,7 @@ const JNINativeMethod g_methods[] = {
{"setInjectLayersPrSetDumpable", "()Z",
reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)},
{"setAngleInfo",
- "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/io/"
- "FileDescriptor;JJ)V",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V",
reinterpret_cast<void*>(setAngleInfo_native)},
{"getShouldUseAngle", "(Ljava/lang/String;)Z",
reinterpret_cast<void*>(shouldUseAngle_native)},
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 0957067de603..869b53df2837 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -449,6 +449,11 @@ static jint nativeSetFrameRate(JNIEnv* env, jclass clazz, jlong nativeObject, jf
int(changeFrameRateStrategy));
}
+static void nativeDestroy(JNIEnv* env, jclass clazz, jlong nativeObject) {
+ sp<Surface> surface(reinterpret_cast<Surface*>(nativeObject));
+ surface->destroy();
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gSurfaceMethods[] = {
@@ -477,6 +482,7 @@ static const JNINativeMethod gSurfaceMethods[] = {
{"nativeSetAutoRefreshEnabled", "(JZ)I", (void*)nativeSetAutoRefreshEnabled},
{"nativeSetFrameRate", "(JFII)I", (void*)nativeSetFrameRate},
{"nativeGetFromBlastBufferQueue", "(JJ)J", (void*)nativeGetFromBlastBufferQueue},
+ {"nativeDestroy", "(J)V", (void*)nativeDestroy},
};
int register_android_view_Surface(JNIEnv* env)
diff --git a/core/proto/OWNERS b/core/proto/OWNERS
index 78650ed34813..931ef4478098 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -5,7 +5,6 @@ joeo@google.com
singhtejinder@google.com
yanmin@google.com
yaochen@google.com
-yro@google.com
zhouwenjie@google.com
# Frameworks
@@ -13,7 +12,7 @@ ogunwale@google.com
jjaggi@google.com
kwekua@google.com
roosa@google.com
-per-file package_item_info.proto = toddke@google.com,patb@google.com
+per-file package_item_info.proto = patb@google.com
per-file usagestatsservice.proto, usagestatsservice_v2.proto = file:/core/java/android/app/usage/OWNERS
per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index c3d159659622..83e26f66fd62 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -771,6 +771,8 @@ message GlobalSettingsProto {
optional SettingProto power_manager_constants = 93;
reserved 94; // Used to be priv_app_oob_enabled
+ optional SettingProto power_button_long_press_duration_ms = 154 [ (android.privacy).dest = DEST_AUTOMATIC ];
+
message PrepaidSetup {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -970,6 +972,7 @@ message GlobalSettingsProto {
optional SettingProto usb_mass_storage_enabled = 127 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto use_google_mail = 128 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto use_open_wifi_package = 129 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto uwb_enabled = 155 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto vt_ims_enabled = 130 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto wait_for_debugger = 131 [ (android.privacy).dest = DEST_AUTOMATIC ];
@@ -1063,5 +1066,5 @@ message GlobalSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 154;
+ // Next tag = 156;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d727a55615f2..f58f4e4be413 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -55,6 +55,7 @@
<protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+ <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENSION_CHANGED" />
<protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
<protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
<protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
diff --git a/core/res/OWNERS b/core/res/OWNERS
index 165dcad896c9..e241dd6e3615 100644
--- a/core/res/OWNERS
+++ b/core/res/OWNERS
@@ -22,7 +22,6 @@ patb@google.com
shanh@google.com
svetoslavganov@android.com
svetoslavganov@google.com
-toddke@google.com
tsuji@google.com
yamasani@google.com
diff --git a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
index 5add19bba51b..aa3800061bd3 100644
--- a/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim-ldrtl/cross_profile_apps_thumbnail_enter.xml
@@ -18,19 +18,9 @@
-->
<!-- This should be kept in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:hasRoundedCorners="true"
android:shareInterpolator="false"
- android:zAdjustment="top">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -38,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
diff --git a/core/res/res/anim-ldrtl/task_close_enter.xml b/core/res/res/anim-ldrtl/task_close_enter.xml
index e00141a8c155..5ace46d4c621 100644
--- a/core/res/res/anim-ldrtl/task_close_enter.xml
+++ b/core/res/res/anim-ldrtl/task_close_enter.xml
@@ -14,20 +14,9 @@
~ limitations under the License
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -35,34 +24,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set> \ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_close_exit.xml b/core/res/res/anim-ldrtl/task_close_exit.xml
index 71a44ae7d2fc..0887019ca608 100644
--- a/core/res/res/anim-ldrtl/task_close_exit.xml
+++ b/core/res/res/anim-ldrtl/task_close_exit.xml
@@ -15,19 +15,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -35,26 +24,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set> \ No newline at end of file
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_enter.xml b/core/res/res/anim-ldrtl/task_open_enter.xml
index 7815f7d661d0..52c74a6c4482 100644
--- a/core/res/res/anim-ldrtl/task_open_enter.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter.xml
@@ -16,20 +16,9 @@
<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,34 +26,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
-</set> \ No newline at end of file
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
index 5fccd6df14a5..90ec0715f8b0 100644
--- a/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim-ldrtl/task_open_enter_cross_profile_apps.xml
@@ -16,20 +16,9 @@
-->
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,36 +26,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the transition around longer for the thumbnail, should be kept in sync with
cross_profile_apps_thumbmail.xml -->
@@ -75,4 +37,4 @@
android:toAlpha="1.0"
android:startOffset="717"
android:duration="200"/>
-</set> \ No newline at end of file
+</set>
diff --git a/core/res/res/anim-ldrtl/task_open_exit.xml b/core/res/res/anim-ldrtl/task_open_exit.xml
index 025e1bdc05c9..88cdcceae415 100644
--- a/core/res/res/anim-ldrtl/task_open_exit.xml
+++ b/core/res/res/anim-ldrtl/task_open_exit.xml
@@ -15,19 +15,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -35,26 +24,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set> \ No newline at end of file
+</set>
diff --git a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
index 2cfeecf4685d..f6d7b7229677 100644
--- a/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
+++ b/core/res/res/anim/cross_profile_apps_thumbnail_enter.xml
@@ -19,18 +19,8 @@
<!-- This should be kept in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:zAdjustment="top">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -38,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the thumbnail around longer and fade out the thumbnail -->
<alpha android:fromAlpha="1.0" android:toAlpha="0"
diff --git a/core/res/res/anim/task_close_enter.xml b/core/res/res/anim/task_close_enter.xml
index 487ff5c748d3..52017b1091bc 100644
--- a/core/res/res/anim/task_close_enter.xml
+++ b/core/res/res/anim/task_close_enter.xml
@@ -16,20 +16,9 @@
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="-105%"
@@ -37,34 +26,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set> \ No newline at end of file
diff --git a/core/res/res/anim/task_close_exit.xml b/core/res/res/anim/task_close_exit.xml
index afc3256cb617..3a8dd934cd17 100644
--- a/core/res/res/anim/task_close_exit.xml
+++ b/core/res/res/anim/task_close_exit.xml
@@ -17,19 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -37,26 +26,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set> \ No newline at end of file
+</set>
diff --git a/core/res/res/anim/task_open_enter.xml b/core/res/res/anim/task_open_enter.xml
index 0aafc1c0b91c..3c934382bb75 100644
--- a/core/res/res/anim/task_open_enter.xml
+++ b/core/res/res/anim/task_open_enter.xml
@@ -15,23 +15,12 @@
** limitations under the License.
*/
-->
-<!-- This should in sync with task_open_enter_cross_profile_apps.xml -->
-<!-- This should in sync with cross_profile_apps_thumbnail_enter.xml -->
+<!-- This should be kept in sync with task_open_enter_cross_profile_apps.xml -->
+<!-- This should be kept in sync with cross_profile_apps_thumbnail_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -39,34 +28,7 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
</set> \ No newline at end of file
diff --git a/core/res/res/anim/task_open_enter_cross_profile_apps.xml b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
index 702f7ba162aa..16249d15e1b7 100644
--- a/core/res/res/anim/task_open_enter_cross_profile_apps.xml
+++ b/core/res/res/anim/task_open_enter_cross_profile_apps.xml
@@ -18,20 +18,9 @@
-->
<!-- This should in sync with task_open_enter.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:zAdjustment="top"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1"
- android:toAlpha="1.0"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="217"/>
+ android:shareInterpolator="false"
+ android:zAdjustment="top"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="105%"
@@ -39,36 +28,9 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0526"
- android:toXScale="1"
- android:fromYScale="1.0526"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
-
- <scale
- android:fromXScale="0.95"
- android:toXScale="1"
- android:fromYScale="0.95"
- android:toYScale="1"
- android:pivotX="50%"
- android:pivotY="50%"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:startOffset="283"
- android:duration="317"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
<!-- To keep the transition around longer for the thumbnail, should be kept in sync with
cross_profile_apps_thumbmail.xml -->
diff --git a/core/res/res/anim/task_open_exit.xml b/core/res/res/anim/task_open_exit.xml
index 691317d2e6e0..21fec7f694bf 100644
--- a/core/res/res/anim/task_open_exit.xml
+++ b/core/res/res/anim/task_open_exit.xml
@@ -17,19 +17,8 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false"
- android:hasRoundedCorners="true"
- android:showWallpaper="true">
-
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:interpolator="@interpolator/linear"
- android:startOffset="67"
- android:duration="283"/>
+ android:shareInterpolator="false"
+ android:hasRoundedCorners="true">
<translate
android:fromXDelta="0"
@@ -37,26 +26,8 @@
android:fillEnabled="true"
android:fillBefore="true"
android:fillAfter="true"
- android:interpolator="@interpolator/aggressive_ease"
- android:startOffset="50"
- android:duration="383"/>
-
- <scale
- android:fromXScale="1.0"
- android:toXScale="0.95"
- android:fromYScale="1.0"
- android:toYScale="0.95"
- android:fillEnabled="true"
- android:fillBefore="true"
- android:fillAfter="true"
- android:pivotX="50%"
- android:pivotY="50%"
- android:interpolator="@interpolator/fast_out_slow_in"
- android:duration="283"/>
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="500"/>
- <!-- This is needed to keep the animation running while task_open_enter completes -->
- <alpha
- android:fromAlpha="1.0"
- android:toAlpha="1.0"
- android:duration="600"/>
-</set> \ No newline at end of file
+</set>
diff --git a/core/res/res/color/overview_background.xml b/core/res/res/color/overview_background.xml
new file mode 100644
index 000000000000..45c6c256d67a
--- /dev/null
+++ b/core/res/res/color/overview_background.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="87" />
+</selector> \ No newline at end of file
diff --git a/core/res/res/color/overview_background_dark.xml b/core/res/res/color/overview_background_dark.xml
new file mode 100644
index 000000000000..84f4fdff4e1a
--- /dev/null
+++ b/core/res/res/color/overview_background_dark.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
+</selector> \ No newline at end of file
diff --git a/core/res/res/drawable/btn_notification_emphasized.xml b/core/res/res/drawable/btn_notification_emphasized.xml
index 29c51f2a33c9..7c09fb889c48 100644
--- a/core/res/res/drawable/btn_notification_emphasized.xml
+++ b/core/res/res/drawable/btn_notification_emphasized.xml
@@ -24,9 +24,9 @@
android:insetBottom="@dimen/button_inset_vertical_material">
<shape android:shape="rectangle">
<corners android:radius="@dimen/notification_action_button_radius" />
- <padding android:left="12dp"
+ <padding android:left="16dp"
android:top="@dimen/button_padding_vertical_material"
- android:right="12dp"
+ android:right="16dp"
android:bottom="@dimen/button_padding_vertical_material" />
<solid android:color="@color/white" />
</shape>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 2e4578c39430..783fabe20a6d 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -33,4 +33,6 @@
<color name="call_notification_answer_color">#5DBA80</color>
<color name="personal_apps_suspension_notification_color">#8AB4F8</color>
+
+ <color name="overview_background">@color/overview_background_dark</color>
</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8496a478af97..fb7cddaf281a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -957,6 +957,20 @@
-->
<integer name="config_longPressOnPowerBehavior">5</integer>
+ <!-- The time in milliseconds after which a press on power button is considered "long". -->
+ <integer name="config_longPressOnPowerDurationMs">500</integer>
+
+ <!-- The possible UI options to be surfaced for configuring long press power on duration
+ action. Value set in config_longPressOnPowerDurationMs should be one of the available
+ options to allow users to restore default. -->
+ <integer-array name="config_longPressOnPowerDurationSettings">
+ <item>250</item>
+ <item>350</item>
+ <item>500</item>
+ <item>650</item>
+ <item>750</item>
+ </integer-array>
+
<!-- Whether the setting to change long press on power behaviour from default to assistant (5)
is available in Settings.
-->
@@ -4497,6 +4511,13 @@
If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
<integer name="config_fixedRefreshRateInHighZone">0</integer>
+ <!-- Default refresh rate while the device has high brightness mode enabled for Sunlight.
+ This value overrides values from DisplayDeviceConfig -->
+ <integer name="config_defaultRefreshRateInHbmSunlight">0</integer>
+
+ <!-- Default refresh rate while the device has high brightness mode enabled for HDR. -->
+ <integer name="config_defaultRefreshRateInHbmHdr">0</integer>
+
<!-- The type of the light sensor to be used by the display framework for things like
auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
<string name="config_displayLightSensorType" translatable="false" />
@@ -4556,6 +4577,13 @@
<!-- Indicates whether device has a power button fingerprint sensor. -->
<bool name="config_is_powerbutton_fps" translatable="false" >false</bool>
+ <!-- When each intermediate UDFPS enroll stage ends, as a fraction of total progress. -->
+ <string-array name="config_udfps_enroll_stage_thresholds" translatable="false">
+ <item>0.25</item>
+ <item>0.5</item>
+ <item>0.75</item>
+ </string-array>
+
<!-- Messages that should not be shown to the user during face auth enrollment. This should be
used to hide messages that may be too chatty or messages that the user can't do much about.
Entries are defined in android.hardware.biometrics.face@1.0 types.hal -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index de7a1175b4a3..a666a5b4b796 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -58,6 +58,9 @@
<!-- How much we expand the touchable region of the status bar below the notch to catch touches
that just start below the notch. -->
<dimen name="display_cutout_touchable_region_size">12dp</dimen>
+ <!-- The default margin used in immersive mode to capture the start of a swipe gesture from the
+ edge of the screen to show the system bars. -->
+ <dimen name="system_gestures_start_threshold">24dp</dimen>
<!-- Height of the bottom navigation bar frame; this is different than navigation_bar_height
where that is the height reported to all the other windows to resize themselves around the
@@ -237,6 +240,9 @@
value is calculated in ConversationLayout#updateActionListPadding() -->
<dimen name="notification_actions_padding_start">36dp</dimen>
+ <!-- The max width of a priority action button when it is collapsed to just the icon. -->
+ <dimen name="notification_actions_collapsed_priority_width">60dp</dimen>
+
<!-- The start padding to optionally use (e.g. if there's extra space) for CallStyle
notification actions.
this = conversation_content_start (80dp) - button inset (4dp) - action padding (12dp) -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index be5063f3609a..0e09936bc251 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1730,6 +1730,8 @@
<!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
<string-array name="fingerprint_error_vendor">
</string-array>
+ <!-- Default error message to use when fingerprint_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+ <string name="fingerprint_error_vendor_unknown">Something went wrong. Try again.</string>
<!-- Content description which should be used for the fingerprint icon. -->
<string name="fingerprint_icon_content_description">Fingerprint icon</string>
@@ -1840,6 +1842,8 @@
<!-- Array containing custom error messages from vendor. Vendor is expected to add and translate these strings -->
<string-array name="face_error_vendor">
</string-array>
+ <!-- Default error message to use when face_error_vendor does not contain a message. [CHAR LIMIT=NONE] -->
+ <string name="face_error_vendor_unknown">Something went wrong. Try again.</string>
<!-- Content description which should be used for the face icon. [CHAR LIMIT=10] -->
<string name="face_icon_content_description">Face icon</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d06dafe9b3e7..8ed799111776 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -439,6 +439,8 @@
<java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
<java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
<java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+ <java-symbol type="integer" name="config_longPressOnPowerDurationMs" />
+ <java-symbol type="array" name="config_longPressOnPowerDurationSettings" />
<java-symbol type="bool" name="config_longPressOnPowerForAssistantSettingAvailable" />
<java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
<java-symbol type="integer" name="config_veryLongPressTimeout" />
@@ -1742,6 +1744,7 @@
<java-symbol type="dimen" name="navigation_bar_width_car_mode" />
<java-symbol type="dimen" name="status_bar_height" />
<java-symbol type="dimen" name="display_cutout_touchable_region_size" />
+ <java-symbol type="dimen" name="system_gestures_start_threshold" />
<java-symbol type="dimen" name="quick_qs_offset_height" />
<java-symbol type="drawable" name="ic_jog_dial_sound_off" />
<java-symbol type="drawable" name="ic_jog_dial_sound_on" />
@@ -2524,6 +2527,7 @@
<java-symbol type="string" name="fingerprint_error_no_space" />
<java-symbol type="string" name="fingerprint_error_timeout" />
<java-symbol type="array" name="fingerprint_error_vendor" />
+ <java-symbol type="string" name="fingerprint_error_vendor_unknown" />
<java-symbol type="string" name="fingerprint_acquired_partial" />
<java-symbol type="string" name="fingerprint_acquired_insufficient" />
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -2563,6 +2567,7 @@
<java-symbol type="string" name="face_error_no_space" />
<java-symbol type="string" name="face_error_timeout" />
<java-symbol type="array" name="face_error_vendor" />
+ <java-symbol type="string" name="face_error_vendor_unknown" />
<java-symbol type="string" name="face_error_canceled" />
<java-symbol type="string" name="face_error_user_canceled" />
<java-symbol type="string" name="face_error_lockout" />
@@ -2605,6 +2610,7 @@
<java-symbol type="array" name="config_udfps_sensor_props" />
<java-symbol type="integer" name="config_udfps_illumination_transition_ms" />
<java-symbol type="bool" name="config_is_powerbutton_fps" />
+ <java-symbol type="array" name="config_udfps_enroll_stage_thresholds" />
<java-symbol type="array" name="config_face_acquire_enroll_ignorelist" />
<java-symbol type="array" name="config_face_acquire_vendor_enroll_ignorelist" />
@@ -3184,6 +3190,7 @@
<java-symbol type="id" name="notification_action_list_margin_target" />
<java-symbol type="dimen" name="notification_actions_padding_start"/>
+ <java-symbol type="dimen" name="notification_actions_collapsed_priority_width"/>
<java-symbol type="dimen" name="notification_action_disabled_alpha" />
<java-symbol type="id" name="tag_margin_end_when_icon_visible" />
<java-symbol type="id" name="tag_margin_end_when_icon_gone" />
@@ -3949,6 +3956,8 @@
<java-symbol type="integer" name="config_defaultRefreshRateInZone" />
<java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
<java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
+ <java-symbol type="integer" name="config_defaultRefreshRateInHbmSunlight" />
+ <java-symbol type="integer" name="config_defaultRefreshRateInHbmHdr" />
<!-- For fixed refresh rate displays in high brightness-->
<java-symbol type="integer" name="config_fixedRefreshRateInHighZone" />
@@ -4431,6 +4440,8 @@
<java-symbol type="integer" name="config_customizedMaxCachedProcesses" />
+ <java-symbol type="color" name="overview_background"/>
+
<java-symbol type="bool" name="config_disableTaskSnapshots" />
</resources>
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index cd07d464ee65..34c1763b3286 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,9 +16,11 @@
package android.app;
-import static androidx.core.graphics.ColorUtils.calculateContrast;
+import static android.app.Notification.Builder.ensureColorSpanContrast;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsAtLeast;
+import static com.android.internal.util.ContrastColorUtilTest.assertContrastIsWithinRange;
import static com.google.common.truth.Truth.assertThat;
@@ -35,6 +37,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.BitmapFactory;
import android.graphics.Color;
@@ -42,12 +45,21 @@ import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.TextAppearanceSpan;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.R;
+import com.android.internal.util.ContrastColorUtil;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -334,6 +346,163 @@ public class NotificationTest {
}
@Test
+ public void testBuilder_getFullLengthSpanColor_returnsNullForString() {
+ assertThat(Notification.Builder.getFullLengthSpanColor("String")).isNull();
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_returnsNullWithPartialSpan() {
+ CharSequence text = new SpannableStringBuilder()
+ .append("text with ")
+ .append("some red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isNull();
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithSingleSpan() {
+ CharSequence text = new SpannableStringBuilder()
+ .append("text that is all red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithFullAndPartialSpans() {
+ Spannable text = new SpannableString("blue text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(Color.BLUE);
+ }
+
+ @Test
+ public void testBuilder_getFullLengthSpanColor_worksWithTextAppearance() {
+ Spannable text = new SpannableString("title text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ int expectedTextColor = textAppearanceSpan.getTextColor().getDefaultColor();
+ text.setSpan(textAppearanceSpan, 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ assertThat(Notification.Builder.getFullLengthSpanColor(text)).isEqualTo(expectedTextColor);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_removesAllFullLengthColorSpans() {
+ Spannable text = new SpannableString("blue text with yellow and green");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ assertThat(taSpan.getTextColor()).isNotNull(); // it must be set to prove it is cleared.
+ text.setSpan(taSpan, 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spannable result = (Spannable) ensureColorSpanContrast(text, Color.BLACK);
+ Object[] spans = result.getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(3);
+
+ assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+ assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+ assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+ assertThat(result.getSpanStart(spans[1])).isEqualTo(0);
+ assertThat(result.getSpanEnd(spans[1])).isEqualTo(31);
+ assertThat(spans[1]).isNotSameInstanceAs(taSpan); // don't mutate the existing span
+ assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+ assertThat(((TextAppearanceSpan) spans[1]).getTextColor()).isNull();
+
+ assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+ assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+ assertThat(((ForegroundColorSpan) spans[2]).getForegroundColor()).isEqualTo(Color.GREEN);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_partialLength_adjusted() {
+ int background = 0xFFFF0101; // Slightly lighter red
+ CharSequence text = new SpannableStringBuilder()
+ .append("text with ")
+ .append("some red", new ForegroundColorSpan(Color.RED),
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ CharSequence result = ensureColorSpanContrast(text, background);
+
+ // ensure the span has been updated to have > 1.3:1 contrast ratio with fill color
+ Object[] spans = ((Spannable) result).getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(1);
+ int foregroundColor = ((ForegroundColorSpan) spans[0]).getForegroundColor();
+ assertContrastIsWithinRange(foregroundColor, background, 3, 3.2);
+ }
+
+ @Test
+ public void testBuilder_ensureColorSpanContrast_worksWithComplexInput() {
+ Spannable text = new SpannableString("blue text with yellow and green and cyan");
+ text.setSpan(new ForegroundColorSpan(Color.YELLOW), 15, 21,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.BLUE), 0, text.length(),
+ Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ // cyan TextAppearanceSpan
+ TextAppearanceSpan taSpan = new TextAppearanceSpan(mContext,
+ R.style.TextAppearance_DeviceDefault_Notification_Title);
+ taSpan = new TextAppearanceSpan(taSpan.getFamily(), taSpan.getTextStyle(),
+ taSpan.getTextSize(), ColorStateList.valueOf(Color.CYAN), null);
+ text.setSpan(taSpan, 36, 40,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ text.setSpan(new ForegroundColorSpan(Color.GREEN), 26, 31,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ Spannable result = (Spannable) ensureColorSpanContrast(text, Color.GRAY);
+ Object[] spans = result.getSpans(0, result.length(), Object.class);
+ assertThat(spans).hasLength(3);
+
+ assertThat(result.getSpanStart(spans[0])).isEqualTo(15);
+ assertThat(result.getSpanEnd(spans[0])).isEqualTo(21);
+ assertThat(((ForegroundColorSpan) spans[0]).getForegroundColor()).isEqualTo(Color.YELLOW);
+
+ assertThat(result.getSpanStart(spans[1])).isEqualTo(36);
+ assertThat(result.getSpanEnd(spans[1])).isEqualTo(40);
+ assertThat(spans[1]).isNotSameInstanceAs(taSpan); // don't mutate the existing span
+ assertThat(((TextAppearanceSpan) spans[1]).getFamily()).isEqualTo(taSpan.getFamily());
+ ColorStateList newCyanList = ((TextAppearanceSpan) spans[1]).getTextColor();
+ assertThat(newCyanList).isNotNull();
+ assertContrastIsWithinRange(newCyanList.getDefaultColor(), Color.GRAY, 3, 3.2);
+
+ assertThat(result.getSpanStart(spans[2])).isEqualTo(26);
+ assertThat(result.getSpanEnd(spans[2])).isEqualTo(31);
+ int newGreen = ((ForegroundColorSpan) spans[2]).getForegroundColor();
+ assertThat(newGreen).isNotEqualTo(Color.GREEN);
+ assertContrastIsWithinRange(newGreen, Color.GRAY, 3, 3.2);
+ }
+
+ @Test
+ public void testBuilder_ensureButtonFillContrast_adjustsDarker() {
+ int background = Color.LTGRAY;
+ int foreground = Color.LTGRAY;
+ int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+ assertContrastIsWithinRange(result, background, 1.3, 1.5);
+ assertThat(ContrastColorUtil.calculateLuminance(result))
+ .isLessThan(ContrastColorUtil.calculateLuminance(background));
+ }
+
+ @Test
+ public void testBuilder_ensureButtonFillContrast_adjustsLighter() {
+ int background = Color.DKGRAY;
+ int foreground = Color.DKGRAY;
+ int result = Notification.Builder.ensureButtonFillContrast(foreground, background);
+ assertContrastIsWithinRange(result, background, 1.3, 1.5);
+ assertThat(ContrastColorUtil.calculateLuminance(result))
+ .isGreaterThan(ContrastColorUtil.calculateLuminance(background));
+ }
+
+ @Test
public void testColors_ensureColors_dayMode_producesValidPalette() {
Notification.Colors c = new Notification.Colors();
boolean colorized = false;
@@ -399,6 +568,8 @@ public class NotificationTest {
assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor());
assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor());
assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor());
+ assertEquals(cDay.getTertiaryAccentColor(), cNight.getTertiaryAccentColor());
+ assertEquals(cDay.getOnAccentTextColor(), cNight.getOnAccentTextColor());
assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor());
assertEquals(cDay.getContrastColor(), cNight.getContrastColor());
assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha());
@@ -413,30 +584,26 @@ public class NotificationTest {
assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getTertiaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID);
+ assertThat(c.getOnAccentTextColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID);
assertThat(c.getRippleAlpha()).isAtLeast(0x00);
assertThat(c.getRippleAlpha()).isAtMost(0xff);
- // Assert that various colors have sufficient contrast
+ // Assert that various colors have sufficient contrast with the background
assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5);
assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5);
- // This accent color is only used for emphasized buttons
+ // These colors are only used for emphasized buttons; they do not need contrast
assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1);
- }
+ assertContrastIsAtLeast(c.getTertiaryAccentColor(), c.getBackgroundColor(), 1);
- private void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
- try {
- assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
- } catch (AssertionError e) {
- throw new AssertionError(
- String.format("Insufficient contrast: foreground=#%08x background=#%08x",
- foreground, background), e);
- }
+ // The text that is used within the accent color DOES need to have contrast
+ assertContrastIsAtLeast(c.getOnAccentTextColor(), c.getTertiaryAccentColor(), 4.5);
}
private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor,
diff --git a/core/tests/coretests/src/android/graphics/FontListParserTest.java b/core/tests/coretests/src/android/graphics/FontListParserTest.java
index 22f6ec0b24b1..701e6194d4ee 100644
--- a/core/tests/coretests/src/android/graphics/FontListParserTest.java
+++ b/core/tests/coretests/src/android/graphics/FontListParserTest.java
@@ -27,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
+import android.graphics.fonts.FontCustomizationParser;
import android.graphics.fonts.FontStyle;
import android.os.LocaleList;
import android.text.FontConfig;
@@ -46,6 +47,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.List;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -318,6 +320,52 @@ public final class FontListParserTest {
}
}
+ @Test
+ public void alias() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + " <family name='custom-family'>"
+ + " <font>missing.ttf</font>"
+ + " </family>"
+ + " <alias name='custom-alias' to='sans-serif'/>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, true /* include non-existing font files */);
+ List<FontConfig.Alias> aliases = config.getAliases();
+ assertThat(aliases.size()).isEqualTo(1);
+ assertThat(aliases.get(0).getName()).isEqualTo("custom-alias");
+ assertThat(aliases.get(0).getOriginal()).isEqualTo("sans-serif");
+ }
+
+ @Test
+ public void dropped_FamilyAlias() throws Exception {
+ String xml = "<?xml version='1.0' encoding='UTF-8'?>"
+ + "<familyset>"
+ + " <family name='sans-serif'>"
+ + " <font>test.ttf</font>"
+ + " </family>"
+ + " <family name='custom-family'>"
+ + " <font>missing.ttf</font>"
+ + " </family>"
+ + " <alias name='custom-alias' to='custom-family'/>"
+ + "</familyset>";
+ FontConfig config = readFamilies(xml, false /* exclude not existing file */);
+ assertThat(config.getAliases()).isEmpty();
+ }
+
+ private FontConfig readFamilies(String xml, boolean allowNonExisting)
+ throws IOException, XmlPullParserException {
+ ByteArrayInputStream buffer = new ByteArrayInputStream(
+ xml.getBytes(StandardCharsets.UTF_8));
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(buffer, "UTF-8");
+ parser.nextTag();
+ return FontListParser.readFamilies(parser, "", new FontCustomizationParser.Result(), null,
+ 0L /* last modified date */, 0 /* config version */, allowNonExisting);
+ }
+
private FontConfig.FontFamily readFamily(String xml)
throws IOException, XmlPullParserException {
ByteArrayInputStream buffer = new ByteArrayInputStream(
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dfc9013e3c05..149f58f0a69b 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -32,6 +32,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -121,6 +122,46 @@ public class DisplayManagerGlobalTest {
Mockito.verifyZeroInteractions(mListener);
}
+ @Test
+ public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreNoOtherListeners()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(ALL_DISPLAY_EVENTS));
+
+ mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+ Mockito.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+ }
+
+ @Test
+ public void testDisplayManagerGlobalRegistersWithDisplayManager_WhenThereAreListeners()
+ throws RemoteException {
+ mDisplayManagerGlobal.registerDisplayListener(mListener, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
+ InOrder inOrder = Mockito.inOrder(mDisplayManager);
+
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.registerNativeChoreographerForRefreshRateCallbacks();
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(ALL_DISPLAY_EVENTS | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.unregisterNativeChoreographerForRefreshRateCallbacks();
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+
+ mDisplayManagerGlobal.unregisterDisplayListener(mListener);
+ inOrder.verify(mDisplayManager)
+ .registerCallbackWithEventMask(mCallbackCaptor.capture(), eq(0L));
+
+ }
+
private void waitForHandler() {
mHandler.runWithScissors(() -> { }, 0);
}
diff --git a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
index a5a98a98f0be..109b7ab73d6f 100644
--- a/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
+++ b/core/tests/coretests/src/android/service/notification/StatusBarNotificationTest.java
@@ -17,6 +17,8 @@
package android.service.notification;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertFalse;
@@ -51,6 +53,7 @@ public class StatusBarNotificationTest {
private final Context mMockContext = mock(Context.class);
@Mock
+ private Context mRealContext;
private PackageManager mPm;
private static final String PKG = "com.example.o";
@@ -75,6 +78,8 @@ public class StatusBarNotificationTest {
InstrumentationRegistry.getContext().getResources());
when(mMockContext.getPackageManager()).thenReturn(mPm);
when(mMockContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+
+ mRealContext = InstrumentationRegistry.getContext();
}
@Test
@@ -199,6 +204,19 @@ public class StatusBarNotificationTest {
}
+ @Test
+ public void testGetPackageContext_worksWithUserAll() {
+ String pkg = "com.android.systemui";
+ int uid = 1000;
+ Notification notification = getNotificationBuilder(GROUP_ID_1, CHANNEL_ID).build();
+ StatusBarNotification sbn = new StatusBarNotification(
+ pkg, pkg, ID, TAG, uid, uid, notification, UserHandle.ALL, null, UID);
+ Context resultContext = sbn.getPackageContext(mRealContext);
+ assertNotNull(resultContext);
+ assertNotSame(mRealContext, resultContext);
+ assertEquals(pkg, resultContext.getPackageName());
+ }
+
private StatusBarNotification getNotification(String pkg, String group, String channelId) {
return getNotification(pkg, getNotificationBuilder(group, channelId));
}
diff --git a/core/tests/coretests/src/android/window/WindowContextControllerTest.java b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
index 020f4a06b892..073e46827bbb 100644
--- a/core/tests/coretests/src/android/window/WindowContextControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowContextControllerTest.java
@@ -23,11 +23,13 @@ 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.doNothing;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import android.content.res.Configuration;
import android.os.Binder;
import android.platform.test.annotations.Presubmit;
import android.view.IWindowManager;
@@ -38,6 +40,8 @@ import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/**
* Tests for {@link WindowContextController}
@@ -53,15 +57,18 @@ import org.junit.runner.RunWith;
@Presubmit
public class WindowContextControllerTest {
private WindowContextController mController;
+ @Mock
private IWindowManager mMockWms;
+ @Mock
+ private WindowTokenClient mMockToken;
@Before
public void setUp() throws Exception {
- mMockWms = mock(IWindowManager.class);
- mController = new WindowContextController(new Binder(), mMockWms);
-
- doReturn(true).when(mMockWms).attachWindowContextToDisplayArea(any(), anyInt(),
- anyInt(), any());
+ MockitoAnnotations.initMocks(this);
+ mController = new WindowContextController(mMockToken, mMockWms);
+ doNothing().when(mMockToken).onConfigurationChanged(any(), anyInt());
+ doReturn(new Configuration()).when(mMockWms).attachWindowContextToDisplayArea(any(),
+ anyInt(), anyInt(), any());
}
@Test(expected = IllegalStateException.class)
@@ -85,6 +92,7 @@ public class WindowContextControllerTest {
null /* options */);
assertThat(mController.mAttachedToDisplayArea).isTrue();
+ verify(mMockToken).onConfigurationChanged(any(), eq(DEFAULT_DISPLAY));
mController.detachIfNeeded();
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 464412f17722..d4799a8f5fd3 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,6 +23,8 @@ import android.app.ActivityManager;
import android.os.BatteryStats;
import android.os.BatteryStats.HistoryItem;
import android.os.BatteryStats.Uid.Sensor;
+import android.os.Process;
+import android.os.UserHandle;
import android.os.WorkSource;
import android.util.SparseLongArray;
import android.view.Display;
@@ -53,6 +55,8 @@ import java.util.Map;
public class BatteryStatsNoteTest extends TestCase {
private static final int UID = 10500;
+ private static final int ISOLATED_APP_ID = Process.FIRST_ISOLATED_UID + 23;
+ private static final int ISOLATED_UID = UserHandle.getUid(0, ISOLATED_APP_ID);
private static final WorkSource WS = new WorkSource(UID);
/**
@@ -114,6 +118,88 @@ public class BatteryStatsNoteTest extends TestCase {
assertEquals(120_000, bgTime);
}
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid.
+ */
+ @SmallTest
+ public void testNoteStartWakeLocked_isolatedUid() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ int pid = 10;
+ String name = "name";
+ String historyName = "historyName";
+
+ WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+ isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+ // Map ISOLATED_UID to UID.
+ bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+ bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL, false);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ clocks.realtime = clocks.uptime = 220;
+ bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL);
+
+ // ISOLATED_UID wakelock time should be attributed to UID.
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
+ long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ assertEquals(220_000, actualTime);
+ assertEquals(120_000, bgTime);
+ }
+
+ /**
+ * Test BatteryStatsImpl.Uid.noteStartWakeLocked for an isolated uid, with a race where the
+ * isolated uid is removed from batterystats before the wakelock has been stopped.
+ */
+ @SmallTest
+ public void testNoteStartWakeLocked_isolatedUidRace() throws Exception {
+ final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+ MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+
+ int pid = 10;
+ String name = "name";
+ String historyName = "historyName";
+
+ WorkSource.WorkChain isolatedWorkChain = new WorkSource.WorkChain();
+ isolatedWorkChain.addNode(ISOLATED_UID, name);
+
+ // Map ISOLATED_UID to UID.
+ bi.addIsolatedUidLocked(ISOLATED_UID, UID);
+
+ bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+ bi.noteStartWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL, false);
+
+ clocks.realtime = clocks.uptime = 100;
+ bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+
+ clocks.realtime = clocks.uptime = 150;
+ bi.maybeRemoveIsolatedUidLocked(ISOLATED_UID, clocks.realtime, clocks.uptime);
+
+ clocks.realtime = clocks.uptime = 220;
+ bi.noteStopWakeLocked(ISOLATED_UID, pid, isolatedWorkChain, name, historyName,
+ WAKE_TYPE_PARTIAL);
+
+ // ISOLATED_UID wakelock time should be attributed to UID.
+ BatteryStats.Timer aggregTimer = bi.getUidStats().get(UID)
+ .getAggregatedPartialWakelockTimer();
+ long actualTime = aggregTimer.getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ long bgTime = aggregTimer.getSubTimer().getTotalTimeLocked(300_000, STATS_SINCE_CHARGED);
+ assertEquals(220_000, actualTime);
+ assertEquals(120_000, bgTime);
+ }
+
/**
* Test BatteryStatsImpl.noteUidProcessStateLocked.
@@ -506,7 +592,7 @@ public class BatteryStatsNoteTest extends TestCase {
public void testUpdateDisplayMeasuredEnergyStatsLocked() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
clocks.realtime = 0;
int screen = Display.STATE_OFF;
@@ -591,7 +677,7 @@ public class BatteryStatsNoteTest extends TestCase {
public void testUpdateCustomMeasuredEnergyStatsLocked_neverCalled() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
bi.setOnBatteryInternal(true);
final int uid1 = 11500;
@@ -605,7 +691,7 @@ public class BatteryStatsNoteTest extends TestCase {
public void testUpdateCustomMeasuredEnergyStatsLocked() {
final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
- bi.initMeasuredEnergyStats();
+ bi.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
final int bucketA = 0; // Custom bucket 0
final int bucketB = 1; // Custom bucket 1
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
index b851f0ad3414..0135fe84909a 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsSensorTest.java
@@ -403,7 +403,7 @@ public class BatteryStatsSensorTest extends TestCase {
assertNotNull(sensor.getSensorBackgroundTime());
// Reset the stats. Since the sensor is still running, we should still see the timer
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
sensor = uid.getSensorStats().get(SENSOR_ID);
assertNotNull(sensor);
@@ -413,7 +413,7 @@ public class BatteryStatsSensorTest extends TestCase {
bi.noteStopSensorLocked(UID, SENSOR_ID);
// Now the sensor timer has stopped so this reset should also take out the sensor.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
sensor = uid.getSensorStats().get(SENSOR_ID);
assertNull(sensor);
@@ -465,7 +465,7 @@ public class BatteryStatsSensorTest extends TestCase {
// Reset the stats. Since the sensor is still running, we should still see the timer
// but still with 0 times.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
@@ -504,7 +504,7 @@ public class BatteryStatsSensorTest extends TestCase {
// Reset the stats. Since the sensor is still running, we should still see the timer
// but with 0 times.
- bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000);
+ bi.getUidStatsLocked(UID).reset(clocks.uptime * 1000, clocks.realtime * 1000, 0);
assertEquals(0, timer.getTotalTimeLocked(1000*clocks.realtime, which));
assertEquals(0, timer.getTotalDurationMsLocked(clocks.realtime));
assertEquals(0, bgTimer.getTotalTimeLocked(1000*clocks.realtime, which));
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
index 0147cdb186f3..74b6dbe76a16 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsProviderTest.java
@@ -18,6 +18,9 @@ package com.android.internal.os;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
import android.app.ActivityManager;
import android.content.Context;
import android.os.BatteryConsumer;
@@ -263,6 +266,39 @@ public class BatteryUsageStatsProviderTest {
.of(180.0);
}
+ @Test
+ public void testAggregateBatteryStats_incompatibleSnapshot() {
+ Context context = InstrumentationRegistry.getContext();
+ MockBatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
+ batteryStats.initMeasuredEnergyStats(new String[]{"FOO", "BAR"});
+
+ BatteryUsageStatsStore batteryUsageStatsStore = mock(BatteryUsageStatsStore.class);
+
+ when(batteryUsageStatsStore.listBatteryUsageStatsTimestamps())
+ .thenReturn(new long[]{1000, 2000});
+
+ when(batteryUsageStatsStore.loadBatteryUsageStats(1000)).thenReturn(
+ new BatteryUsageStats.Builder(batteryStats.getCustomEnergyConsumerNames())
+ .setStatsDuration(1234).build());
+
+ // Add a snapshot, with a different set of custom power components. It should
+ // be skipped by the aggregation.
+ when(batteryUsageStatsStore.loadBatteryUsageStats(2000)).thenReturn(
+ new BatteryUsageStats.Builder(new String[]{"different"})
+ .setStatsDuration(4321).build());
+
+ BatteryUsageStatsProvider provider = new BatteryUsageStatsProvider(context,
+ batteryStats, batteryUsageStatsStore);
+
+ BatteryUsageStatsQuery query = new BatteryUsageStatsQuery.Builder()
+ .aggregateSnapshots(0, 3000)
+ .build();
+ final BatteryUsageStats stats = provider.getBatteryUsageStats(query);
+ assertThat(stats.getCustomPowerComponentNames())
+ .isEqualTo(batteryStats.getCustomEnergyConsumerNames());
+ assertThat(stats.getStatsDuration()).isEqualTo(1234);
+ }
+
private static class TestHandler extends Handler {
TestHandler() {
super(Looper.getMainLooper());
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 99d576d259ec..cee1a0352a7e 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -57,11 +57,10 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl {
this(new MockClocks());
}
- public void initMeasuredEnergyStats() {
+ public void initMeasuredEnergyStats(String[] customBucketNames) {
final boolean[] supportedStandardBuckets =
new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
Arrays.fill(supportedStandardBuckets, true);
- final String[] customBucketNames = {"FOO", "BAR"};
mGlobalMeasuredEnergyStats =
new MeasuredEnergyStats(supportedStandardBuckets, customBucketNames);
}
diff --git a/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
new file mode 100644
index 000000000000..cfe660c77817
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/ContrastColorUtilTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.util;
+
+import static androidx.core.graphics.ColorUtils.calculateContrast;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+
+import androidx.test.filters.SmallTest;
+
+import junit.framework.TestCase;
+
+public class ContrastColorUtilTest extends TestCase {
+
+ @SmallTest
+ public void testEnsureTextContrastAgainstDark() {
+ int darkBg = 0xFF35302A;
+
+ int blueContrastColor = ContrastColorUtil.ensureTextContrast(Color.BLUE, darkBg, true);
+ assertContrastIsWithinRange(blueContrastColor, darkBg, 4.5, 4.75);
+
+ int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, darkBg, true);
+ assertContrastIsWithinRange(redContrastColor, darkBg, 4.5, 4.75);
+
+ final int darkGreen = 0xff008800;
+ int greenContrastColor = ContrastColorUtil.ensureTextContrast(darkGreen, darkBg, true);
+ assertContrastIsWithinRange(greenContrastColor, darkBg, 4.5, 4.75);
+
+ int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.DKGRAY, darkBg, true);
+ assertContrastIsWithinRange(grayContrastColor, darkBg, 4.5, 4.75);
+
+ int selfContrastColor = ContrastColorUtil.ensureTextContrast(darkBg, darkBg, true);
+ assertContrastIsWithinRange(selfContrastColor, darkBg, 4.5, 4.75);
+ }
+
+ @SmallTest
+ public void testEnsureTextContrastAgainstLight() {
+ int lightBg = 0xFFFFF8F2;
+
+ final int lightBlue = 0xff8888ff;
+ int blueContrastColor = ContrastColorUtil.ensureTextContrast(lightBlue, lightBg, false);
+ assertContrastIsWithinRange(blueContrastColor, lightBg, 4.5, 4.75);
+
+ int redContrastColor = ContrastColorUtil.ensureTextContrast(Color.RED, lightBg, false);
+ assertContrastIsWithinRange(redContrastColor, lightBg, 4.5, 4.75);
+
+ int greenContrastColor = ContrastColorUtil.ensureTextContrast(Color.GREEN, lightBg, false);
+ assertContrastIsWithinRange(greenContrastColor, lightBg, 4.5, 4.75);
+
+ int grayContrastColor = ContrastColorUtil.ensureTextContrast(Color.LTGRAY, lightBg, false);
+ assertContrastIsWithinRange(grayContrastColor, lightBg, 4.5, 4.75);
+
+ int selfContrastColor = ContrastColorUtil.ensureTextContrast(lightBg, lightBg, false);
+ assertContrastIsWithinRange(selfContrastColor, lightBg, 4.5, 4.75);
+ }
+
+ public static void assertContrastIsWithinRange(int foreground, int background,
+ double minContrast, double maxContrast) {
+ assertContrastIsAtLeast(foreground, background, minContrast);
+ assertContrastIsAtMost(foreground, background, maxContrast);
+ }
+
+ public static void assertContrastIsAtLeast(int foreground, int background, double minContrast) {
+ try {
+ assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast);
+ } catch (AssertionError e) {
+ throw new AssertionError(
+ String.format("Insufficient contrast: foreground=#%08x background=#%08x",
+ foreground, background), e);
+ }
+ }
+
+ public static void assertContrastIsAtMost(int foreground, int background, double maxContrast) {
+ try {
+ assertThat(calculateContrast(foreground, background)).isAtMost(maxContrast);
+ } catch (AssertionError e) {
+ throw new AssertionError(
+ String.format("Excessive contrast: foreground=#%08x background=#%08x",
+ foreground, background), e);
+ }
+ }
+
+}
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea23aba16d12..2c93d5abc155 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -8,8 +8,6 @@ jsharkey@google.com
lorenzo@google.com
svetoslavganov@android.com
svetoslavganov@google.com
-toddke@android.com
-toddke@google.com
patb@google.com
yamasani@google.com
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index 40dda6573ccc..eeb65ae8e129 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -48,6 +48,7 @@
<permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission name="android.car.permission.CONTROL_APP_BLOCKING"/>
<!-- use for rotary fragment to enable/disable packages related to rotary -->
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 93a336e7a408..96b33259e739 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -25,6 +25,7 @@ import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
import android.text.FontConfig;
+import android.util.ArraySet;
import android.util.Xml;
import org.xmlpull.v1.XmlPullParser;
@@ -37,6 +38,7 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.regex.Pattern;
/**
@@ -120,7 +122,23 @@ public class FontListParser {
}
}
- private static FontConfig readFamilies(
+ /**
+ * Parses the familyset tag in font.xml
+ * @param parser a XML pull parser
+ * @param fontDir A system font directory, e.g. "/system/fonts"
+ * @param customization A OEM font customization
+ * @param updatableFontMap A map of updated font files
+ * @param lastModifiedDate A date that the system font is updated.
+ * @param configVersion A version of system font config.
+ * @param allowNonExistingFile true if allowing non-existing font files during parsing fonts.xml
+ * @return result of fonts.xml
+ *
+ * @throws XmlPullParserException
+ * @throws IOException
+ *
+ * @hide
+ */
+ public static FontConfig readFamilies(
@NonNull XmlPullParser parser,
@NonNull String fontDir,
@NonNull FontCustomizationParser.Result customization,
@@ -159,7 +177,24 @@ public class FontListParser {
}
families.addAll(oemNamedFamilies.values());
- return new FontConfig(families, aliases, lastModifiedDate, configVersion);
+
+ // Filters aliases that point to non-existing families.
+ Set<String> namedFamilies = new ArraySet<>();
+ for (int i = 0; i < families.size(); ++i) {
+ String name = families.get(i).getName();
+ if (name != null) {
+ namedFamilies.add(name);
+ }
+ }
+ List<FontConfig.Alias> filtered = new ArrayList<>();
+ for (int i = 0; i < aliases.size(); ++i) {
+ FontConfig.Alias alias = aliases.get(i);
+ if (namedFamilies.contains(alias.getOriginal())) {
+ filtered.add(alias);
+ }
+ }
+
+ return new FontConfig(families, filtered, lastModifiedDate, configVersion);
}
private static boolean keepReading(XmlPullParser parser)
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 5b79d76dd6b9..9feb619b34e3 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -178,22 +178,8 @@ public class ImageFormat {
* <p>Android YUV P010 format.</p>
*
* P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
- * followed immediately by a Wx(H/2) CbCr plane. Each sample is
- * represented by a 16-bit little-endian value, with the lower 6 bits set
- * to zero.
- *
- * <p>This format assumes
- * <ul>
- * <li>an even height</li>
- * <li>a vertical stride equal to the height</li>
- * </ul>
- * </p>
- *
- * <pre> stride_in_bytes = stride * 2 </pre>
- * <pre> y_size = stride_in_bytes * height </pre>
- * <pre> cbcr_size = stride_in_bytes * (height / 2) </pre>
- * <pre> cb_offset = y_size </pre>
- * <pre> cr_offset = cb_offset + 2 </pre>
+ * followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
+ * little-endian value, with the lower 6 bits set to zero.
*
* <p>For example, the {@link android.media.Image} object can provide data
* in this format from a {@link android.hardware.camera2.CameraDevice}
@@ -841,7 +827,7 @@ public class ImageFormat {
case RAW_SENSOR:
return 16;
case YCBCR_P010:
- return 20;
+ return 24;
case RAW_DEPTH10:
case RAW10:
return 10;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index ba0ab6db1003..656bdff0c782 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -336,6 +336,13 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
@Override
+ public void onImeDrawnOnTask(int taskId) {
+ if (mStartingWindow != null) {
+ mStartingWindow.onImeDrawnOnTask(taskId);
+ }
+ }
+
+ @Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index c0df06f2954f..ac97c8f80617 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -576,20 +576,17 @@ public class BubbleStackView extends FrameLayout
mBubbleContainer.setActiveController(mStackAnimationController);
hideFlyoutImmediate();
- if (!mPositioner.showingInTaskbar()) {
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(
- mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
- } else {
+ if (mPositioner.showingInTaskbar()) {
// In taskbar, the stack isn't draggable so we shouldn't dispatch touch events.
mMagnetizedObject = null;
+ } else {
+ // Save the magnetized stack so we can dispatch touch events to it.
+ mMagnetizedObject = mStackAnimationController.getMagnetizedStack();
+ mMagnetizedObject.clearAllTargets();
+ mMagnetizedObject.addTarget(mMagneticTarget);
+ mMagnetizedObject.setMagnetListener(mStackMagnetListener);
}
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-
mIsDraggingStack = true;
// Cancel animations to make the stack temporarily invisible, since we're now
@@ -881,7 +878,6 @@ public class BubbleStackView extends FrameLayout
mRelativeStackPositionBeforeRotation = null;
}
- setUpDismissView();
if (mIsExpanded) {
// Re-draw bubble row and pointer for new orientation.
beforeExpandedViewAnimation();
@@ -1043,10 +1039,9 @@ public class BubbleStackView extends FrameLayout
contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
// Save the MagneticTarget instance for the newly set up view - we'll add this to the
- // MagnetizedObjects.
+ // MagnetizedObjects when the dismiss view gets shown.
mMagneticTarget = new MagnetizedObject.MagneticTarget(
mDismissView.getCircle(), dismissRadius);
-
mBubbleContainer.bringToFront();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 0802fb59a008..636e1452aa9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -1024,11 +1024,9 @@ public class StackAnimationController extends
}
/**
- * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
- * {@link MagnetizedObject.MagneticTarget} added as a target.
+ * Returns the {@link MagnetizedObject} instance for the bubble stack.
*/
- public MagnetizedObject<StackAnimationController> getMagnetizedStack(
- MagnetizedObject.MagneticTarget target) {
+ public MagnetizedObject<StackAnimationController> getMagnetizedStack() {
if (mMagnetizedStack == null) {
mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
mLayout.getContext(),
@@ -1053,7 +1051,6 @@ public class StackAnimationController extends
loc[1] = (int) mStackPosition.y;
}
};
- mMagnetizedStack.addTarget(target);
mMagnetizedStack.setHapticsEnabled(true);
mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
index 9f6dd1f27b62..9e012598554b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
@@ -303,6 +303,13 @@ abstract class MagnetizedObject<T : Any>(
}
/**
+ * Removes all associated targets from this object.
+ */
+ fun clearAllTargets() {
+ associatedTargets.clear()
+ }
+
+ /**
* Provide this method with all motion events that move the magnetized object. If the
* location of the motion events moves within the magnetic field of a target, or indicate a
* fling-to-target gesture, this method will return true and you should not move the object
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
index d3274706631b..9e1c61aac868 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedBackgroundPanelOrganizer.java
@@ -23,6 +23,7 @@ import android.content.Context;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.animation.LinearInterpolator;
@@ -33,7 +34,6 @@ import android.window.DisplayAreaOrganizer;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import androidx.appcompat.view.ContextThemeWrapper;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 954ca14b4960..e511bffad247 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -445,6 +445,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>
mOneHandedSettingsUtil.registerSettingsKeyObserver(
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
+ mOneHandedSettingsUtil.registerSettingsKeyObserver(
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+ mContext.getContentResolver(), mShortcutEnabledObserver, newUserId);
}
private void unregisterSettingObservers() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
index 7cf4fb7a811d..ff333c8c659d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java
@@ -171,9 +171,22 @@ public final class OneHandedSettingsUtil {
* @return true if user enabled one-handed shortcut in settings, false otherwise.
*/
public boolean getShortcutEnabled(ContentResolver resolver, int userId) {
- final String targets = Settings.Secure.getStringForUser(resolver,
+ // Checks SOFTWARE_SHORTCUT_KEY
+ final String targetsSwKey = Settings.Secure.getStringForUser(resolver,
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, userId);
- return TextUtils.isEmpty(targets) ? false : targets.contains(ONE_HANDED_MODE_TARGET_NAME);
+ if (!TextUtils.isEmpty(targetsSwKey) && targetsSwKey.contains(
+ ONE_HANDED_MODE_TARGET_NAME)) {
+ return true;
+ }
+
+ // Checks HARDWARE_SHORTCUT_KEY
+ final String targetsHwKey = Settings.Secure.getStringForUser(resolver,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+ if (!TextUtils.isEmpty(targetsHwKey) && targetsHwKey.contains(
+ ONE_HANDED_MODE_TARGET_NAME)) {
+ return true;
+ }
+ return false;
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
index f58c6b173af9..81dd60d715e9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedTutorialHandler.java
@@ -32,6 +32,7 @@ import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Rect;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -44,7 +45,6 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
-import androidx.appcompat.view.ContextThemeWrapper;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;
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 4f3ec96968b2..63f1985aa86e 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
@@ -303,13 +303,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
mTaskStackListener = taskStackListener;
- mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
- INPUT_CONSUMER_PIP, mainExecutor);
//TODO: move this to ShellInit when PipController can be injected
mMainExecutor.execute(this::init);
}
public void init() {
+ mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
+ INPUT_CONSUMER_PIP, mMainExecutor);
mPipTransitionController.registerPipTransitionCallback(this);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipBoundsState.setDisplayId(displayId);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
index a5e96d14dde6..20021ebea834 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUILayout.java
@@ -28,6 +28,7 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
+import android.util.Log;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -45,7 +46,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
class SizeCompatUILayout {
private static final String TAG = "SizeCompatUILayout";
- private final SyncTransactionQueue mSyncQueue;
+ final SyncTransactionQueue mSyncQueue;
private final SizeCompatUIController.SizeCompatUICallback mCallback;
private Context mContext;
private Configuration mTaskConfig;
@@ -306,6 +307,10 @@ class SizeCompatUILayout {
private void updateSurfacePosition(SurfaceControl leash, int positionX, int positionY) {
mSyncQueue.runInSync(t -> {
+ if (!leash.isValid()) {
+ Log.w(TAG, "The leash has been released.");
+ return;
+ }
t.setPosition(leash, positionX, positionY);
// The size compat UI should be the topmost child of the Task in case there can be more
// than one children.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
index f634c4586e39..82f69c3e2985 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sizecompatui/SizeCompatUIWindowManager.java
@@ -110,7 +110,8 @@ class SizeCompatUIWindowManager extends WindowlessWindowManager {
}
if (mLeash != null) {
- new SurfaceControl.Transaction().remove(mLeash).apply();
+ final SurfaceControl leash = mLeash;
+ mLayout.mSyncQueue.runInSync(t -> t.remove(leash));
mLeash = null;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index fc7c86d669cb..147f5e30f9d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -545,6 +545,15 @@ public class StartingSurfaceDrawer {
removeWindowSynced(taskId, null, null, false);
}
+ void onImeDrawnOnTask(int taskId) {
+ final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+ if (record != null && record.mTaskSnapshotWindow != null
+ && record.mTaskSnapshotWindow.hasImeSurface()) {
+ record.mTaskSnapshotWindow.removeImmediately();
+ }
+ mStartingWindowRecords.remove(taskId);
+ }
+
protected void removeWindowSynced(int taskId, SurfaceControl leash, Rect frame,
boolean playRevealAnimation) {
final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
@@ -572,14 +581,15 @@ public class StartingSurfaceDrawer {
Slog.e(TAG, "Found empty splash screen, remove!");
removeWindowInner(record.mDecorView, false);
}
+ mStartingWindowRecords.remove(taskId);
}
if (record.mTaskSnapshotWindow != null) {
if (DEBUG_TASK_SNAPSHOT) {
Slog.v(TAG, "Removing task snapshot window for " + taskId);
}
- record.mTaskSnapshotWindow.remove();
+ record.mTaskSnapshotWindow.scheduleRemove(
+ () -> mStartingWindowRecords.remove(taskId));
}
- mStartingWindowRecords.remove(taskId);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index e84d498a9258..dee21b093dce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -177,6 +177,13 @@ public class StartingWindowController implements RemoteCallable<StartingWindowCo
}
/**
+ * Called when the IME has drawn on the organized task.
+ */
+ public void onImeDrawnOnTask(int taskId) {
+ mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId));
+ }
+
+ /**
* Called when the content of a task is ready to show, starting window can be removed.
*/
public void removeStartingWindow(int taskId, SurfaceControl leash, Rect frame,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 6052d3dee891..dfb1ae3ef2a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -64,7 +64,6 @@ import android.graphics.RectF;
import android.hardware.HardwareBuffer;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.util.MergedConfiguration;
import android.util.Slog;
@@ -119,7 +118,12 @@ public class TaskSnapshotWindow {
private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=%s";
private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
- private static final long DELAY_REMOVAL_TIME_IME_VISIBLE = 350;
+ /**
+ * The max delay time in milliseconds for removing the task snapshot window with IME visible.
+ * Ideally the delay time will be shorter when receiving
+ * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+ */
+ private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
//tmp vars for unused relayout params
private static final Point TMP_SURFACE_SIZE = new Point();
@@ -138,7 +142,6 @@ public class TaskSnapshotWindow {
private final RectF mTmpDstFrame = new RectF();
private final CharSequence mTitle;
private boolean mHasDrawn;
- private long mShownTime;
private boolean mSizeMismatch;
private final Paint mBackgroundPaint = new Paint();
private final int mActivityType;
@@ -148,6 +151,8 @@ public class TaskSnapshotWindow {
private final SurfaceControl.Transaction mTransaction;
private final Matrix mSnapshotMatrix = new Matrix();
private final float[] mTmpFloat9 = new float[9];
+ private Runnable mScheduledRunnable;
+ private final boolean mHasImeSurface;
static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
@@ -216,7 +221,7 @@ public class TaskSnapshotWindow {
taskDescription.setBackgroundColor(WHITE);
}
- final long delayRemovalTime = snapshot.hasImeSurface() ? DELAY_REMOVAL_TIME_IME_VISIBLE
+ final long delayRemovalTime = snapshot.hasImeSurface() ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
: DELAY_REMOVAL_TIME_GENERAL;
final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
@@ -281,12 +286,17 @@ public class TaskSnapshotWindow {
mDelayRemovalTime = delayRemovalTime;
mTransaction = new SurfaceControl.Transaction();
mClearWindowHandler = clearWindowHandler;
+ mHasImeSurface = snapshot.hasImeSurface();
}
int getBackgroundColor() {
return mBackgroundPaint.getColor();
}
+ boolean hasImeSurface() {
+ return mHasImeSurface;
+ }
+
/**
* Ask system bar background painter to draw status bar background.
* @hide
@@ -304,21 +314,32 @@ public class TaskSnapshotWindow {
mSystemBarBackgroundPainter.drawNavigationBarBackground(c);
}
- void remove() {
- final long now = SystemClock.uptimeMillis();
- if ((now - mShownTime < mDelayRemovalTime)
- // Show the latest content as soon as possible for unlocking to home.
- && mActivityType != ACTIVITY_TYPE_HOME) {
- final long delayTime = mShownTime + mDelayRemovalTime - now;
- mSplashScreenExecutor.executeDelayed(() -> remove(), delayTime);
- if (DEBUG) {
- Slog.d(TAG, "Defer removing snapshot surface in " + delayTime);
- }
+ void scheduleRemove(Runnable onRemove) {
+ // Show the latest content as soon as possible for unlocking to home.
+ if (mActivityType == ACTIVITY_TYPE_HOME) {
+ removeImmediately();
+ onRemove.run();
return;
}
+ if (mScheduledRunnable != null) {
+ mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
+ mScheduledRunnable = null;
+ }
+ mScheduledRunnable = () -> {
+ TaskSnapshotWindow.this.removeImmediately();
+ onRemove.run();
+ };
+ mSplashScreenExecutor.executeDelayed(mScheduledRunnable, mDelayRemovalTime);
+ if (DEBUG) {
+ Slog.d(TAG, "Defer removing snapshot surface in " + mDelayRemovalTime);
+ }
+ }
+
+ void removeImmediately() {
+ mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
try {
if (DEBUG) {
- Slog.d(TAG, "Removing snapshot surface, mHasDrawn: " + mHasDrawn);
+ Slog.d(TAG, "Removing taskSnapshot surface, mHasDrawn: " + mHasDrawn);
}
mSession.remove(mWindow);
} catch (RemoteException e) {
@@ -356,7 +377,6 @@ public class TaskSnapshotWindow {
} else {
drawSizeMatchSnapshot();
}
- mShownTime = SystemClock.uptimeMillis();
mHasDrawn = true;
reportDrawn();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index d536adb9f8ae..eef0d9bb268f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -15,38 +15,53 @@
*/
package com.android.wm.shell.startingsurface;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.graphics.ColorSpace;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.TestableContext;
+import android.view.IWindowSession;
+import android.view.InsetsState;
+import android.view.Surface;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.WindowMetrics;
import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -61,6 +76,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
import java.util.function.IntSupplier;
@@ -78,6 +94,7 @@ public class StartingSurfaceDrawerTests {
private TransactionPool mTransactionPool;
private final Handler mTestHandler = new Handler(Looper.getMainLooper());
+ private ShellExecutor mTestExecutor;
private final TestableContext mTestContext = new TestContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
TestStartingSurfaceDrawer mStartingSurfaceDrawer;
@@ -138,9 +155,9 @@ public class StartingSurfaceDrawerTests {
doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
doNothing().when(mMockWindowManager).addView(any(), any());
-
- mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(mTestContext,
- new HandlerExecutor(mTestHandler), mTransactionPool));
+ mTestExecutor = new HandlerExecutor(mTestHandler);
+ mStartingSurfaceDrawer = spy(
+ new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mTransactionPool));
}
@Test
@@ -205,6 +222,48 @@ public class StartingSurfaceDrawerTests {
assertEquals(0, windowColor3.mReuseCount);
}
+ @Test
+ public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
+ final int taskId = 1;
+ final StartingWindowInfo windowInfo =
+ createWindowInfo(taskId, android.R.style.Theme);
+ TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
+ new Rect(0, 0, 0, 50), true /* hasImeSurface */);
+ final IWindowSession session = WindowManagerGlobal.getWindowSession();
+ spyOn(session);
+ doReturn(WindowManagerGlobal.ADD_OKAY).when(session).addToDisplay(
+ any() /* window */, any() /* attrs */,
+ anyInt() /* viewVisibility */, anyInt() /* displayId */,
+ any() /* requestedVisibility */, any() /* outInputChannel */,
+ any() /* outInsetsState */, any() /* outActiveControls */);
+ TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
+ mBinder,
+ snapshot, mTestExecutor, () -> {
+ });
+ spyOn(mockSnapshotWindow);
+ try (AutoCloseable mockTaskSnapshotSession = new AutoCloseable() {
+ MockitoSession mockSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(TaskSnapshotWindow.class)
+ .startMocking();
+ @Override
+ public void close() {
+ mockSession.finishMocking();
+ }
+ }) {
+ when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
+ any())).thenReturn(mockSnapshotWindow);
+ // Simulate a task snapshot window created with IME snapshot shown.
+ mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+ waitHandlerIdle(mTestHandler);
+
+ // Verify the task snapshot with IME snapshot will be removed when received the real IME
+ // drawn callback.
+ mStartingSurfaceDrawer.onImeDrawnOnTask(1);
+ verify(mockSnapshotWindow).removeImmediately();
+ }
+ }
+
private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
StartingWindowInfo windowInfo = new StartingWindowInfo();
final ActivityInfo info = new ActivityInfo();
@@ -216,10 +275,27 @@ public class StartingSurfaceDrawerTests {
taskInfo.taskId = taskId;
windowInfo.targetActivityInfo = info;
windowInfo.taskInfo = taskInfo;
+ windowInfo.topOpaqueWindowInsetsState = new InsetsState();
+ windowInfo.mainWindowLayoutParams = new WindowManager.LayoutParams();
+ windowInfo.topOpaqueWindowLayoutParams = new WindowManager.LayoutParams();
return windowInfo;
}
private static void waitHandlerIdle(Handler handler) {
handler.runWithScissors(() -> { }, 0 /* timeout */);
}
+
+ private TaskSnapshot createTaskSnapshot(int width, int height, Point taskSize,
+ Rect contentInsets, boolean hasImeSurface) {
+ final HardwareBuffer buffer = HardwareBuffer.create(width, height, HardwareBuffer.RGBA_8888,
+ 1, HardwareBuffer.USAGE_CPU_READ_RARELY);
+ return new TaskSnapshot(
+ System.currentTimeMillis(),
+ new ComponentName("", ""), buffer,
+ ColorSpace.get(ColorSpace.Named.SRGB), ORIENTATION_PORTRAIT,
+ Surface.ROTATION_0, taskSize, contentInsets, false,
+ true /* isRealSnapshot */, WINDOWING_MODE_FULLSCREEN,
+ 0 /* systemUiVisibility */, false /* isTranslucent */,
+ hasImeSurface /* hasImeSurface */);
+ }
}
diff --git a/libs/androidfw/OWNERS b/libs/androidfw/OWNERS
index 17f5164cf417..38b642501fcd 100644
--- a/libs/androidfw/OWNERS
+++ b/libs/androidfw/OWNERS
@@ -1,5 +1,4 @@
set noparent
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index c804418e8380..513ad9aab9ff 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -50,6 +50,7 @@ bool Properties::showDirtyRegions = false;
bool Properties::skipEmptyFrames = true;
bool Properties::useBufferAge = true;
bool Properties::enablePartialUpdates = true;
+bool Properties::enableRenderEffectCache = false;
DebugLevel Properties::debugLevel = kDebugDisabled;
OverdrawColorSet Properties::overdrawColorSet = OverdrawColorSet::Default;
@@ -84,7 +85,7 @@ float Properties::defaultSdrWhitePoint = 200.f;
bool Properties::useHintManager = true;
int Properties::targetCpuTimePercentage = 70;
-bool Properties::enableWebViewOverlays = false;
+bool Properties::enableWebViewOverlays = true;
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
@@ -141,7 +142,7 @@ bool Properties::load() {
targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
- enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, false);
+ enableWebViewOverlays = base::GetBoolProperty(PROPERTY_WEBVIEW_OVERLAYS_ENABLED, true);
// call isDrawingEnabled to force loading of the property
isDrawingEnabled();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 7f9782bf8d20..2f8c67903a8b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -232,6 +232,7 @@ public:
static bool skipEmptyFrames;
static bool useBufferAge;
static bool enablePartialUpdates;
+ static bool enableRenderEffectCache;
// TODO: Move somewhere else?
static constexpr float textGamma = 1.45f;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 0c422df65881..b348a6ecaae4 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -341,6 +341,7 @@ std::optional<RenderNode::SnapshotResult> RenderNode::updateSnapshotIfRequired(
sk_sp<SkImage> snapshot = layerSurface->makeImageSnapshot();
const auto subset = SkIRect::MakeWH(properties().getWidth(),
properties().getHeight());
+ uint32_t layerSurfaceGenerationId = layerSurface->generationID();
// If we don't have an ImageFilter just return the snapshot
if (imageFilter == nullptr) {
mSnapshotResult.snapshot = snapshot;
@@ -348,9 +349,10 @@ std::optional<RenderNode::SnapshotResult> RenderNode::updateSnapshotIfRequired(
mSnapshotResult.outOffset = SkIPoint::Make(0.0f, 0.0f);
mImageFilterClipBounds = clipBounds;
mTargetImageFilter = nullptr;
- } else if (mSnapshotResult.snapshot == nullptr ||
- imageFilter != mTargetImageFilter.get() ||
- mImageFilterClipBounds != clipBounds) {
+ mTargetImageFilterLayerSurfaceGenerationId = 0;
+ } else if (mSnapshotResult.snapshot == nullptr || imageFilter != mTargetImageFilter.get() ||
+ mImageFilterClipBounds != clipBounds ||
+ mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
// Otherwise create a new snapshot with the given filter and snapshot
mSnapshotResult.snapshot =
snapshot->makeWithFilter(context,
@@ -361,6 +363,7 @@ std::optional<RenderNode::SnapshotResult> RenderNode::updateSnapshotIfRequired(
&mSnapshotResult.outOffset);
mTargetImageFilter = sk_ref_sp(imageFilter);
mImageFilterClipBounds = clipBounds;
+ mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
}
return mSnapshotResult;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 45a4f6c9c70d..da0476259b97 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -396,6 +396,7 @@ private:
* SkImageFilter used to create the mSnapshotResult
*/
sk_sp<SkImageFilter> mTargetImageFilter;
+ uint32_t mTargetImageFilterLayerSurfaceGenerationId = 0;
/**
* Clip bounds used to create the mSnapshotResult
diff --git a/libs/hwui/WebViewFunctorManager.cpp b/libs/hwui/WebViewFunctorManager.cpp
index df4101109a18..5aad821ad59f 100644
--- a/libs/hwui/WebViewFunctorManager.cpp
+++ b/libs/hwui/WebViewFunctorManager.cpp
@@ -100,6 +100,9 @@ WebViewFunctor::~WebViewFunctor() {
destroyContext();
ATRACE_NAME("WebViewFunctor::onDestroy");
+ if (mSurfaceControl) {
+ removeOverlays();
+ }
mCallbacks.onDestroyed(mFunctor, mData);
}
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 7556af918170..48145d2331ee 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -231,14 +231,33 @@ void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
SkASSERT(properties.effectiveLayerType() == LayerType::RenderLayer);
SkPaint paint;
layerNeedsPaint(layerProperties, alphaMultiplier, &paint);
- const auto snapshotResult = renderNode->updateSnapshotIfRequired(
- canvas->recordingContext(),
- layerProperties.getImageFilter(),
- clipBounds.roundOut()
- );
- sk_sp<SkImage> snapshotImage = snapshotResult->snapshot;
- srcBounds = snapshotResult->outSubset;
- offset = snapshotResult->outOffset;
+ sk_sp<SkImage> snapshotImage;
+ auto* imageFilter = layerProperties.getImageFilter();
+ auto recordingContext = canvas->recordingContext();
+ // On some GL vendor implementations, caching the result of
+ // getLayerSurface->makeImageSnapshot() causes a call to
+ // Fence::waitForever without a corresponding signal. This would
+ // lead to ANRs throughout the system.
+ // Instead only cache the SkImage created with the SkImageFilter
+ // for supported devices. Otherwise just create a new SkImage with
+ // the corresponding SkImageFilter each time.
+ // See b/193145089 and b/197263715
+ if (!Properties::enableRenderEffectCache) {
+ snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
+ if (imageFilter) {
+ auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
+ snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
+ subset, clipBounds.roundOut(),
+ &srcBounds, &offset);
+ }
+ } else {
+ const auto snapshotResult = renderNode->updateSnapshotIfRequired(
+ recordingContext, layerProperties.getImageFilter(), clipBounds.roundOut());
+ snapshotImage = snapshotResult->snapshot;
+ srcBounds = snapshotResult->outSubset;
+ offset = snapshotResult->outOffset;
+ }
+
const auto dstBounds = SkIRect::MakeXYWH(offset.x(),
offset.y(),
srcBounds.width(),
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index a11678189bad..383c79b27918 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -146,6 +146,9 @@ void EglManager::initialize() {
LOG_ALWAYS_FATAL("Unsupported wide color space.");
}
mHasWideColorGamutSupport = EglExtensions.glColorSpace && hasWideColorSpaceExtension;
+
+ auto* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
+ Properties::enableRenderEffectCache = (strcmp(vendor, "Qualcomm") != 0);
}
EGLConfig EglManager::load8BitsConfig(EGLDisplay display, EglManager::SwapBehavior swapBehavior) {
diff --git a/location/java/android/location/GpsNavigationMessage.java b/location/java/android/location/GpsNavigationMessage.java
index dc1e99fd6a4f..2b978f759a13 100644
--- a/location/java/android/location/GpsNavigationMessage.java
+++ b/location/java/android/location/GpsNavigationMessage.java
@@ -262,12 +262,8 @@ public class GpsNavigationMessage implements Parcelable {
parcel.readByteArray(data);
navigationMessage.setData(data);
- if (parcel.dataAvail() >= Integer.SIZE) {
- int status = parcel.readInt();
- navigationMessage.setStatus((short) status);
- } else {
- navigationMessage.setStatus(STATUS_UNKNOWN);
- }
+ int status = parcel.readInt();
+ navigationMessage.setStatus((short) status);
return navigationMessage;
}
diff --git a/media/java/android/media/IMediaRouterClient.aidl b/media/java/android/media/IMediaRouterClient.aidl
index 6b754e157cfb..9b4912373122 100644
--- a/media/java/android/media/IMediaRouterClient.aidl
+++ b/media/java/android/media/IMediaRouterClient.aidl
@@ -23,5 +23,4 @@ oneway interface IMediaRouterClient {
void onStateChanged();
void onRestoreRoute();
void onGroupRouteSelected(String routeId);
- void onGlobalA2dpChanged(boolean a2dpOn);
}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index 345d9b27c8a8..2986f7c75f4d 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -654,9 +654,12 @@ public class MediaRouter {
final class Client extends IMediaRouterClient.Stub {
@Override
public void onStateChanged() {
- mHandler.post(() -> {
- if (Client.this == mClient) {
- updateClientState();
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (Client.this == mClient) {
+ updateClientState();
+ }
}
});
}
@@ -690,26 +693,6 @@ public class MediaRouter {
}
});
}
-
- // Called when the selection of a connected device (phone speaker or BT devices)
- // is changed.
- @Override
- public void onGlobalA2dpChanged(boolean a2dpOn) {
- mHandler.post(() -> {
- if (mSelectedRoute == null || mBluetoothA2dpRoute == null) {
- return;
- }
- if (mSelectedRoute.isDefault() && a2dpOn) {
- setSelectedRoute(mBluetoothA2dpRoute, /*explicit=*/ false);
- dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
- dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
- } else if (mSelectedRoute.isBluetooth() && !a2dpOn) {
- setSelectedRoute(mDefaultAudioVideo, /*explicit=*/ false);
- dispatchRouteUnselected(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
- dispatchRouteSelected(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
- }
- });
- }
}
}
@@ -1367,9 +1350,6 @@ public class MediaRouter {
}
static void dispatchRouteSelected(int type, RouteInfo info) {
- if (DEBUG) {
- Log.d(TAG, "Dispatching route selected: " + info);
- }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteSelected(cbi.router, type, info);
@@ -1378,9 +1358,6 @@ public class MediaRouter {
}
static void dispatchRouteUnselected(int type, RouteInfo info) {
- if (DEBUG) {
- Log.d(TAG, "Dispatching route unselected: " + info);
- }
for (CallbackInfo cbi : sStatic.mCallbacks) {
if (cbi.filterRouteEvent(info)) {
cbi.cb.onRouteUnselected(cbi.router, type, info);
diff --git a/native/android/OWNERS b/native/android/OWNERS
index 02dfd393a0b7..8b35f8d38691 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -1,7 +1,7 @@
jreck@google.com
per-file libandroid_net.map.txt, net.c = set noparent
-per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com
+per-file libandroid_net.map.txt, net.c = jchalard@google.com, junyulai@google.com
per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com
per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS
diff --git a/packages/CtsShim/OWNERS b/packages/CtsShim/OWNERS
index 94197715150d..eb631bc7cbcf 100644
--- a/packages/CtsShim/OWNERS
+++ b/packages/CtsShim/OWNERS
@@ -1,3 +1,2 @@
ioffe@google.com
-toddke@google.com
patb@google.com \ No newline at end of file
diff --git a/packages/PackageInstaller/AndroidManifest.xml b/packages/PackageInstaller/AndroidManifest.xml
index 48cdf1647d86..197b7b2b0eaf 100644
--- a/packages/PackageInstaller/AndroidManifest.xml
+++ b/packages/PackageInstaller/AndroidManifest.xml
@@ -31,7 +31,7 @@
android:directBootAware="true">
<receiver android:name=".TemporaryFileManager"
- android:exported="true">
+ android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
@@ -76,7 +76,7 @@
<receiver android:name=".InstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="com.android.packageinstaller.ACTION_INSTALL_COMMIT" />
</intent-filter>
@@ -106,14 +106,14 @@
<receiver android:name=".UninstallEventReceiver"
android:permission="android.permission.INSTALL_PACKAGES"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="com.android.packageinstaller.ACTION_UNINSTALL_COMMIT" />
</intent-filter>
</receiver>
<receiver android:name=".PackageInstalledReceiver"
- android:exported="true">
+ android:exported="false">
<intent-filter android:priority="1">
<action android:name="android.intent.action.PACKAGE_ADDED" />
<data android:scheme="package" />
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index 5d7b9bb36f75..cef9014ec229 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -19,6 +19,9 @@
},
{
"name": "CtsPackageUninstallTestCases"
+ },
+ {
+ "name": "PackageInstallerTests"
}
]
-} \ No newline at end of file
+}
diff --git a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
index 5f107d662b75..34e7e3d1cd6b 100644
--- a/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
+++ b/packages/PrintRecommendationService/src/com/android/printservice/recommendation/plugin/mdnsFilter/MDNSFilterPlugin.java
@@ -84,8 +84,7 @@ public class MDNSFilterPlugin implements PrintServicePlugin {
*/
public MDNSFilterPlugin(@NonNull Context context, @NonNull String name,
@NonNull CharSequence packageName, @NonNull List<String> mDNSNames) {
- mName = context.getResources().getIdentifier(name, null,
- "com.android.printservice.recommendation");
+ mName = context.getResources().getIdentifier(name, null, context.getPackageName());
mPackageName = packageName;
mMDNSFilteredDiscovery = new MDNSFilteredDiscovery(context, PRINTER_SERVICE_TYPES,
new VendorNameFilter(new HashSet<>(mDNSNames)));
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
index e74ac44ec8a4..fede44feb090 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v31/styles.xml
@@ -33,18 +33,18 @@
<style name="Banner.Title.SettingsLib"
parent="@android:style/TextAppearance.Material.Subhead">
<item name="android:textSize">20sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="Banner.Subtitle.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">14sp</item>
</style>
<style name="Banner.Summary.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">14sp</item>
</style>
@@ -58,4 +58,4 @@
parent="android:Widget.DeviceDefault.Button.Borderless.Colored">
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
-</resources> \ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
index df47c642e402..4c6ed58f4a58 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values/styles.xml
@@ -17,14 +17,13 @@
<resources>
<style name="Banner.Text.Title"
- parent="@android:style/TextAppearance.Material.Subhead">
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textSize">16sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="Banner.Text.Summary"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">14sp</item>
</style>
diff --git a/packages/SettingsLib/BarChartPreference/res/values/styles.xml b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
index 92514ad58b77..1c4420717188 100644
--- a/packages/SettingsLib/BarChartPreference/res/values/styles.xml
+++ b/packages/SettingsLib/BarChartPreference/res/values/styles.xml
@@ -87,9 +87,9 @@
</style>
<style name="BarChart.Text"
- parent="@android:style/TextAppearance.Material.Subhead">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">16sp</item>
</style>
<style name="BarChart.Text.HeaderTitle">
@@ -101,7 +101,7 @@
</style>
<style name="BarChart.Text.Summary"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textSize">12sp</item>
</style>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 2f911c4e6546..238e65ec9a3c 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -19,6 +19,7 @@ android_library {
"com.google.android.material_material",
"SettingsLibSettingsTransition",
"SettingsLibUtils",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
min_sdk_version: "29",
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
index 59506564400b..907863e19972 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_base_layout.xml
@@ -16,7 +16,6 @@
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content_parent"
android:layout_width="match_parent"
@@ -40,7 +39,7 @@
android:clipToPadding="false"
app:forceApplySystemWindowInsetTop="true"
app:extraMultilineHeightEnabled="true"
- app:contentScrim="?androidprv:attr/colorSurfaceHeader"
+ app:contentScrim="@color/settingslib_colorSurfaceHeader"
app:maxLines="3"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:scrimAnimationDuration="50"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
index 878275a08752..c20beaf9bf93 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml
@@ -18,7 +18,7 @@
<style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
<item name="elevationOverlayEnabled">true</item>
<item name="elevationOverlayColor">?attr/colorPrimary</item>
- <item name="colorPrimary">@*android:color/primary_dark_device_default_settings</item>
- <item name="colorAccent">@*android:color/accent_device_default_dark</item>
+ <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item>
+ <item name="colorAccent">@color/settingslib_accent_device_default_dark</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml
index 15c1abbf97ba..15c1abbf97ba 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/dimens.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/dimens.xml
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
index 63d397c69353..d0b6c4d54bb1 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/styles.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/styles.xml
@@ -16,11 +16,13 @@
-->
<resources>
<style name="CollapsingToolbarTitle.Collapsed" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textSize">20dp</item>
+ <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
</style>
<style name="CollapsingToolbarTitle.Expanded" parent="CollapsingToolbarTitle.Collapsed">
<item name="android:textSize">36dp</item>
+ <item name="android:textColor">@color/settingslib_text_color_primary_device_default</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
index 2e7a6a9181fe..9ecc297c6d36 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values/themes.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml
@@ -18,7 +18,7 @@
<style name="Theme.CollapsingToolbar.Settings" parent="@style/Theme.MaterialComponents.DayNight">
<item name="elevationOverlayEnabled">true</item>
<item name="elevationOverlayColor">?attr/colorPrimary</item>
- <item name="colorPrimary">@*android:color/primary_device_default_settings_light</item>
- <item name="colorAccent">@*android:color/accent_device_default_light</item>
+ <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item>
+ <item name="colorAccent">@color/settingslib_accent_device_default_light</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/FooterPreference/res/values/styles.xml b/packages/SettingsLib/FooterPreference/res/values/styles.xml
index 08dd35991f69..5a3bada3e594 100644
--- a/packages/SettingsLib/FooterPreference/res/values/styles.xml
+++ b/packages/SettingsLib/FooterPreference/res/values/styles.xml
@@ -17,9 +17,8 @@
<resources>
<style name="TextAppearance.Footer.Title.SettingsLib"
- parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textSize">14sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/LayoutPreference/res/values/styles.xml b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
index 4a99e845a5fc..2ffe6d91651b 100644
--- a/packages/SettingsLib/LayoutPreference/res/values/styles.xml
+++ b/packages/SettingsLib/LayoutPreference/res/values/styles.xml
@@ -24,14 +24,13 @@
</style>
<style name="TextAppearance.EntityHeaderTitle"
- parent="@android:style/TextAppearance.Material.Subhead">
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
+ parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.EntityHeaderSummary"
- parent="@*android:style/TextAppearance.DeviceDefault.Body1">
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textAlignment">viewStart</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:singleLine">true</item>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml
index 088e82bb4260..088e82bb4260 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_disabled.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_disabled.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml
index 088e82bb4260..088e82bb4260 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_off.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_off.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml
index 250188b892f4..250188b892f4 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/drawable/settingslib_switch_bar_bg_on.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/drawable-v31/settingslib_switch_bar_bg_on.xml
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index 6e5911cbf0a0..30748e6244cb 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -50,7 +50,7 @@
android:tint="?android:attr/colorAccent"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
- android:src="@*android:drawable/ic_info"
+ android:src="@android:drawable/ic_info"
android:visibility="gone" />
<Switch
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 306145a3e689..d0c2d0b5937d 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -28,7 +28,7 @@
android:layout_gravity="center_vertical"
android:maxLines="2"
android:ellipsize="end"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
+ android:textAppearance="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title"
android:textSize="16sp"
android:textColor="?android:attr/textColorPrimaryInverse"
android:layout_marginStart="@dimen/settingslib_switchbar_subsettings_margin_start"
@@ -42,7 +42,7 @@
android:theme="@android:style/Theme.Material"
android:layout_gravity="center_vertical"
android:layout_marginEnd="@dimen/settingslib_restricted_icon_margin_end"
- android:src="@*android:drawable/ic_info"
+ android:src="@android:drawable/ic_info"
android:visibility="gone"/>
<Switch
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
new file mode 100644
index 000000000000..2272a375fb83
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/dimens.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <!-- Size of layout margin -->
+ <dimen name="settingslib_switchbar_margin">16dp</dimen>
+
+ <!-- Size of layout margin left -->
+ <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
+
+ <!-- Size of layout margin right -->
+ <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
+
+ <!-- Minimum width of switch -->
+ <dimen name="settingslib_min_switch_width">52dp</dimen>
+
+ <!-- Minimum width of switch bar -->
+ <dimen name="settingslib_min_switch_bar_height">72dp</dimen>
+
+ <!-- Radius of switch bar -->
+ <dimen name="settingslib_switch_bar_radius">28dp</dimen>
+</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
new file mode 100644
index 000000000000..a50fc7cc0028
--- /dev/null
+++ b/packages/SettingsLib/MainSwitchPreference/res/values-v31/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+
+ <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
+ <item name="android:textSize">20sp</item>
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
+ <item name="android:textColor">@android:color/black</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 16b8af6a2dab..6362882e2332 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2020 The Android Open Source Project
+ 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.
@@ -17,30 +17,12 @@
<resources>
- <!-- Size of layout margin -->
- <dimen name="settingslib_switchbar_margin">16dp</dimen>
-
- <!-- Size of layout margin left -->
- <dimen name="settingslib_switchbar_padding_left">24dp</dimen>
-
- <!-- Size of layout margin right -->
- <dimen name="settingslib_switchbar_padding_right">16dp</dimen>
-
- <!-- Minimum width of switch -->
- <dimen name="settingslib_min_switch_width">52dp</dimen>
-
- <!-- Minimum width of switch bar -->
- <dimen name="settingslib_min_switch_bar_height">72dp</dimen>
-
<!-- Restricted icon size in switch bar -->
- <dimen name="settingslib_restricted_icon_size">@*android:dimen/config_restrictedIconSize</dimen>
+ <dimen name="settingslib_restricted_icon_size">@android:dimen/config_restrictedIconSize</dimen>
<!-- Restricted icon in switch bar -->
<dimen name="settingslib_restricted_icon_margin_end">16dp</dimen>
- <!-- Radius of switch bar -->
- <dimen name="settingslib_switch_bar_radius">28dp</dimen>
-
<!-- Size of title margin -->
<dimen name="settingslib_switch_title_margin">16dp</dimen>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
index 3924e301a2d3..870812ae6caf 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
- Copyright (C) 2020 The Android Open Source Project
+ 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.
@@ -17,13 +17,6 @@
<resources>
- <style name="MainSwitchText.Settingslib" parent="@android:style/TextAppearance.DeviceDefault.Widget.ActionBar.Title">
- <item name="android:textSize">20sp</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
- <item name="android:textColor">@android:color/black</item>
- </style>
-
-
<style name="SwitchBar.Switch.Settingslib" parent="@android:style/Widget.Material.CompoundButton.Switch">
<item name="android:trackTint">@color/settingslib_switchbar_switch_track_tint</item>
<item name="android:thumbTint">@color/settingslib_switchbar_switch_thumb_tint</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
new file mode 100644
index 000000000000..037b80abc6f9
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/color-v31/settingslib_surface_light.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/system_neutral1_500" android:lStar="98" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
index 8c7c7ed5b120..c20690342c19 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml
@@ -36,4 +36,11 @@
<color name="settingslib_dialog_colorError">#f28b82</color> <!-- Red 300 -->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral1_700</color>
+
+ <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_700</color>
+
+ <!-- copy from accent_primary_variant_dark_device_default-->
+ <color name="settingslib_accent_primary_variant">@android:color/system_accent1_300</color>
+
+ <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_50</color>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
index 77f1bcd17371..04010985fe74 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/colors.xml
@@ -37,9 +37,32 @@
<!-- Dialog accent color -->
<color name="settingslib_dialog_accent">@android:color/system_accent1_600</color>
<!-- Dialog background color -->
- <color name="settingslib_dialog_background">@*android:color/surface_light</color>
+ <color name="settingslib_dialog_background">@color/settingslib_surface_light</color>
<!-- Dialog error color. -->
<color name="settingslib_dialog_colorError">#d93025</color> <!-- Red 600 -->
<color name="settingslib_colorSurfaceVariant">@android:color/system_neutral2_100</color>
+
+ <color name="settingslib_colorSurfaceHeader">@android:color/system_neutral1_100</color>
+
+ <color name="settingslib_accent_device_default_dark">@android:color/system_accent1_100</color>
+
+ <color name="settingslib_accent_device_default_light">@android:color/system_accent1_600</color>
+
+ <color name="settingslib_primary_dark_device_default_settings">@android:color/system_neutral1_900</color>
+
+ <color name="settingslib_primary_device_default_settings_light">@android:color/system_neutral1_50</color>
+
+ <color name="settingslib_accent_primary_device_default">@android:color/system_accent1_100</color>
+
+ <!-- copy from accent_primary_variant_light_device_default-->
+ <color name="settingslib_accent_primary_variant">@android:color/system_accent1_600</color>
+
+ <color name="settingslib_accent_secondary_device_default">@android:color/system_accent2_100</color>
+
+ <color name="settingslib_background_device_default_dark">@android:color/system_neutral1_900</color>
+
+ <color name="settingslib_background_device_default_light">@android:color/system_neutral1_50</color>
+
+ <color name="settingslib_text_color_primary_device_default">@android:color/system_neutral1_900</color>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
index ddcc83eee4bf..1c33f1a57ea5 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/dimens.xml
@@ -19,4 +19,5 @@
<dimen name="app_preference_padding_start">20dp</dimen>
<dimen name="app_icon_min_width">52dp</dimen>
<dimen name="settingslib_preferred_minimum_touch_target">48dp</dimen>
+ <dimen name="settingslib_dialogCornerRadius">28dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml
new file mode 100644
index 000000000000..6d072a936b15
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/strings.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of a font family to use for headlines in SettingsLib. -->
+ <string name="settingslib_config_headlineFontFamily" translatable="false">
+ @*android:string/config_headlineFontFamily
+ </string>
+
+ <!-- Name of a font family to use for headlines-medium in SettingsLib. -->
+ <string name="settingslib_config_headlineFontFamilyMedium" translatable="false">
+ @*android:string/config_headlineFontFamilyMedium
+ </string>
+
+ <!-- Name of a font family to use for body in SettingsLib. -->
+ <string name="settingslib_config_bodyFontFamily" translatable="false">
+ @*android:string/config_bodyFontFamily
+ </string>
+
+ <!-- Name of a font family to use for body-medium in SettingsLib. -->
+ <string name="settingslib_config_bodyFontFamilyMedium" translatable="false">
+ @*android:string/config_bodyFontFamilyMedium
+ </string>
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
index 46f1e030af23..58006369988e 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/styles.xml
@@ -16,12 +16,16 @@
-->
<resources>
<style name="TextAppearance.PreferenceTitle.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.ListItem">
+ parent="@android:style/TextAppearance.Material.Subhead">
+ <item name="android:fontFamily">@string/settingslib_config_headlineFontFamily</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.CategoryTitle.SettingsLib"
- parent="@*android:style/TextAppearance.DeviceDefault.Body2" />
+ parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">14sp</item>
+ </style>
<style name="Switch.SettingsLib" parent="@android:style/Widget.Material.CompoundButton.Switch">
<item name="android:switchMinWidth">52dp</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
index 8034710b4341..6bf288b74d5a 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/themes.xml
@@ -50,6 +50,6 @@
<item name="android:clipToPadding">true</item>
<item name="android:clipChildren">true</item>
- <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+ <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index 25f9514c29b7..18af1f9c15d0 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -20,4 +20,5 @@
<dimen name="app_preference_padding_start">?android:attr/listPreferredItemPaddingStart</dimen>
<dimen name="app_icon_min_width">56dp</dimen>
<dimen name="two_target_min_width">72dp</dimen>
+ <dimen name="settingslib_dialogCornerRadius">8dp</dimen>
</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/themes.xml b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
index 6f2517746ddc..2d881d1a8a7b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/themes.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/themes.xml
@@ -35,7 +35,7 @@
<!-- TODO(b/189308264): fix the crash in Android R if set the attributes:
<item name="colorAccent">@*android:color/accent_device_default_light</item>
<item name="android:colorBackground">@color/settingslib_dialog_background</item>
- <item name="dialogCornerRadius">@*android:dimen/config_dialogCornerRadius</item>
+ <item name="dialogCornerRadius">@dimen/settingslib_dialogCornerRadius</item>
-->
<item name="android:windowSoftInputMode">adjustResize</item>
<item name="android:clipToPadding">true</item>
diff --git a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
index 65869b5580b5..b6ca41fb6b6d 100644
--- a/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
+++ b/packages/SettingsLib/TopIntroPreference/res/values/styles.xml
@@ -16,8 +16,7 @@
-->
<resources>
<style name="TextAppearance.TopIntroText"
- parent="@*android:style/TextAppearance.DeviceDefault">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
+ parent="@android:style/TextAppearance.DeviceDefault">
<item name="android:textSize">14sp</item>
<item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 72fa25fc7a3a..bf0dc7bce5f9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -157,7 +157,6 @@ public class WifiStatusTracker {
private Network mDefaultNetwork = null;
private NetworkCapabilities mDefaultNetworkCapabilities = null;
private final Runnable mCallback;
- private final boolean mSupportMergedUi;
private WifiInfo mWifiInfo;
public boolean enabled;
@@ -181,7 +180,6 @@ public class WifiStatusTracker {
mNetworkScoreManager = networkScoreManager;
mConnectivityManager = connectivityManager;
mCallback = callback;
- mSupportMergedUi = false;
}
public void setListening(boolean listening) {
@@ -223,10 +221,8 @@ public class WifiStatusTracker {
} else {
ssid = getValidSsid(mWifiInfo);
}
- if (mSupportMergedUi) {
- isCarrierMerged = mWifiInfo.isCarrierMerged();
- subId = mWifiInfo.getSubscriptionId();
- }
+ isCarrierMerged = mWifiInfo.isCarrierMerged();
+ subId = mWifiInfo.getSubscriptionId();
updateRssi(mWifiInfo.getRssi());
maybeRequestNetworkScore();
}
@@ -255,10 +251,8 @@ public class WifiStatusTracker {
} else {
ssid = getValidSsid(mWifiInfo);
}
- if (mSupportMergedUi) {
- isCarrierMerged = mWifiInfo.isCarrierMerged();
- subId = mWifiInfo.getSubscriptionId();
- }
+ isCarrierMerged = mWifiInfo.isCarrierMerged();
+ subId = mWifiInfo.getSubscriptionId();
updateRssi(mWifiInfo.getRssi());
maybeRequestNetworkScore();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
index 61006156d8aa..56454e975370 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.java
@@ -20,10 +20,13 @@ import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.getMaxNetworkSelectionDisableReason;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiInfo;
+import android.os.Bundle;
import android.os.SystemClock;
import androidx.annotation.VisibleForTesting;
@@ -36,6 +39,23 @@ public class WifiUtils {
private static final int INVALID_RSSI = -127;
+ /**
+ * The intent action shows network details settings to allow configuration of Wi-Fi.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you
+ * safeguard against this.
+ * <p>
+ * Input: The calling package should put the chosen
+ * com.android.wifitrackerlib.WifiEntry#getKey() to a string extra in the request bundle into
+ * the {@link #KEY_CHOSEN_WIFIENTRY_KEY}.
+ * <p>
+ * Output: Nothing.
+ */
+ public static final String ACTION_WIFI_DETAILS_SETTINGS =
+ "android.settings.WIFI_DETAILS_SETTINGS";
+ public static final String KEY_CHOSEN_WIFIENTRY_KEY = "key_chosen_wifientry_key";
+ public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+
static final int[] WIFI_PIE = {
com.android.internal.R.drawable.ic_wifi_signal_0,
com.android.internal.R.drawable.ic_wifi_signal_1,
@@ -275,7 +295,42 @@ public class WifiUtils {
return noInternet ? NO_INTERNET_WIFI_PIE[level] : WIFI_PIE[level];
}
+ /**
+ * Wrapper the {@link #getInternetIconResource} for testing compatibility.
+ */
+ public static class InternetIconInjector {
+
+ protected final Context mContext;
+
+ public InternetIconInjector(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Returns the Internet icon for a given RSSI level.
+ *
+ * @param noInternet True if a connected Wi-Fi network cannot access the Internet
+ * @param level The number of bars to show (0-4)
+ */
+ public Drawable getIcon(boolean noInternet, int level) {
+ return mContext.getDrawable(WifiUtils.getInternetIconResource(level, noInternet));
+ }
+ }
+
public static boolean isMeteredOverridden(WifiConfiguration config) {
return config.meteredOverride != WifiConfiguration.METERED_OVERRIDE_NONE;
}
+
+ /**
+ * Returns the Intent for Wi-Fi network details settings.
+ *
+ * @param key The Wi-Fi entry key
+ */
+ public static Intent getWifiDetailsSettingsIntent(String key) {
+ final Intent intent = new Intent(ACTION_WIFI_DETAILS_SETTINGS);
+ final Bundle bundle = new Bundle();
+ bundle.putString(KEY_CHOSEN_WIFIENTRY_KEY, key);
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
+ return intent;
+ }
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index 89960cba2bf5..7c2b904fc576 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -20,9 +20,12 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.Intent;
import android.net.NetworkKey;
import android.net.RssiCurve;
import android.net.ScoredNetwork;
@@ -36,6 +39,8 @@ import android.os.SystemClock;
import android.text.format.DateUtils;
import android.util.ArraySet;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.settingslib.R;
import org.junit.Before;
@@ -44,7 +49,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Set;
@@ -69,7 +73,7 @@ public class WifiUtilsTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mContext = RuntimeEnvironment.application;
+ mContext = spy(ApplicationProvider.getApplicationContext());
}
@Test
@@ -148,6 +152,32 @@ public class WifiUtilsTest {
assertThat(WifiUtils.isMeteredOverridden(mWifiConfig)).isTrue();
}
+ @Test
+ public void getWifiDetailsSettingsIntent_returnsCorrectValues() {
+ final String key = "test_key";
+
+ final Intent intent = WifiUtils.getWifiDetailsSettingsIntent(key);
+
+ assertThat(intent.getAction()).isEqualTo(WifiUtils.ACTION_WIFI_DETAILS_SETTINGS);
+ final Bundle bundle = intent.getBundleExtra(WifiUtils.EXTRA_SHOW_FRAGMENT_ARGUMENTS);
+ assertThat(bundle.getString(WifiUtils.KEY_CHOSEN_WIFIENTRY_KEY)).isEqualTo(key);
+ }
+
+ @Test
+ public void testInternetIconInjector_getIcon_returnsCorrectValues() {
+ WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
+
+ for (int level = 0; level <= 4; level++) {
+ iconInjector.getIcon(false /* noInternet */, level);
+ verify(mContext).getDrawable(
+ WifiUtils.getInternetIconResource(level, false /* noInternet */));
+
+ iconInjector.getIcon(true /* noInternet */, level);
+ verify(mContext).getDrawable(
+ WifiUtils.getInternetIconResource(level, true /* noInternet */));
+ }
+ }
+
private static ArrayList<ScanResult> buildScanResultCache() {
ArrayList<ScanResult> scanResults = new ArrayList<>();
for (int i = 0; i < 5; i++) {
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index eb8196176034..a46d28b273e5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -76,5 +76,6 @@ public class GlobalSettings {
Settings.Global.ARE_USER_DISABLED_HDR_FORMATS_ALLOWED,
Settings.Global.DEVICE_CONFIG_SYNC_DISABLED,
Settings.Global.POWER_BUTTON_LONG_PRESS,
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 60226084c70d..96f127b6a611 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -101,6 +101,7 @@ public class SecureSettings {
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED,
Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED,
Settings.Secure.QS_TILES,
+ Settings.Secure.QS_AUTO_ADDED_TILES,
Settings.Secure.CONTROLS_ENABLED,
Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT,
Settings.Secure.DOZE_ENABLED,
@@ -118,7 +119,6 @@ public class SecureSettings {
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
- Settings.Secure.QS_AUTO_ADDED_TILES,
Settings.Secure.SCREENSAVER_ENABLED,
Settings.Secure.SCREENSAVER_COMPONENTS,
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 5220a04d73e6..84c5febcb5a2 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -20,6 +20,7 @@ import static android.media.AudioFormat.SURROUND_SOUND_ENCODING;
import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR;
+import static android.provider.settings.validators.SettingsValidators.NONE_NEGATIVE_LONG_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PACKAGE_NAME_VALIDATOR;
import static android.provider.settings.validators.SettingsValidators.PERCENTAGE_INTEGER_VALIDATOR;
import static android.view.Display.HdrCapabilities.HDR_TYPES;
@@ -140,6 +141,7 @@ public class GlobalSettingsValidators {
/* last= */Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT));
VALIDATORS.put(Global.DISABLE_WINDOW_BLURS, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index c57786888e8d..6cfcb51239a3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -72,8 +72,9 @@ public class SettingsHelper {
* {@hide}
*/
private static final ArraySet<String> sBroadcastOnRestore;
+ private static final ArraySet<String> sBroadcastOnRestoreSystemUI;
static {
- sBroadcastOnRestore = new ArraySet<String>(4);
+ sBroadcastOnRestore = new ArraySet<String>(9);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_VR_LISTENERS);
sBroadcastOnRestore.add(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
@@ -83,6 +84,9 @@ public class SettingsHelper {
sBroadcastOnRestore.add(Settings.Secure.DARK_THEME_CUSTOM_END_TIME);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
sBroadcastOnRestore.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS);
+ sBroadcastOnRestoreSystemUI = new ArraySet<String>(2);
+ sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_TILES);
+ sBroadcastOnRestoreSystemUI.add(Settings.Secure.QS_AUTO_ADDED_TILES);
}
private interface SettingsLookup {
@@ -133,6 +137,7 @@ public class SettingsHelper {
// Will we need a post-restore broadcast for this element?
String oldValue = null;
boolean sendBroadcast = false;
+ boolean sendBroadcastSystemUI = false;
final SettingsLookup table;
if (destination.equals(Settings.Secure.CONTENT_URI)) {
@@ -143,10 +148,12 @@ public class SettingsHelper {
table = sGlobalLookup;
}
- if (sBroadcastOnRestore.contains(name)) {
+ sendBroadcast = sBroadcastOnRestore.contains(name);
+ sendBroadcastSystemUI = sBroadcastOnRestoreSystemUI.contains(name);
+
+ if (sendBroadcast || sendBroadcastSystemUI) {
// TODO: http://b/22388012
oldValue = table.lookup(cr, name, UserHandle.USER_SYSTEM);
- sendBroadcast = true;
}
try {
@@ -193,18 +200,28 @@ public class SettingsHelper {
} catch (Exception e) {
// If we fail to apply the setting, by definition nothing happened
sendBroadcast = false;
+ sendBroadcastSystemUI = false;
} finally {
// If this was an element of interest, send the "we just restored it"
// broadcast with the historical value now that the new value has
// been committed and observers kicked off.
- if (sendBroadcast) {
+ if (sendBroadcast || sendBroadcastSystemUI) {
Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED)
- .setPackage("android").addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
.putExtra(Intent.EXTRA_SETTING_NAME, name)
.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, value)
.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, oldValue)
.putExtra(Intent.EXTRA_SETTING_RESTORED_FROM_SDK_INT, restoredFromSdkInt);
- context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+
+ if (sendBroadcast) {
+ intent.setPackage("android");
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+ }
+ if (sendBroadcastSystemUI) {
+ intent.setPackage(
+ context.getString(com.android.internal.R.string.config_systemUi));
+ context.sendBroadcastAsUser(intent, UserHandle.SYSTEM, null);
+ }
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 073b4d00653d..4ac19386db0a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -763,9 +763,6 @@ class SettingsProtoDumpUtil {
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
GlobalSettingsProto.Gpu.ANGLE_GL_DRIVER_SELECTION_VALUES);
dumpSetting(s, p,
- Settings.Global.ANGLE_ALLOWLIST,
- GlobalSettingsProto.Gpu.ANGLE_ALLOWLIST);
- dumpSetting(s, p,
Settings.Global.ANGLE_EGL_FEATURES,
GlobalSettingsProto.Gpu.ANGLE_EGL_FEATURES);
dumpSetting(s, p,
@@ -1195,6 +1192,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Global.POWER_MANAGER_CONSTANTS,
GlobalSettingsProto.POWER_MANAGER_CONSTANTS);
+ dumpSetting(s, p,
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+ GlobalSettingsProto.POWER_BUTTON_LONG_PRESS_DURATION_MS);
final long prepaidSetupToken = p.start(GlobalSettingsProto.PREPAID_SETUP);
dumpSetting(s, p,
@@ -1476,6 +1476,9 @@ class SettingsProtoDumpUtil {
Settings.Global.USE_OPEN_WIFI_PACKAGE,
GlobalSettingsProto.USE_OPEN_WIFI_PACKAGE);
dumpSetting(s, p,
+ Settings.Global.UWB_ENABLED,
+ GlobalSettingsProto.UWB_ENABLED);
+ dumpSetting(s, p,
Settings.Global.VT_IMS_ENABLED,
GlobalSettingsProto.VT_IMS_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 3297937e3e75..7db73c697ea7 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -509,7 +509,6 @@ public class SettingsBackupTest {
Settings.Global.ANGLE_GL_DRIVER_ALL_ANGLE,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS,
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES,
- Settings.Global.ANGLE_ALLOWLIST,
Settings.Global.ANGLE_EGL_FEATURES,
Settings.Global.UPDATABLE_DRIVER_ALL_APPS,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_OPT_IN_APPS,
@@ -519,6 +518,7 @@ public class SettingsBackupTest {
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_DENYLIST,
Settings.Global.UPDATABLE_DRIVER_PRODUCTION_ALLOWLIST,
Settings.Global.UPDATABLE_DRIVER_SPHAL_LIBRARIES,
+ Settings.Global.UWB_ENABLED,
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX,
Settings.Global.GPU_DEBUG_LAYER_APP,
Settings.Global.ENABLE_GNSS_RAW_MEAS_FULL_TRACKING,
diff --git a/packages/Shell/OWNERS b/packages/Shell/OWNERS
index 177f86b08864..80bb307749ed 100644
--- a/packages/Shell/OWNERS
+++ b/packages/Shell/OWNERS
@@ -6,7 +6,6 @@ nandana@google.com
svetoslavganov@google.com
hackbod@google.com
yamasani@google.com
-toddke@google.com
patb@google.com
cbrubaker@google.com
omakoto@google.com
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
new file mode 100644
index 000000000000..495fbb893eac
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_media_recommendation_bg_gradient.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="24dp"/>
+ <gradient
+ android:angle="0"
+ android:startColor="#00000000"
+ android:endColor="#ff000000"
+ android:type="linear" />
+</shape>
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index a4d0ff7269a2..1934457b4bc6 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -21,6 +21,9 @@
<!-- Skeleton string format for displaying the date when an alarm is set. -->
<string name="abbrev_wday_month_day_no_year_alarm">EEEMMMd</string>
+ <!-- Skeleton string format for displaying the date shorter. -->
+ <string name="abbrev_month_day_no_year">MMMd</string>
+
<!-- Skeleton string format for displaying the time in 12-hour format. -->
<string name="clock_12hr_format">hm</string>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 72b027af1bf6..098b7e8dedbe 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -142,4 +142,8 @@
<item name="android:shadowColor">@color/keyguard_shadow_color</item>
<item name="android:shadowRadius">?attr/shadowRadius</item>
</style>
+
+ <style name="TextAppearance.Keyguard.BottomArea.Button">
+ <item name="android:shadowRadius">0</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
new file mode 100644
index 000000000000..13133cb451e4
--- /dev/null
+++ b/packages/SystemUI/res/anim/progress_indeterminate_horizontal_rect.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_rect2 in frameworks/base/core/res -->
+<set xmlns:android="http://schemas.android.com/apk/res/android" >
+ <objectAnimator
+ android:duration="2000"
+ android:propertyXName="translateX"
+ android:pathData="M -197.60001,0 c 14.28182,0 85.07782,0 135.54689,0 c 54.26191,0 90.42461,0 168.24331,0 c 144.72154,0 316.40982,0 316.40982,0 "
+ android:interpolator="@interpolator/progress_indeterminate_horizontal_rect2_translatex_copy"
+ android:repeatCount="infinite" />
+</set> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_off.xml b/packages/SystemUI/res/color/settingslib_state_off.xml
new file mode 100644
index 000000000000..e8218259087d
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondaryVariant"/>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_state_on.xml b/packages/SystemUI/res/color/settingslib_state_on.xml
new file mode 100644
index 000000000000..6d2133c5bacf
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_state_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimary"/>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_off.xml b/packages/SystemUI/res/color/settingslib_track_off.xml
new file mode 100644
index 000000000000..21d1dccbf900
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_off.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentSecondary"/>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/settingslib_track_on.xml b/packages/SystemUI/res/color/settingslib_track_on.xml
new file mode 100644
index 000000000000..ba7848a5d23e
--- /dev/null
+++ b/packages/SystemUI/res/color/settingslib_track_on.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="?androidprv:attr/colorAccentPrimaryVariant"/>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
index 33263a9131a0..0ae5dc745478 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml
@@ -1,413 +1,242 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
+ xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
- <vector
- android:width="60dp"
- android:height="60dp"
- android:viewportHeight="60"
- android:viewportWidth="60">
+ <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
+ android:viewportWidth="60">
<group android:name="_R_G">
- <group
- android:name="_R_G_L_1_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_1_G"
- android:pivotX="114"
- android:pivotY="114"
- android:scaleX="0.42200000000000004"
- android:scaleY="0.42200000000000004"
- android:translateX="-114"
- android:translateY="-114">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_1_P_0"
- android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_2_P_0"
- android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_3_P_0"
- android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_4_P_0"
- android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="0"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
+ <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
+ <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="38.75" android:scaleX="1" android:scaleY="1">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
</group>
- </group>
- <group
- android:name="_R_G_L_0_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_0_G"
- android:translateX="-30.05"
- android:translateY="-30">
- <group
- android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="30"
- android:translateY="38.75">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
- </group>
- <group
- android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"
- android:pivotX="0.002"
- android:pivotY="7.488"
- android:scaleX="1"
- android:scaleY="1"
- android:translateX="30"
- android:translateY="25">
- <path
- android:name="_R_G_L_0_G_D_1_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
- </group>
- <path
- android:name="_R_G_L_0_G_D_2_P_0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="2.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
+ <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
+ android:scaleX="1" android:scaleY="1">
+ <path android:name="_R_G_L_1_G_D_1_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
</group>
+ <path android:name="_R_G_L_1_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_error"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2.5" android:strokeAlpha="1"
+ android:trimPathStart="0" android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-10.325"
+ android:translateY="-10.25">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
+ <path android:name="_R_G_L_0_G_D_1_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
+ <path android:name="_R_G_L_0_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
+ <path android:name="_R_G_L_0_G_D_3_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="0" android:trimPathOffset="0"
+ android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
</group>
</group>
- <group android:name="time_group" />
+ <group android:name="time_group"/>
</vector>
</aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0">
+ <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,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:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_2_P_0">
+ <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ 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.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.659,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="1.1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,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:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="67" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="67" android:valueFrom="1.1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_1_G_D_4_P_0">
+ <target android:name="_R_G_L_1_G_D_2_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="83"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="67"
+ 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.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="250"
- android:propertyName="trimPathEnd"
- android:startOffset="83"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="133"
+ android:startOffset="67" android:valueFrom="1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1.1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.853,0 0.6,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="67"
- android:valueFrom="1.1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ 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.8,0 0.92,1.06 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="67"
- android:valueFrom="1.1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.06 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ 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.8,0 0.659,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1.1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="67"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ 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.8,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="67"
- android:valueFrom="1.1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.8,0 0.92,1.096 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
+ <target android:name="_R_G_L_0_G_D_3_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="67"
- android:propertyName="trimPathEnd"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="83"
+ 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.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="133"
- android:propertyName="trimPathEnd"
- android:startOffset="67"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathEnd" android:duration="250"
+ android:startOffset="83" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -416,14 +245,10 @@
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
+ <objectAnimator android:propertyName="translateX" android:duration="417"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
-</animated-vector> \ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
index b899828cd85c..fc2c7d00f3a7 100644
--- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
+++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml
@@ -1,391 +1,235 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2018 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:aapt="http://schemas.android.com/aapt">
+ xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
- <vector
- android:width="60dp"
- android:height="60dp"
- android:viewportHeight="60"
- android:viewportWidth="60">
+ <vector android:height="60dp" android:width="60dp" android:viewportHeight="60"
+ android:viewportWidth="60">
<group android:name="_R_G">
- <group
- android:name="_R_G_L_1_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_1_G"
- android:pivotX="114"
- android:pivotY="114"
- android:scaleX="0.42244"
- android:scaleY="0.42244"
- android:translateX="-114"
- android:translateY="-114">
- <path
- android:name="_R_G_L_1_G_D_0_P_0"
- android:pathData=" M79.63 67.24 C79.63,67.24 111.5,47.42 147.83,67.24 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_1_P_0"
- android:pathData=" M64.27 98.07 C64.27,98.07 80.13,73.02 113.98,73.02 C147.83,73.02 163.56,97.26 163.56,97.26 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_2_P_0"
- android:pathData=" M72.53 151.07 C72.53,151.07 62.46,122.89 76.16,105.55 C89.86,88.21 106.72,86.73 113.98,86.73 C121.08,86.73 153.51,90.62 158.7,125.87 C159.14,128.82 158.8,132.88 157.18,136.09 C154.88,140.63 150.62,143.63 145.85,143.97 C133.78,144.85 129.76,137.92 129.26,128.49 C128.88,121.19 122.49,115.35 113.15,115.35 C102.91,115.35 95.97,126.69 99.77,139.74 C103.57,152.78 111.33,163.85 130.32,169.13 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_3_P_0"
- android:pathData=" M100.6 167.84 C100.6,167.84 82.76,152.1 83.75,130.31 C84.75,108.53 102.58,100.7 113.73,100.7 C124.87,100.7 144.19,108.56 144.19,130.01 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="_R_G_L_1_G_D_4_P_0"
- android:pathData=" M113.73 129.17 C113.73,129.17 113.15,161.33 149.15,156.58 "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_accent"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="5.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
+ <group android:name="_R_G_L_1_G" android:translateX="-0.05000000000000071">
+ <group android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="38.75" android:scaleX="0" android:scaleY="0">
+ <path android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c "/>
</group>
- </group>
- <group
- android:name="_R_G_L_0_G_N_4_T_0"
- android:translateX="30"
- android:translateY="30">
- <group
- android:name="_R_G_L_0_G"
- android:translateX="-30.05"
- android:translateY="-30">
- <group
- android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0"
- android:scaleX="0"
- android:scaleY="0"
- android:translateX="30"
- android:translateY="38.75">
- <path
- android:name="_R_G_L_0_G_D_0_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -1.25 C-1.2,-1.25 1.2,-1.25 1.2,-1.25 C1.2,-1.25 1.2,1.25 1.2,1.25 C1.2,1.25 -1.2,1.25 -1.2,1.25 C-1.2,1.25 -1.2,-1.25 -1.2,-1.25c " />
- </group>
- <group
- android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0"
- android:pivotX="0.002"
- android:pivotY="7.488"
- android:scaleX="1"
- android:scaleY="0"
- android:translateX="30"
- android:translateY="25">
- <path
- android:name="_R_G_L_0_G_D_1_P_0"
- android:fillAlpha="1"
- android:fillColor="@color/biometric_dialog_error"
- android:fillType="nonZero"
- android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c " />
- </group>
- <path
- android:name="_R_G_L_0_G_D_2_P_0"
- android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "
- android:strokeAlpha="1"
- android:strokeColor="@color/biometric_dialog_error"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeWidth="2.5"
- android:trimPathEnd="1"
- android:trimPathOffset="0"
- android:trimPathStart="1" />
+ <group android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0" android:translateX="30"
+ android:translateY="25" android:pivotX="0.002" android:pivotY="7.488"
+ android:scaleX="1" android:scaleY="0">
+ <path android:name="_R_G_L_1_G_D_1_P_0"
+ android:fillColor="@color/biometric_dialog_error"
+ android:fillAlpha="1" android:fillType="nonZero"
+ android:pathData=" M-1.2 -7.5 C-1.2,-7.5 1.2,-7.5 1.2,-7.5 C1.2,-7.5 1.2,7.5 1.2,7.5 C1.2,7.5 -1.2,7.5 -1.2,7.5 C-1.2,7.5 -1.2,-7.5 -1.2,-7.5c "/>
</group>
+ <path android:name="_R_G_L_1_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_error"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2.5" android:strokeAlpha="1"
+ android:trimPathStart="1" android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:pathData=" M30 6.2 C16.9,6.2 6.3,16.8 6.3,30 C6.3,43.2 16.9,53.8 30,53.8 C43.1,53.8 53.8,43.2 53.8,30 C53.8,16.8 43.1,6.2 30,6.2c "/>
+ </group>
+ <group android:name="_R_G_L_0_G" android:translateX="-10.325"
+ android:translateY="-10.25">
+ <path android:name="_R_G_L_0_G_D_0_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M31.41 48.43 C30.78,46.69 30.78,44.91 30.78,44.91 C30.78,40.09 34.88,36.16 40.32,36.16 C45.77,36.16 49.87,40.09 49.87,44.91 C49.87,44.91 49.87,45.17 49.87,45.17 C49.87,46.97 48.41,48.43 46.61,48.43 C45.28,48.43 44.09,47.63 43.6,46.39 C43.6,46.39 42.51,43.66 42.51,43.66 C42.02,42.42 40.82,41.61 39.49,41.61 C37.69,41.61 36.23,43.07 36.23,44.87 C36.23,47.12 37.26,49.26 39.02,50.67 C39.02,50.67 39.64,51.16 39.64,51.16 "/>
+ <path android:name="_R_G_L_0_G_D_1_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M32.14 27.3 C34.5,26 37.31,25.25 40.33,25.25 C43.34,25.25 46.15,26 48.51,27.3 "/>
+ <path android:name="_R_G_L_0_G_D_2_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M29.42 36.16 C31.35,32.94 35.51,30.71 40.33,30.71 C45.14,30.71 49.3,32.94 51.23,36.16 "/>
+ <path android:name="_R_G_L_0_G_D_3_P_0"
+ android:strokeColor="@color/biometric_dialog_accent"
+ android:strokeLineCap="round" android:strokeLineJoin="round"
+ android:strokeWidth="2" android:strokeAlpha="1" android:trimPathStart="0"
+ android:trimPathEnd="1" android:trimPathOffset="0"
+ android:pathData=" M47.14 52.52 C45.33,54.21 42.94,55.25 40.33,55.25 C37.71,55.25 35.32,54.21 33.51,52.52 "/>
</group>
</group>
- <group android:name="time_group" />
+ <group android:name="time_group"/>
</vector>
</aapt:attr>
- <target android:name="_R_G_L_1_G_D_0_P_0">
+ <target android:name="_R_G_L_1_G_D_0_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="167"
+ 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.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,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:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="167"
+ 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.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,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:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="167" android:valueFrom="0"
+ android:valueTo="1.1" android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,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:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="167" android:valueFrom="0"
+ android:valueTo="1.1" android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- </set>
- </aapt:attr>
- </target>
- <target android:name="_R_G_L_1_G_D_4_P_0">
- <aapt:attr name="android:animation">
- <set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="267" android:valueFrom="1.1"
+ android:valueTo="1" android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="267" android:valueFrom="1.1"
+ android:valueTo="1" android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.6,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_0_P_0_G_0_T_0">
+ <target android:name="_R_G_L_1_G_D_1_P_0_G_0_T_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="167"
+ 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.08,0.06 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleY"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="167"
+ 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.08,0.06 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="167"
- android:valueFrom="0"
- android:valueTo="1.1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="100"
+ android:startOffset="167" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="167"
- android:valueFrom="0"
- android:valueTo="1.1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="100"
+ android:startOffset="167" android:valueFrom="0"
+ android:valueTo="1.1" android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.06 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="267"
- android:valueFrom="1.1"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleX" android:duration="67"
+ android:startOffset="267" android:valueFrom="1" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="267"
- android:valueFrom="1.1"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="scaleY" android:duration="67"
+ android:startOffset="267" android:valueFrom="1.1"
+ android:valueTo="1" android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.147,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_1_P_0_G_0_T_0">
+ <target android:name="_R_G_L_1_G_D_2_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleX"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="267"
+ android:startOffset="0" android:valueFrom="1" android:valueTo="0"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="167"
- android:propertyName="scaleY"
- 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.08,0.096 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleX"
- android:startOffset="167"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
- <aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
- </aapt:attr>
- </objectAnimator>
- <objectAnimator
- android:duration="100"
- android:propertyName="scaleY"
- android:startOffset="167"
- android:valueFrom="0"
- android:valueTo="1.1"
- android:valueType="floatType">
+ </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="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.08,0.096 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleX"
- android:startOffset="267"
- android:valueFrom="1"
- android:valueTo="1"
- android:valueType="floatType">
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.341,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
- <objectAnimator
- android:duration="67"
- android:propertyName="scaleY"
- android:startOffset="267"
- android:valueFrom="1.1"
- android:valueTo="1"
- android:valueType="floatType">
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
</aapt:attr>
</target>
- <target android:name="_R_G_L_0_G_D_2_P_0">
+ <target android:name="_R_G_L_0_G_D_3_P_0">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="267"
- android:propertyName="trimPathStart"
- android:startOffset="0"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType">
+ <objectAnimator android:propertyName="trimPathStart" android:duration="167"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType">
<aapt:attr name="android:interpolator">
- <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0"/>
</aapt:attr>
</objectAnimator>
</set>
@@ -394,14 +238,10 @@
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
- <objectAnimator
- android:duration="350"
- android:propertyName="translateX"
- android:startOffset="0"
- android:valueFrom="0"
- android:valueTo="1"
- android:valueType="floatType" />
+ <objectAnimator android:propertyName="translateX" android:duration="350"
+ android:startOffset="0" android:valueFrom="0" android:valueTo="1"
+ android:valueType="floatType"/>
</set>
</aapt:attr>
</target>
-</animated-vector> \ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_arrow_forward.xml b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
new file mode 100644
index 000000000000..438e4c70dc71
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_arrow_forward.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:autoMirrored="true"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/black"
+ android:pathData="M6.23,20.23l1.77,1.77l10,-10l-10,-10l-1.77,1.77l8.23,8.23z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
new file mode 100644
index 000000000000..2c34060ccd61
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_friction_lock_closed.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:attr/colorControlNormal"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10C20,8.9 19.1,8 18,8zM9,6c0,-1.66 1.34,-3 3,-3s3,1.34 3,3v2H9V6zM18,20H6V10h12V20zM12,17c1.1,0 2,-0.9 2,-2c0,-1.1 -0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2C10,16.1 10.9,17 12,17z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_home_devices.xml b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
new file mode 100644
index 000000000000..886c64d9bf02
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_media_home_devices.xml
@@ -0,0 +1,16 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,4H4c-1.1,0 -2,0.9 -2,2v11c0,1.1 0.9,2 2,2h4v2h3v-4H4V6h16v1h2V6c0,-1.1 -0.9,-2 -2,-2z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17.5,16.5m-2.33,0a2.33,2.33 0,1 1,4.66 0a2.33,2.33 0,1 1,-4.66 0"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21,8h-7c-0.55,0 -1,0.45 -1,1v11c0,0.55 0.45,1 1,1h7c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM17.5,9c0.83,0 1.5,0.67 1.5,1.5s-0.67,1.5 -1.5,1.5 -1.5,-0.67 -1.5,-1.5 0.67,-1.5 1.5,-1.5zM17.5,20c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_settings_24dp.xml b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
new file mode 100644
index 000000000000..ac4c43bd35b9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_settings_24dp.xml
@@ -0,0 +1,29 @@
+<!--
+ Copyright (C) 2015 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46c0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19C1.98,9.9 1.83,9.09 2.2,8.47l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91C15.21,21.71 14.59,22.25 13.85,22.25zM13.32,20.72c0,0.01 0,0.01 0,0.02L13.32,20.72zM10.68,20.7l0,0.02C10.69,20.72 10.69,20.71 10.68,20.7zM10.62,20.25h2.76l0.37,-2.55l0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34l2.38,0.96l1.38,-2.4l-2.03,-1.58l0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78c0,-0.27 -0.03,-0.53 -0.06,-0.78l-0.07,-0.56l2.03,-1.58l-1.39,-2.4l-2.39,0.96l-0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77L13.75,6.3l-0.37,-2.55h-2.76L10.25,6.3L9.72,6.51C9.28,6.7 8.84,6.95 8.38,7.3L7.93,7.63L5.55,6.68L4.16,9.07l2.03,1.58l-0.07,0.56C6.09,11.47 6.06,11.74 6.06,12c0,0.26 0.02,0.53 0.06,0.78l0.07,0.56l-2.03,1.58l1.38,2.4l2.39,-0.96l0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22L10.62,20.25zM18.22,17.72c0,0.01 -0.01,0.02 -0.01,0.03L18.22,17.72zM5.77,17.71l0.01,0.02C5.78,17.72 5.77,17.71 5.77,17.71zM3.93,9.47L3.93,9.47C3.93,9.47 3.93,9.47 3.93,9.47zM18.22,6.27c0,0.01 0.01,0.02 0.01,0.02L18.22,6.27zM5.79,6.25L5.78,6.27C5.78,6.27 5.79,6.26 5.79,6.25zM13.31,3.28c0,0.01 0,0.01 0,0.02L13.31,3.28zM10.69,3.26l0,0.02C10.69,3.27 10.69,3.27 10.69,3.26z"/>
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
new file mode 100644
index 000000000000..f38a36804bc8
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_signal_strength_zero_bar_no_internet.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M2,22l16,0l0,-2l-11,0l13,-13l0,1l2,0l0,-6z"
+ android:strokeAlpha="0.3"
+ android:fillAlpha="0.3"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,10h2v8h-2z"/>
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M20,20h2v2h-2z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_background.xml b/packages/SystemUI/res/drawable/internet_dialog_background.xml
new file mode 100644
index 000000000000..3ceb0f6ac06a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners android:radius="8dp" />
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
new file mode 100644
index 000000000000..50267fda0b25
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_footer_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <stroke
+ android:color="?androidprv:attr/colorAccentPrimaryVariant"
+ android:width="1dp"/>
+ <corners android:radius="20dp"/>
+ <padding
+ android:left="8dp"
+ android:right="8dp"
+ android:top="4dp"
+ android:bottom="4dp" />
+ <solid android:color="@android:color/transparent" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
new file mode 100644
index 000000000000..14672ef3dcfe
--- /dev/null
+++ b/packages/SystemUI/res/drawable/internet_dialog_rounded_top_corner_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<inset xmlns:android="http://schemas.android.com/apk/res/android">
+ <shape android:shape="rectangle">
+ <corners
+ android:topLeftRadius="@dimen/internet_dialog_corner_radius"
+ android:topRightRadius="@dimen/internet_dialog_corner_radius"
+ android:bottomLeftRadius="@dimen/internet_dialog_corner_radius"
+ android:bottomRightRadius="@dimen/internet_dialog_corner_radius"/>
+ <solid android:color="?android:attr/colorBackground" />
+ </shape>
+</inset>
diff --git a/packages/SystemUI/res/drawable/logout_button_background.xml b/packages/SystemUI/res/drawable/logout_button_background.xml
index eafd663f19d9..34434be7aefb 100644
--- a/packages/SystemUI/res/drawable/logout_button_background.xml
+++ b/packages/SystemUI/res/drawable/logout_button_background.xml
@@ -17,7 +17,8 @@
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
- <solid android:color="@color/logout_button_bg_color"/>
+ <solid android:color="?androidprv:attr/colorAccentPrimary"/>
<corners android:radius="@dimen/logout_button_corner_radius"/>
</shape>
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
index 6725a26c102c..e6266754c0af 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_background.xml
@@ -14,19 +14,20 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid
- android:color="?androidprv:attr/colorSurface"
- />
- <corners android:radius="20dp" />
-
- <padding
- android:left="20dp"
- android:right="20dp">
- </padding>
-
-</shape> \ No newline at end of file
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <inset
+ android:insetBottom="4dp"
+ android:insetTop="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="20dp" />
+ <padding
+ android:left="20dp"
+ android:right="20dp" />
+ <solid android:color="?androidprv:attr/colorSurface" />
+ </shape>
+ </inset>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
new file mode 100644
index 000000000000..95209f8eb8ab
--- /dev/null
+++ b/packages/SystemUI/res/drawable/progress_indeterminate_horizontal_material_trimmed.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Copy of progress_indeterminate_horizontal_material_trimmed in frameworks/base/core/res -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_progress_indeterminate_horizontal_trimmed" >
+ <target
+ android:name="rect_grp"
+ android:animation="@anim/progress_indeterminate_horizontal_rect" />
+</animated-vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
new file mode 100644
index 000000000000..088e82bb4260
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_disabled.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_state_off_color"/>
+ <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
new file mode 100644
index 000000000000..250188b892f4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/settingslib_state_on_color"/>
+ <corners android:radius="@dimen/settingslib_switch_bar_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
new file mode 100644
index 000000000000..b41762f7908e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_disabled.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid
+ android:color="@color/settingslib_thumb_off_color"
+ android:alpha="?android:attr/disabledAlpha"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_off.xml b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
new file mode 100644
index 000000000000..87d4aeaac84f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_off.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid android:color="@color/settingslib_thumb_off_color"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
new file mode 100644
index 000000000000..5566ea3f62fc
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:top="@dimen/settingslib_switch_thumb_margin"
+ android:bottom="@dimen/settingslib_switch_thumb_margin">
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/settingslib_switch_thumb_size"
+ android:width="@dimen/settingslib_switch_thumb_size"/>
+ <solid android:color="@color/settingslib_state_on_color"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
new file mode 100644
index 000000000000..06bb779b91ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_thumb_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/settingslib_thumb_on" android:state_checked="true"/>
+ <item android:drawable="@drawable/settingslib_thumb_off" android:state_checked="false"/>
+ <item android:drawable="@drawable/settingslib_thumb_disabled" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
new file mode 100644
index 000000000000..15dfcb70e25e
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_disabled_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <solid
+ android:color="@color/settingslib_track_off_color"
+ android:alpha="?android:attr/disabledAlpha"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_off_background.xml b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
new file mode 100644
index 000000000000..3a09284d10a0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_off_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <padding android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"/>
+ <solid android:color="@color/settingslib_track_off_color"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
new file mode 100644
index 000000000000..1d9dacd6c0f9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ android:width="@dimen/settingslib_switch_track_width"
+ android:height="@dimen/settingslib_switch_track_height">
+ <padding android:left="@dimen/settingslib_switch_thumb_margin"
+ android:right="@dimen/settingslib_switch_thumb_margin"/>
+ <solid android:color="@color/settingslib_track_on_color"/>
+ <corners android:radius="@dimen/settingslib_switch_track_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/settingslib_track_selector.xml b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
new file mode 100644
index 000000000000..a38c3b4241a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settingslib_track_selector.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/settingslib_track_on_background" android:state_checked="true"/>
+ <item android:drawable="@drawable/settingslib_track_off_background" android:state_checked="false"/>
+ <item android:drawable="@drawable/settingslib_track_disabled_background" android:state_enabled="false"/>
+</selector>
diff --git a/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
new file mode 100644
index 000000000000..aec204f45aa7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Variant of vector_drawable_progress_indeterminate_horizontal in frameworks/base/core/res, which
+ draws the whole height of the progress bar instead having blank space above and below the
+ bar. -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:height="10dp"
+ android:width="340dp"
+ android:viewportHeight="10"
+ android:viewportWidth="340" >
+ <group
+ android:name="progress_group"
+ android:translateX="180"
+ android:translateY="5" >
+ <path
+ android:name="background_track"
+ android:pathData="M -180.0,-5.0 l 360.0,0 l 0,10.0 l -360.0,0 Z"
+ android:fillColor="?androidprv:attr/colorSurfaceVariant"/>
+ <group
+ android:name="rect_grp"
+ android:translateX="-197.60001"
+ android:scaleX="0.5" >
+ <path
+ android:name="rect"
+ android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
+ android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" />
+ </group>
+ </group>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_actions_toast.xml b/packages/SystemUI/res/layout/global_actions_toast.xml
new file mode 100644
index 000000000000..1f0899623ddf
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_toast.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center|bottom"
+ android:gravity="center"
+ android:layout_marginBottom="@dimen/global_actions_info_margin"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintWidth_max="382dp"
+ android:layout_weight="0"
+ android:background="@drawable/global_actions_lite_background"
+ android:theme="@style/Theme.SystemUI.QuickSettings"
+ android:paddingTop="14dp"
+ android:paddingBottom="14dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:orientation="horizontal">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textSize="14sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/global_action_smart_lock_disabled" />
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/global_screenshot_static.xml b/packages/SystemUI/res/layout/global_screenshot_static.xml
index e4a96947aa6a..6a9254cad8f4 100644
--- a/packages/SystemUI/res/layout/global_screenshot_static.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_static.xml
@@ -36,7 +36,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/screenshot_action_container_margin_horizontal"
- android:layout_marginBottom="@dimen/screenshot_action_container_offset_y"
android:paddingEnd="@dimen/screenshot_action_container_padding_right"
android:paddingVertical="@dimen/screenshot_action_container_padding_vertical"
android:elevation="1dp"
diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
new file mode 100644
index 000000000000..918635d666fa
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/internet_connectivity_dialog"
+ android:layout_width="@dimen/internet_dialog_list_max_width"
+ android:layout_height="@dimen/internet_dialog_list_max_height"
+ android:background="@drawable/internet_dialog_rounded_top_corner_background"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Widget.SliceView.Panel"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_marginTop="24dp"
+ android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/internet_dialog_title"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:id="@+id/internet_dialog_subtitle"
+ android:gravity="center_vertical|center_horizontal"
+ android:layout_width="wrap_content"
+ android:layout_height="20dp"
+ android:layout_marginTop="4dp"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.InternetDialog.Secondary"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/internet_dialog_network_layout_margin"
+ android:orientation="vertical">
+
+ <View
+ android:id="@+id/divider"
+ android:layout_gravity="center_vertical|center_horizontal"
+ android:layout_width="340dp"
+ android:layout_height="4dp"
+ android:background="?androidprv:attr/colorSurfaceVariant"/>
+
+ <ProgressBar
+ android:id="@+id/wifi_searching_progress"
+ android:indeterminate="true"
+ android:layout_width="340dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:visibility="gone"
+ style="@style/TrimmedHorizontalProgressBar"/>
+ </LinearLayout>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:id="@+id/scroll_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/ethernet_layout"
+ style="@style/InternetDialog.Network"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center_vertical|start"
+ android:clickable="false">
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:autoMirrored="true"
+ android:src="@drawable/stat_sys_ethernet_fully"
+ android:tint="@color/connected_network_primary_color"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical"
+ android:orientation="vertical"
+ android:clickable="false">
+ <TextView
+ android:text="@string/ethernet_label"
+ style="@style/InternetDialog.NetworkTitle.Active"/>
+ <TextView
+ android:text="@string/to_switch_networks_disconnect_ethernet"
+ style="@style/InternetDialog.NetworkSummary.Active"/>
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mobile_network_layout"
+ style="@style/InternetDialog.Network">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/signal_icon"
+ android:autoMirrored="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/mobile_title"
+ style="@style/InternetDialog.NetworkTitle"/>
+ <TextView
+ android:id="@+id/mobile_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="48dp"
+ android:layout_gravity="end|center_vertical">
+ <Switch
+ android:id="@+id/mobile_toggle"
+ android:contentDescription="@string/mobile_data_settings_title"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="match_parent"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/turn_on_wifi_layout"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:gravity="center"
+ android:clickable="false"
+ android:focusable="false">
+
+ <FrameLayout
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/wifi_toggle_title"
+ android:text="@string/turn_on_wifi"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="48dp"
+ android:layout_marginTop="10dp"
+ android:layout_marginBottom="10dp">
+ <Switch
+ android:id="@+id/wifi_toggle"
+ android:contentDescription="@string/turn_on_wifi"
+ android:switchMinWidth="@dimen/settingslib_switch_track_width"
+ android:layout_gravity="center"
+ android:layout_width="@dimen/settingslib_switch_track_width"
+ android:layout_height="match_parent"
+ android:track="@drawable/settingslib_track_selector"
+ android:thumb="@drawable/settingslib_thumb_selector"
+ android:theme="@style/MainSwitch.Settingslib"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_connected_layout"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="24dp"
+ android:background="@drawable/settingslib_switch_bar_bg_on"
+ android:visibility="gone">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/wifi_connected_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="72dp"
+ android:layout_marginEnd="30dp"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/wifi_connected_title"
+ style="@style/InternetDialog.NetworkTitle.Active"
+ android:textSize="14sp"/>
+ <TextView
+ android:id="@+id/wifi_connected_summary"
+ style="@style/InternetDialog.NetworkSummary.Active"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/wifi_settings_icon"
+ android:src="@drawable/ic_settings_24dp"
+ android:layout_width="24dp"
+ android:layout_gravity="end|center_vertical"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/wifi_list_layout"
+ android:scrollbars="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:overScrollMode="never"
+ android:nestedScrollingEnabled="false"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/see_all_layout"
+ android:layout_width="match_parent"
+ android:layout_height="64dp"
+ android:clickable="true"
+ android:focusable="true"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical|center_horizontal"
+ android:orientation="horizontal"
+ android:paddingStart="22dp"
+ android:paddingEnd="22dp">
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ <ImageView
+ android:id="@+id/arrow_forward"
+ android:src="@drawable/ic_arrow_forward"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/internet_dialog_network_layout_margin">
+ <TextView
+ android:text="@string/see_all_networks"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"/>
+ </FrameLayout>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/done_layout"
+ android:layout_width="67dp"
+ android:layout_height="48dp"
+ android:layout_marginEnd="24dp"
+ android:layout_marginBottom="40dp"
+ android:layout_gravity="end|center_vertical"
+ android:clickable="true"
+ android:focusable="true">
+ <Button
+ android:text="@string/inline_done_button"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:layout_width="match_parent"
+ android:layout_height="36dp"
+ android:layout_gravity="center"
+ android:textAppearance="@style/TextAppearance.InternetDialog"
+ android:textSize="14sp"
+ android:background="@drawable/internet_dialog_footer_background"
+ android:clickable="false"/>
+ </FrameLayout>
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml
new file mode 100644
index 000000000000..868331ec830f
--- /dev/null
+++ b/packages/SystemUI/res/layout/internet_list_item.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/internet_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/wifi_list"
+ style="@style/InternetDialog.Network"
+ android:layout_height="72dp"
+ android:paddingStart="20dp"
+ android:paddingEnd="24dp">
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:clickable="false"
+ android:layout_gravity="center_vertical|start">
+ <ImageView
+ android:id="@+id/wifi_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/wifi_network_layout"
+ android:orientation="vertical"
+ android:clickable="false"
+ android:layout_width="wrap_content"
+ android:layout_height="72dp"
+ android:layout_marginEnd="30dp"
+ android:layout_weight="1"
+ android:gravity="start|center_vertical">
+ <TextView
+ android:id="@+id/wifi_title"
+ style="@style/InternetDialog.NetworkTitle"
+ android:textSize="14sp"/>
+ <TextView
+ android:id="@+id/wifi_summary"
+ style="@style/InternetDialog.NetworkSummary"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:layout_width="24dp"
+ android:layout_height="match_parent"
+ android:clickable="false"
+ android:layout_gravity="end|center_vertical">
+ <ImageView
+ android:id="@+id/wifi_end_icon"
+ android:layout_gravity="end|center_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
index a21a63c3e7c3..9cf09ff328c4 100644
--- a/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
+++ b/packages/SystemUI/res/layout/keyguard_qs_user_switch.xml
@@ -15,19 +15,25 @@
~ limitations under the License
-->
<!-- This is a view that shows a user switcher in Keyguard. -->
-<com.android.systemui.statusbar.phone.UserAvatarView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyguard_qs_user_switch_view"
- android:layout_width="@dimen/kg_framed_avatar_size"
- android:layout_height="@dimen/kg_framed_avatar_size"
- android:layout_centerHorizontal="true"
- android:layout_gravity="top|end"
- android:layout_marginEnd="16dp"
- systemui:avatarPadding="0dp"
- systemui:badgeDiameter="18dp"
- systemui:badgeMargin="1dp"
- systemui:frameColor="@color/kg_user_avatar_frame"
- systemui:framePadding="0dp"
- systemui:frameWidth="0dp">
-</com.android.systemui.statusbar.phone.UserAvatarView>
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="end">
+ <com.android.systemui.statusbar.phone.UserAvatarView
+ android:id="@+id/kg_multi_user_avatar"
+ android:layout_width="@dimen/kg_framed_avatar_size"
+ android:layout_height="@dimen/kg_framed_avatar_size"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|end"
+ android:layout_marginEnd="16dp"
+ systemui:avatarPadding="0dp"
+ systemui:badgeDiameter="18dp"
+ systemui:badgeMargin="1dp"
+ systemui:frameColor="@color/kg_user_avatar_frame"
+ systemui:framePadding="0dp"
+ systemui:frameWidth="0dp">
+ </com.android.systemui.statusbar.phone.UserAvatarView>
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index eb7638233efd..850b01717308 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -30,7 +30,6 @@
android:id="@+id/status_icon_area"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:paddingEnd="@dimen/system_icons_keyguard_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
android:layout_alignParentEnd="true"
android:gravity="center_vertical|end" >
@@ -39,6 +38,7 @@
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginStart="@dimen/system_icons_super_container_margin_start"
+ android:layout_marginEnd="@dimen/status_bar_padding_end"
android:gravity="center_vertical|end">
<include layout="@layout/system_icons" />
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
index c7e54d45626c..c3fc66952395 100644
--- a/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
+++ b/packages/SystemUI/res/layout/media_smartspace_recommendations.xml
@@ -48,20 +48,41 @@
android:id="@+id/recommendation_card_icon"
android:layout_width="@dimen/qs_media_icon_size"
android:layout_height="@dimen/qs_media_icon_size"
+ android:layout_marginTop="@dimen/qs_media_padding"
android:src="@drawable/ic_headset"
- style="@style/MediaPlayer.AppIcon"/>
+ style="@style/MediaPlayer.AppIcon"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="0"/>
<TextView
android:id="@+id/recommendation_card_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:maxLines="2"
+ android:maxLines="1"
android:text="@string/controls_media_smartspace_rec_title"
android:fontFamily="google-sans-medium"
android:textDirection="locale"
android:textSize="@dimen/qq_aa_media_rec_header_text_size"
- android:breakStrategy="balanced"
- android:hyphenationFrequency="none"/>
+ android:hyphenationFrequency="none"
+ app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="0"/>
+
+ <View
+ android:id="@+id/recommendation_gradient_view"
+ android:layout_width="@dimen/qs_aa_media_gradient_bg_width"
+ android:layout_height="0dp"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:background="@drawable/qs_media_recommendation_bg_gradient"
+ app:layout_constraintTop_toTopOf="@id/recommendation_card_text"
+ app:layout_constraintBottom_toBottomOf="@id/recommendation_card_text"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
+ app:layout_constraintHorizontal_bias="1"/>
<FrameLayout
android:id="@+id/media_cover1_container"
diff --git a/packages/SystemUI/res/layout/media_view.xml b/packages/SystemUI/res/layout/media_view.xml
index 075473aef9ac..566cd25e86a5 100644
--- a/packages/SystemUI/res/layout/media_view.xml
+++ b/packages/SystemUI/res/layout/media_view.xml
@@ -163,18 +163,6 @@
</LinearLayout>
</LinearLayout>
- <ImageView
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:tint="?android:attr/textColor"
- android:src="@drawable/ic_cast_connected"
- android:forceHasOverlappingRendering="false" />
-
<!-- Seek Bar -->
<!-- As per Material Design on Biderectionality, this is forced to LTR in code -->
<SeekBar
diff --git a/packages/SystemUI/res/layout/privacy_dialog.xml b/packages/SystemUI/res/layout/privacy_dialog.xml
index 459fb6611fd7..ee4530cb4377 100644
--- a/packages/SystemUI/res/layout/privacy_dialog.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog.xml
@@ -24,9 +24,9 @@
android:layout_marginEnd="@dimen/ongoing_appops_dialog_side_margins"
android:layout_marginTop="8dp"
android:orientation="vertical"
- android:paddingBottom="12dp"
- android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingTop="12dp"
+ android:paddingHorizontal="@dimen/ongoing_appops_dialog_side_padding"
android:background="@drawable/qs_dialog_bg"
/>
-<!-- 12dp padding bottom so there's 20dp total under the icon -->
-<!-- 8dp padding top, as there's 4dp margin in each row --> \ No newline at end of file
+<!-- 8dp padding bottom so there's 16dp total under the icon --> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item.xml b/packages/SystemUI/res/layout/privacy_dialog_item.xml
index 7c8945eddc70..e1f0793f08ad 100644
--- a/packages/SystemUI/res/layout/privacy_dialog_item.xml
+++ b/packages/SystemUI/res/layout/privacy_dialog_item.xml
@@ -24,8 +24,6 @@
android:layout_marginTop="4dp"
android:importantForAccessibility="yes"
android:background="?android:attr/selectableItemBackground"
- android:paddingLeft="@dimen/ongoing_appops_dialog_side_padding"
- android:paddingRight="@dimen/ongoing_appops_dialog_side_padding"
android:focusable="true"
>
<!-- 4dp marginTop makes 20dp minimum between icons -->
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 5b9ca1b26158..42a7c895ff84 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -26,17 +26,38 @@
android:focusable="true"
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
+ <LinearLayout
+ android:id="@+id/clock_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:minWidth="48dp"
- android:minHeight="48dp"
+ android:orientation="horizontal"
+ android:layout_gravity="center_vertical|start"
android:gravity="center_vertical|start"
- android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
- android:singleLine="true"
- android:textAppearance="@style/TextAppearance.QS.Status" />
+ >
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minHeight="48dp"
+ android:gravity="center_vertical|start"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status" />
+
+ <com.android.systemui.statusbar.policy.VariableDateView
+ android:id="@+id/date_clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/status_bar_left_clock_end_padding"
+ android:gravity="center_vertical|start"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.QS.Status"
+ systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+ />
+ </LinearLayout>
<include layout="@layout/qs_carrier_group"
android:id="@+id/carrier_group"
diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
index bff93a99258a..b1e8c386fe21 100644
--- a/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
+++ b/packages/SystemUI/res/layout/quick_status_bar_header_date_privacy.xml
@@ -36,7 +36,7 @@
android:layout_weight="1"
android:gravity="center_vertical|start" >
- <com.android.systemui.statusbar.policy.DateView
+ <com.android.systemui.statusbar.policy.VariableDateView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -44,9 +44,16 @@
android:gravity="center_vertical"
android:singleLine="true"
android:textAppearance="@style/TextAppearance.QS.Status"
- systemui:datePattern="@string/abbrev_wday_month_day_no_year_alarm" />
+ systemui:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ systemui:shortDatePattern="@string/abbrev_month_day_no_year"
+ />
</FrameLayout>
+ <!-- We want this to be centered (to align with notches). In order to do that, the following
+ has to hold (in portrait):
+ * date_container and privacy_container must have the same width and weight
+ * header_text_container must be gone
+ -->
<android.widget.Space
android:id="@+id/space"
android:layout_width="0dp"
@@ -73,7 +80,7 @@
android:layout_weight="1"
android:gravity="center_vertical|end" >
- <include layout="@layout/ongoing_privacy_chip" />
+ <include layout="@layout/ongoing_privacy_chip" />
</FrameLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
index d1cc01f0694e..c122829c01b6 100644
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ b/packages/SystemUI/res/layout/screen_record_dialog.xml
@@ -113,6 +113,8 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_weight="1"
+ android:layout_gravity="fill_vertical"
+ android:gravity="center_vertical"
android:text="@string/screenrecord_taps_label"
android:textAppearance="?android:attr/textAppearanceMedium"
android:fontFamily="@*android:string/config_headlineFontFamily"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 6d5be301a82d..8b787323ccb6 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -54,8 +54,7 @@
<com.android.keyguard.LockIconView
android:id="@+id/lock_icon_view"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center">
+ android:layout_height="wrap_content">
<!-- Background protection -->
<ImageView
android:id="@+id/lock_icon_bg"
@@ -71,6 +70,19 @@
android:padding="@dimen/lock_icon_padding"
android:layout_gravity="center"
android:scaleType="centerCrop"/>
+
+ <!-- Fingerprint -->
+ <!-- AOD dashed fingerprint icon with moving dashes -->
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/lock_udfps_aod_fp"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="@dimen/lock_icon_padding"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ systemui:lottie_autoPlay="false"
+ systemui:lottie_loop="true"
+ systemui:lottie_rawRes="@raw/udfps_aod_fp"/>
</com.android.keyguard.LockIconView>
<com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
@@ -114,6 +126,7 @@
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/close_handle_underlap"
+ android:importantForAccessibility="no"
systemui:layout_constraintStart_toStartOf="parent"
systemui:layout_constraintEnd_toEndOf="parent"
/>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_footer.xml b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
index 412276d05ea3..bbb8df1c5a4a 100644
--- a/packages/SystemUI/res/layout/status_bar_notification_footer.xml
+++ b/packages/SystemUI/res/layout/status_bar_notification_footer.xml
@@ -30,8 +30,8 @@
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/manage_text"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="16dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
android:layout_gravity="start"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
@@ -43,8 +43,8 @@
style="@style/TextAppearance.NotificationSectionHeaderButton"
android:id="@+id/dismiss_text"
android:layout_width="wrap_content"
- android:layout_height="40dp"
- android:layout_marginTop="16dp"
+ android:layout_height="48dp"
+ android:layout_marginTop="12dp"
android:layout_gravity="end"
android:background="@drawable/notif_footer_btn_background"
android:focusable="true"
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bea50e87a29a..71e8fc9991ed 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -62,8 +62,7 @@
<com.android.systemui.statusbar.LightRevealScrim
android:id="@+id/light_reveal_scrim"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone" />
+ android:layout_height="match_parent" />
<include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/udfps_enroll_view.xml b/packages/SystemUI/res/layout/udfps_enroll_view.xml
index f1ff6d669256..e41a632fa70a 100644
--- a/packages/SystemUI/res/layout/udfps_enroll_view.xml
+++ b/packages/SystemUI/res/layout/udfps_enroll_view.xml
@@ -20,10 +20,23 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <!-- The layout height/width are placeholders, which will be overwritten by
+ FingerprintSensorPropertiesInternal. -->
+ <View
+ android:id="@+id/udfps_enroll_accessibility_view"
+ android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/accessibility_fingerprint_label"/>
+
+ <ImageView
+ android:id="@+id/udfps_enroll_animation_fp_progress_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
<!-- Fingerprint -->
<ImageView
android:id="@+id/udfps_enroll_animation_fp_view"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:contentDescription="@string/accessibility_fingerprint_label"/>
+ android:layout_height="match_parent"/>
</com.android.systemui.biometrics.UdfpsEnrollView>
diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml
index 687830d5c7b3..0fcbfa161ddf 100644
--- a/packages/SystemUI/res/layout/udfps_view.xml
+++ b/packages/SystemUI/res/layout/udfps_view.xml
@@ -20,7 +20,7 @@
android:id="@+id/udfps_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- systemui:sensorTouchAreaCoefficient="0.75"
+ systemui:sensorTouchAreaCoefficient="1.0"
android:contentDescription="@string/accessibility_fingerprint_label">
<ViewStub
diff --git a/packages/SystemUI/res/values-h800dp/dimens.xml b/packages/SystemUI/res/values-h800dp/dimens.xml
index 19ec8cee18a8..f057603e2cd0 100644
--- a/packages/SystemUI/res/values-h800dp/dimens.xml
+++ b/packages/SystemUI/res/values-h800dp/dimens.xml
@@ -16,7 +16,7 @@
<resources>
<!-- Minimum margin between clock and top of screen or ambient indication -->
- <dimen name="keyguard_clock_top_margin">76dp</dimen>
+ <dimen name="keyguard_clock_top_margin">38dp</dimen>
<!-- Large clock maximum font size (dp is intentional, to prevent any further scaling) -->
<dimen name="large_clock_text_size">200dp</dimen>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index 461505f12ce6..ffcc3a821d36 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -48,4 +48,16 @@
<item name="android:windowLightStatusBar">false</item>
</style>
+ <style name="TextAppearance.InternetDialog.Active">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">@color/connected_network_primary_color</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Secondary.Active">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">@color/connected_network_secondary_color</item>
+ </style>
+
</resources>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index da80b85b38bf..0a34dfd71c7e 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -95,4 +95,7 @@
<dimen name="controls_top_margin">24dp</dimen>
<dimen name="global_actions_grid_item_layout_height">80dp</dimen>
+
+ <!-- Internet panel related dimensions -->
+ <dimen name="internet_dialog_list_max_width">624dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index b5337d363e12..3121ce37490a 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -69,6 +69,10 @@
<declare-styleable name="DateView">
<attr name="datePattern" format="string" />
</declare-styleable>
+ <declare-styleable name="VariableDateView">
+ <attr name="longDatePattern" format="string" />
+ <attr name="shortDatePattern" format="string" />
+ </declare-styleable>
<declare-styleable name="PseudoGridView">
<attr name="numColumns" format="integer" />
<attr name="verticalSpacing" format="dimension" />
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 2260d2175268..f539b0cf68a1 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-->
-<resources>
+<resources xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<drawable name="notification_number_text_color">#ffffffff</drawable>
<drawable name="ticker_background_color">#ff1d1d1d</drawable>
<drawable name="system_bar_background">@color/system_bar_background_opaque</drawable>
@@ -187,10 +187,8 @@
<!-- UDFPS colors -->
<color name="udfps_enroll_icon">#000000</color> <!-- 100% black -->
<color name="udfps_moving_target_fill">#cc4285f4</color> <!-- 80% blue -->
- <color name="udfps_enroll_progress">#ff669DF6</color> <!-- 100% blue -->
-
- <!-- Logout button -->
- <color name="logout_button_bg_color">#ccffffff</color>
+ <color name="udfps_enroll_progress">#ff669DF6</color> <!-- blue 400 -->
+ <color name="udfps_enroll_progress_help">#ffEE675C</color> <!-- red 400 -->
<!-- Color for the Assistant invocation lights -->
<color name="default_invocation_lights_color">#ffffffff</color> <!-- white -->
@@ -281,4 +279,16 @@
<color name="wallet_card_border">#33FFFFFF</color>
<color name="people_tile_background">@android:color/system_accent2_50</color>
+
+ <!-- Internet Dialog -->
+ <!-- Material next state on color-->
+ <color name="settingslib_state_on_color">@color/settingslib_state_on</color>
+ <!-- Material next state off color-->
+ <color name="settingslib_state_off_color">@color/settingslib_state_off</color>
+ <!-- Material next track on color-->
+ <color name="settingslib_track_on_color">@color/settingslib_track_on</color>
+ <!-- Material next track off color-->
+ <color name="settingslib_track_off_color">@color/settingslib_track_off</color>
+ <color name="connected_network_primary_color">#191C18</color>
+ <color name="connected_network_secondary_color">#41493D</color>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6ad9ab9a26ec..c231afc41953 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -90,7 +90,7 @@
<dimen name="status_bar_left_clock_starting_padding">0dp</dimen>
<!-- End padding for left-aligned status bar clock -->
- <dimen name="status_bar_left_clock_end_padding">7dp</dimen>
+ <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
<!-- Spacing after the wifi signals that is present if there are any icons following it. -->
<dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
@@ -331,11 +331,10 @@
<dimen name="global_screenshot_x_scale">80dp</dimen>
<dimen name="screenshot_bg_protection_height">242dp</dimen>
<dimen name="screenshot_preview_elevation">4dp</dimen>
- <dimen name="screenshot_offset_y">24dp</dimen>
+ <dimen name="screenshot_offset_y">8dp</dimen>
<dimen name="screenshot_offset_x">16dp</dimen>
<dimen name="screenshot_dismiss_button_tappable_size">48dp</dimen>
<dimen name="screenshot_dismiss_button_margin">8dp</dimen>
- <dimen name="screenshot_action_container_offset_y">16dp</dimen>
<dimen name="screenshot_action_container_corner_radius">18dp</dimen>
<dimen name="screenshot_action_container_padding_vertical">4dp</dimen>
<dimen name="screenshot_action_container_margin_horizontal">8dp</dimen>
@@ -744,9 +743,7 @@
<!-- The margin between the status view and the notifications on Keyguard.-->
<dimen name="keyguard_status_view_bottom_margin">20dp</dimen>
<!-- Minimum margin between clock and status bar -->
- <dimen name="keyguard_clock_top_margin">36dp</dimen>
- <!-- The margin between top of clock and bottom of lock icon. -->
- <dimen name="keyguard_clock_lock_margin">16dp</dimen>
+ <dimen name="keyguard_clock_top_margin">18dp</dimen>
<!-- The amount to shift the clocks during a small/large transition -->
<dimen name="keyguard_clock_switch_y_shift">10dp</dimen>
<!-- When large clock is showing, offset the smartspace by this amount -->
@@ -1150,9 +1147,9 @@
<dimen name="default_burn_in_prevention_offset">15dp</dimen>
<!-- The maximum offset for the under-display fingerprint sensor (UDFPS) icon in either
- direction that elements aer moved to prevent burn-in on AOD-->
- <dimen name="udfps_burn_in_offset_x">2dp</dimen>
- <dimen name="udfps_burn_in_offset_y">8dp</dimen>
+ direction that elements are moved to prevent burn-in on AOD-->
+ <dimen name="udfps_burn_in_offset_x">7px</dimen>
+ <dimen name="udfps_burn_in_offset_y">28px</dimen>
<dimen name="corner_size">8dp</dimen>
<dimen name="top_padding">0dp</dimen>
@@ -1243,10 +1240,7 @@
<integer name="wired_charging_keyguard_text_animation_distance">-30</integer>
<!-- Logout button -->
- <dimen name="logout_button_layout_height">32dp</dimen>
- <dimen name="logout_button_padding_horizontal">16dp</dimen>
- <dimen name="logout_button_margin_bottom">12dp</dimen>
- <dimen name="logout_button_corner_radius">4dp</dimen>
+ <dimen name="logout_button_corner_radius">50dp</dimen>
<!-- Blur radius on status bar window and power menu -->
<dimen name="min_window_blur_radius">1px</dimen>
@@ -1277,7 +1271,7 @@
<dimen name="ongoing_appops_dialog_circle_size">32dp</dimen>
- <dimen name="ongoing_appops_dialog_icon_size">20dp</dimen>
+ <dimen name="ongoing_appops_dialog_icon_size">16dp</dimen>
<dimen name="ongoing_appops_dialog_side_padding">16dp</dimen>
@@ -1299,8 +1293,6 @@
<dimen name="qs_media_action_margin">12dp</dimen>
<dimen name="qs_seamless_height">24dp</dimen>
<dimen name="qs_seamless_icon_size">12dp</dimen>
- <dimen name="qs_seamless_fallback_icon_size">@dimen/qs_seamless_icon_size</dimen>
- <dimen name="qs_seamless_fallback_margin">20dp</dimen>
<dimen name="qs_footer_horizontal_margin">22dp</dimen>
<dimen name="qs_media_disabled_seekbar_height">1dp</dimen>
<dimen name="qs_media_enabled_seekbar_height">2dp</dimen>
@@ -1310,6 +1302,7 @@
<!-- Size of Smartspace media recommendations cards in the QSPanel carousel -->
<dimen name="qs_aa_media_rec_album_size_collapsed">72dp</dimen>
<dimen name="qs_aa_media_rec_album_size_expanded">76dp</dimen>
+ <dimen name="qs_aa_media_gradient_bg_width">32dp</dimen>
<dimen name="qs_aa_media_rec_album_margin">8dp</dimen>
<dimen name="qs_aa_media_rec_album_margin_vert">4dp</dimen>
<dimen name="qq_aa_media_rec_header_text_size">16sp</dimen>
@@ -1462,6 +1455,10 @@
<!-- Maximum overshoot for the pulse expansion -->
<dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>
+ <!-- Alpha in duration in ms for the auth ripple to become fully vislble. If set to 0,
+ it is immediately visible. -->
+ <integer name="auth_ripple_alpha_in_duration">100</integer>
+
<dimen name="people_space_widget_radius">28dp</dimen>
<dimen name="people_space_image_radius">20dp</dimen>
<dimen name="people_space_messages_count_radius">12dp</dimen>
@@ -1586,4 +1583,42 @@
<!-- The padding between the icon and the text. -->
<dimen name="ongoing_call_chip_icon_text_padding">4dp</dimen>
<dimen name="ongoing_call_chip_corner_radius">28dp</dimen>
+
+ <!-- Internet panel related dimensions -->
+ <dimen name="internet_dialog_list_margin">12dp</dimen>
+ <dimen name="internet_dialog_list_max_height">646dp</dimen>
+ <dimen name="internet_dialog_list_max_width">@dimen/match_parent</dimen>
+
+ <!-- Signal icon in internet dialog -->
+ <dimen name="signal_strength_icon_size">24dp</dimen>
+
+ <!-- Internet dialog related dimensions -->
+ <dimen name="internet_dialog_corner_radius">24dp</dimen>
+ <!-- End margin of network layout -->
+ <dimen name="internet_dialog_network_layout_margin">16dp</dimen>
+ <!-- Size of switch bar in internet dialog -->
+ <dimen name="settingslib_switchbar_margin">16dp</dimen>
+ <!-- Minimum width of switch -->
+ <dimen name="settingslib_min_switch_width">52dp</dimen>
+ <!-- Size of layout margin left -->
+ <dimen name="settingslib_switchbar_padding_left">20dp</dimen>
+ <!-- Size of layout margin right -->
+ <dimen name="settingslib_switchbar_padding_right">20dp</dimen>
+ <!-- Radius of switch bar -->
+ <dimen name="settingslib_switch_bar_radius">35dp</dimen>
+ <!-- Margin of switch thumb -->
+ <dimen name="settingslib_switch_thumb_margin">4dp</dimen>
+ <!-- Size of switch thumb -->
+ <dimen name="settingslib_switch_thumb_size">20dp</dimen>
+ <!-- Width of switch track -->
+ <dimen name="settingslib_switch_track_width">52dp</dimen>
+ <!-- Height of switch track -->
+ <dimen name="settingslib_switch_track_height">28dp</dimen>
+ <!-- Radius of switch track -->
+ <dimen name="settingslib_switch_track_radius">35dp</dimen>
+
+ <!-- Height percentage of the parent container occupied by the communal view -->
+ <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item>
+
+ <dimen name="drag_and_drop_icon_size">70dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a67021611812..7aacb702b75a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -211,6 +211,8 @@
<!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
<string name="global_action_screenshot">Screenshot</string>
+ <!-- Message shown in power menu when smart lock has been disabled [CHAR_LIMIT=NONE] -->
+ <string name="global_action_smart_lock_disabled">Smart Lock disabled</string>
<!-- text to show in place of RemoteInput images when they cannot be shown.
[CHAR LIMIT=50] -->
@@ -1006,7 +1008,7 @@
<string name="sensor_privacy_start_use_mic_camera_dialog_content">This unblocks access for all apps and services allowed to use your camera or microphone.</string>
<!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
- <string name="media_seamless_remote_device">Device</string>
+ <string name="media_seamless_other_device">Other device</string>
<!-- QuickStep: Accessibility to toggle overview [CHAR LIMIT=40] -->
<string name="quick_step_accessibility_toggle_overview">Toggle Overview</string>
@@ -1777,6 +1779,8 @@
<string name="tuner_full_importance_settings">Power notification controls</string>
<string name="tuner_full_importance_settings_on">On</string>
<string name="tuner_full_importance_settings_off">Off</string>
+ <!-- [CHAR LIMIT=NONE] Notification camera based rotation enabled description -->
+ <string name="rotation_lock_camera_rotation_on">On - Face-based</string>
<string name="power_notification_controls_description">With power notification controls, you can set an importance level from 0 to 5 for an app\'s notifications.
\n\n<b>Level 5</b>
\n- Show at the top of the notification list
@@ -2992,4 +2996,41 @@
<string name="global_actions_change_description" translatable="false"><xliff:g>%1$s</xliff:g></string>
<!-- URL for more information about changes in global actions -->
<string name="global_actions_change_url" translatable="false"></string>
+
+ <!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
+ <string name="mobile_data_settings_title">Mobile data</string>
+ <!-- Provider Model: Summary text separator for preferences including a short description
+ (eg. "Connected / 5G"). [CHAR LIMIT=50] -->
+ <string name="preference_summary_default_combination"><xliff:g id="state" example="Connected">%1$s</xliff:g> / <xliff:g id="networkMode" example="LTE">%2$s</xliff:g></string>
+ <!-- Provider Model:
+ Summary indicating that a SIM has an active mobile data connection [CHAR LIMIT=50] -->
+ <string name="mobile_data_connection_active">Connected</string>
+ <!-- Provider Model:
+ Summary indicating that a SIM has no mobile data connection [CHAR LIMIT=50] -->
+ <string name="mobile_data_off_summary">Mobile data won\u0027t auto\u2011connect</string>
+ <!-- Provider Model:
+ Summary indicating that a active SIM and no network available [CHAR LIMIT=50] -->
+ <string name="mobile_data_no_connection">No connection</string>
+ <!-- Provider Model: Summary indicating that no other networks available [CHAR LIMIT=50] -->
+ <string name="non_carrier_network_unavailable">No other networks available</string>
+ <!-- Provider Model: Summary indicating that no networks available [CHAR LIMIT=50] -->
+ <string name="all_network_unavailable">No networks available</string>
+ <!-- Provider Model: Panel title text for turning on the Wi-Fi networks. [CHAR LIMIT=40] -->
+ <string name="turn_on_wifi">Wi\u2011Fi</string>
+ <!-- Provider Model: Title for detail page of wifi network [CHAR LIMIT=30] -->
+ <string name="pref_title_network_details" msgid="7329759534269363308">"Network details"</string>
+ <!-- Provider Model: Panel subtitle for tapping a network to connect to internet. [CHAR LIMIT=60] -->
+ <string name="tap_a_network_to_connect">Tap a network to connect</string>
+ <!-- Provider Model: Panel subtitle for unlocking screen to view networks. [CHAR LIMIT=60] -->
+ <string name="unlock_to_view_networks">Unlock to view networks</string>
+ <!-- Provider Model: Wi-Fi settings. text displayed when Wi-Fi is on and network list is empty [CHAR LIMIT=50]-->
+ <string name="wifi_empty_list_wifi_on">Searching for networks\u2026</string>
+ <!-- Provider Model: Failure notification for connect -->
+ <string name="wifi_failed_connect_message">Failed to connect to network</string>
+ <!-- Provider Model: Toast message for when the user selects cellular as the internet provider and Wi-Fi auto-connect is temporarily disabled [CHAR LIMIT=60]-->
+ <string name="wifi_wont_autoconnect_for_now">Wi\u2011Fi won\u2019t auto-connect for now</string>
+ <!-- Provider Model: Title to see all the networks [CHAR LIMIT=50] -->
+ <string name="see_all_networks">See all</string>
+ <!-- Summary for warning to disconnect ethernet first then switch to other networks. [CHAR LIMIT=60] -->
+ <string name="to_switch_networks_disconnect_ethernet">To switch networks, disconnect ethernet</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 51eabf60385e..93d60cce2915 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -416,7 +416,9 @@
</style>
<!-- Overridden by values-television/styles.xml with tv-specific settings -->
- <style name="volume_dialog_theme" parent="Theme.SystemUI"/>
+ <style name="volume_dialog_theme" parent="Theme.SystemUI">
+ <item name="android:windowIsFloating">true</item>
+ </style>
<style name="Theme.SystemUI.Dialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog" />
@@ -903,4 +905,108 @@
<!-- Setting a placeholder will avoid using the SystemUI icon on the splash screen. -->
<item name="android:windowSplashScreenAnimatedIcon">@drawable/ic_blank</item>
</style>
+
+ <style name="Animation.InternetDialog" parent="@android:style/Animation.InputMethod">
+ </style>
+
+ <style name="Widget.SliceView.Panel">
+ <item name="titleSize">16sp</item>
+ <item name="rowStyle">@style/SliceRow</item>
+ <item name="android:background">?android:attr/colorBackgroundFloating</item>
+ </style>
+
+ <style name="SliceRow">
+ <!-- 2dp start padding for the start icon -->
+ <item name="titleItemStartPadding">2dp</item>
+ <item name="titleItemEndPadding">0dp</item>
+
+ <!-- Padding between content and the start icon is 14dp -->
+ <item name="contentStartPadding">14dp</item>
+ <!-- Padding between content and end items is 16dp -->
+ <item name="contentEndPadding">16dp</item>
+
+ <!-- Both side margins of end item are 16dp -->
+ <item name="endItemStartPadding">16dp</item>
+ <item name="endItemEndPadding">16dp</item>
+
+ <!-- Both side margins of bottom divider are 12dp -->
+ <item name="bottomDividerStartPadding">12dp</item>
+ <item name="bottomDividerEndPadding">12dp</item>
+
+ <item name="actionDividerHeight">32dp</item>
+ </style>
+
+ <style name="Theme.SystemUI.Dialog.Internet">
+ <item name="android:windowBackground">@drawable/internet_dialog_background</item>
+ </style>
+
+ <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item>
+ </style>
+
+ <style name="TrimmedHorizontalProgressBar"
+ parent="android:Widget.Material.ProgressBar.Horizontal">
+ <item name="android:indeterminateDrawable">
+ @drawable/progress_indeterminate_horizontal_material_trimmed
+ </item>
+ <item name="android:minHeight">4dp</item>
+ <item name="android:maxHeight">4dp</item>
+ </style>
+
+ <!-- Internet Dialog -->
+ <style name="InternetDialog">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center_vertical|start</item>
+ <item name="android:layout_marginStart">@dimen/internet_dialog_network_layout_margin</item>
+ </style>
+
+ <style name="InternetDialog.Network">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">88dp</item>
+ <item name="android:layout_marginEnd">@dimen/internet_dialog_network_layout_margin</item>
+ <item name="android:paddingStart">22dp</item>
+ <item name="android:paddingEnd">22dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:focusable">true</item>
+ <item name="android:clickable">true</item>
+ </style>
+
+ <style name="InternetDialog.NetworkTitle">
+ <item name="android:layout_marginEnd">7dp</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog</item>
+ </style>
+
+ <style name="InternetDialog.NetworkTitle.Active">
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item>
+ </style>
+
+ <style name="InternetDialog.NetworkSummary">
+ <item name="android:layout_marginEnd">34dp</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary</item>
+ </style>
+
+ <style name="InternetDialog.NetworkSummary.Active">
+ <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active
+ </item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog">
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textSize">16sp</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Secondary">
+ <item name="android:textSize">14sp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="TextAppearance.InternetDialog.Active"/>
+
+ <style name="TextAppearance.InternetDialog.Secondary.Active"/>
+
</resources>
diff --git a/packages/SystemUI/res/xml/media_collapsed.xml b/packages/SystemUI/res/xml/media_collapsed.xml
index d6c6a60d56b8..c3510b61e68a 100644
--- a/packages/SystemUI/res/xml/media_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_collapsed.xml
@@ -44,23 +44,6 @@
/>
<Constraint
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="@dimen/qs_media_padding"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:alpha="0.5"
- android:visibility="gone"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintBottom_toTopOf="@id/center_horizontal_guideline"
- app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size_small"
android:layout_height="@dimen/qs_media_album_size_small"
diff --git a/packages/SystemUI/res/xml/media_expanded.xml b/packages/SystemUI/res/xml/media_expanded.xml
index 0e284e6ad631..6b83aae38e15 100644
--- a/packages/SystemUI/res/xml/media_expanded.xml
+++ b/packages/SystemUI/res/xml/media_expanded.xml
@@ -45,22 +45,6 @@
android:layout_marginBottom="4dp" />
<Constraint
- android:id="@+id/media_seamless_fallback"
- android:layout_width="@dimen/qs_seamless_fallback_icon_size"
- android:layout_height="@dimen/qs_seamless_fallback_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- android:layout_marginBottom="16dp"
- android:layout_marginStart="@dimen/qs_center_guideline_padding"
- android:layout_marginEnd="@dimen/qs_seamless_fallback_margin"
- android:alpha="0.5"
- android:visibility="gone"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toEndOf="@id/center_vertical_guideline"
- app:layout_constraintEnd_toEndOf="parent"
- />
-
- <Constraint
android:id="@+id/album_art"
android:layout_width="@dimen/qs_media_album_size"
android:layout_height="@dimen/qs_media_album_size"
diff --git a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
index 5c41ad82bd0b..b6258d1c71eb 100644
--- a/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_collapsed.xml
@@ -19,25 +19,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_icon_size"
- android:layout_height="@dimen/qs_media_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
- android:id="@+id/recommendation_card_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
android:id="@+id/media_cover1_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_aa_media_rec_album_size_collapsed"
diff --git a/packages/SystemUI/res/xml/media_recommendation_expanded.xml b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
index 8a3d5ca75a77..2fb33415e0df 100644
--- a/packages/SystemUI/res/xml/media_recommendation_expanded.xml
+++ b/packages/SystemUI/res/xml/media_recommendation_expanded.xml
@@ -19,25 +19,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/recommendation_card_icon"
- android:layout_width="@dimen/qs_media_icon_size"
- android:layout_height="@dimen/qs_media_icon_size"
- android:layout_marginTop="@dimen/qs_media_padding"
- app:layout_constraintTop_toTopOf="parent"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
- android:id="@+id/recommendation_card_text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- app:layout_constraintTop_toBottomOf="@id/recommendation_card_icon"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintEnd_toStartOf="@id/media_vertical_start_guideline"
- app:layout_constraintHorizontal_bias="0" />
-
- <Constraint
android:id="@+id/media_cover1_container"
android:layout_width="0dp"
android:layout_height="@dimen/qs_aa_media_rec_album_size_expanded"
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 92f89d6b90fd..a383cab94c36 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -20,12 +20,16 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Resources;
import android.graphics.Color;
import android.icu.text.NumberFormat;
+import androidx.annotation.VisibleForTesting;
+
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -67,20 +71,20 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
BroadcastDispatcher broadcastDispatcher,
BatteryController batteryController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController bypassController) {
+ KeyguardBypassController bypassController,
+ @Main Resources resources
+ ) {
super(view);
mStatusBarStateController = statusBarStateController;
- mIsDozing = mStatusBarStateController.isDozing();
- mDozeAmount = mStatusBarStateController.getDozeAmount();
mBroadcastDispatcher = broadcastDispatcher;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mBatteryController = batteryController;
mBurmeseNumerals = mBurmeseNf.format(FORMAT_NUMBER);
- mBurmeseLineSpacing = getContext().getResources().getFloat(
+ mBurmeseLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale_burmese);
- mDefaultLineSpacing = getContext().getResources().getFloat(
+ mDefaultLineSpacing = resources.getFloat(
R.dimen.keyguard_clock_line_spacing_scale);
}
@@ -106,7 +110,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
}
};
- private final StatusBarStateController.StateListener mStatusBarStatePersistentListener =
+ private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
@@ -144,11 +148,11 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mBroadcastDispatcher.registerReceiver(mLocaleBroadcastReceiver,
new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
mDozeAmount = mStatusBarStateController.getDozeAmount();
+ mIsDozing = mStatusBarStateController.isDozing() || mDozeAmount != 0;
mBatteryController.addCallback(mBatteryCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- mStatusBarStateController.addCallback(mStatusBarStatePersistentListener);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
refreshTime();
initColors();
@@ -160,9 +164,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mBroadcastDispatcher.unregisterReceiver(mLocaleBroadcastReceiver);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mBatteryController.removeCallback(mBatteryCallback);
- if (!mView.isAttachedToWindow()) {
- mStatusBarStateController.removeCallback(mStatusBarStatePersistentListener);
- }
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
}
/** Animate the clock appearance */
@@ -191,6 +193,14 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
mView.refreshFormat();
}
+ /**
+ * Return locallly stored dozing state.
+ */
+ @VisibleForTesting
+ public boolean isDozing() {
+ return mIsDozing;
+ }
+
private void updateLocale() {
Locale currLocale = Locale.getDefault();
if (!Objects.equals(currLocale, mLocale)) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index f4a3fb236ffa..7a01b4e8c971 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -20,6 +20,7 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.WallpaperManager;
+import android.content.res.Resources;
import android.text.TextUtils;
import android.view.View;
import android.widget.FrameLayout;
@@ -30,6 +31,7 @@ import com.android.keyguard.clock.ClockManager;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -65,18 +67,22 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final BroadcastDispatcher mBroadcastDispatcher;
private final BatteryController mBatteryController;
private final LockscreenSmartspaceController mSmartspaceController;
+ private final Resources mResources;
/**
* Clock for both small and large sizes
*/
private AnimatableClockController mClockViewController;
- private FrameLayout mClockFrame;
+ private FrameLayout mClockFrame; // top aligned clock
private AnimatableClockController mLargeClockViewController;
- private FrameLayout mLargeClockFrame;
+ private FrameLayout mLargeClockFrame; // centered clock
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mBypassController;
+ private int mLargeClockTopMargin = 0;
+ private int mKeyguardClockTopMargin = 0;
+
/**
* Listener for changes to the color palette.
*
@@ -113,7 +119,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
KeyguardBypassController bypassController,
LockscreenSmartspaceController smartspaceController,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- SmartspaceTransitionController smartspaceTransitionController) {
+ SmartspaceTransitionController smartspaceTransitionController,
+ @Main Resources resources) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mColorExtractor = colorExtractor;
@@ -125,6 +132,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mSmartspaceController = smartspaceController;
+ mResources = resources;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
mSmartspaceTransitionController = smartspaceTransitionController;
@@ -154,7 +162,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mBypassController);
+ mBypassController,
+ mResources);
mClockViewController.init();
mLargeClockViewController =
@@ -164,7 +173,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mBroadcastDispatcher,
mBatteryController,
mKeyguardUpdateMonitor,
- mBypassController);
+ mBypassController,
+ mResources);
mLargeClockViewController.init();
}
@@ -175,6 +185,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
mColorExtractor.addOnColorsChangedListener(mColorsListener);
mView.updateColors(getGradientColors());
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
if (mOnlyClock) {
View ksa = mView.findViewById(R.id.keyguard_status_area);
@@ -249,6 +261,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
*/
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
+ mKeyguardClockTopMargin =
+ mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
updateClockLayout();
}
@@ -257,9 +271,12 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
if (mSmartspaceController.isEnabled()) {
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(MATCH_PARENT,
MATCH_PARENT);
- lp.topMargin = getContext().getResources().getDimensionPixelSize(
+ mLargeClockTopMargin = getContext().getResources().getDimensionPixelSize(
R.dimen.keyguard_large_clock_top_margin);
+ lp.topMargin = mLargeClockTopMargin;
mLargeClockFrame.setLayoutParams(lp);
+ } else {
+ mLargeClockTopMargin = 0;
}
}
@@ -369,6 +386,28 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
}
}
+ /**
+ * Get y-bottom position of the currently visible clock on the keyguard.
+ * We can't directly getBottom() because clock changes positions in AOD for burn-in
+ */
+ int getClockBottom(int statusBarHeaderHeight) {
+ if (mLargeClockFrame.getVisibility() == View.VISIBLE) {
+ View clock = mLargeClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view_large);
+ int frameHeight = mLargeClockFrame.getHeight();
+ int clockHeight = clock.getHeight();
+ return frameHeight / 2 + clockHeight / 2;
+ } else {
+ return mClockFrame.findViewById(
+ com.android.systemui.R.id.animatable_clock_view).getHeight()
+ + statusBarHeaderHeight + mKeyguardClockTopMargin;
+ }
+ }
+
+ boolean isClockTopAligned() {
+ return mLargeClockFrame.getVisibility() != View.VISIBLE;
+ }
+
private void updateAodIcons() {
NotificationIconContainer nic = (NotificationIconContainer)
mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 72e502816534..6b3e9c27c25f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -185,6 +185,20 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
/**
+ * Get y-bottom position of the currently visible clock.
+ */
+ public int getClockBottom(int statusBarHeaderHeight) {
+ return mKeyguardClockSwitchController.getClockBottom(statusBarHeaderHeight);
+ }
+
+ /**
+ * @return true if the currently displayed clock is top aligned (as opposed to center aligned)
+ */
+ public boolean isClockTopAligned() {
+ return mKeyguardClockSwitchController.isClockTopAligned();
+ }
+
+ /**
* Set whether the view accessibility importance mode.
*/
public void setStatusAccessibilityImportance(int mode) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 28a54d56b071..e115c342c4fe 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -132,8 +132,7 @@ public class KeyguardVisibilityHelper {
.alpha(1f)
.withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable)
.start();
- } else if (mUnlockedScreenOffAnimationController
- .isScreenOffLightRevealAnimationPlaying()) {
+ } else if (mUnlockedScreenOffAnimationController.shouldAnimateInKeyguard()) {
mKeyguardViewVisibilityAnimating = true;
// Ask the screen off animation controller to animate the keyguard visibility for us
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 622419a86bfc..ef4353b93179 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,12 +22,12 @@ import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils;
import com.android.systemui.Dumpable;
@@ -45,9 +45,10 @@ public class LockIconView extends FrameLayout implements Dumpable {
private int mRadius;
private ImageView mLockIcon;
- private ImageView mUnlockBgView;
+ private ImageView mBgView;
private int mLockIconColor;
+ private boolean mUseBackground = false;
public LockIconView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -58,19 +59,19 @@ public class LockIconView extends FrameLayout implements Dumpable {
public void onFinishInflate() {
super.onFinishInflate();
mLockIcon = findViewById(R.id.lock_icon);
- mUnlockBgView = findViewById(R.id.lock_icon_bg);
+ mBgView = findViewById(R.id.lock_icon_bg);
}
- void updateColorAndBackgroundVisibility(boolean useBackground) {
- if (useBackground) {
+ void updateColorAndBackgroundVisibility() {
+ if (mUseBackground && mLockIcon.getDrawable() != null) {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
android.R.attr.textColorPrimary);
- mUnlockBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
- mUnlockBgView.setVisibility(View.VISIBLE);
+ mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg));
+ mBgView.setVisibility(View.VISIBLE);
} else {
mLockIconColor = Utils.getColorAttrDefaultColor(getContext(),
R.attr.wallpaperTextColorAccent);
- mUnlockBgView.setVisibility(View.GONE);
+ mBgView.setVisibility(View.GONE);
}
mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor));
@@ -78,9 +79,29 @@ public class LockIconView extends FrameLayout implements Dumpable {
void setImageDrawable(Drawable drawable) {
mLockIcon.setImageDrawable(drawable);
+
+ if (!mUseBackground) return;
+
+ if (drawable == null) {
+ mBgView.setVisibility(View.INVISIBLE);
+ } else {
+ mBgView.setVisibility(View.VISIBLE);
+ }
}
- void setCenterLocation(@NonNull PointF center, int radius) {
+ /**
+ * Whether or not to render the lock icon background. Mainly used for UDPFS.
+ */
+ public void setUseBackground(boolean useBackground) {
+ mUseBackground = useBackground;
+ updateColorAndBackgroundVisibility();
+ }
+
+ /**
+ * Set the location of the lock icon.
+ */
+ @VisibleForTesting
+ public void setCenterLocation(@NonNull PointF center, int radius) {
mLockIconCenter = center;
mRadius = radius;
@@ -91,13 +112,11 @@ public class LockIconView extends FrameLayout implements Dumpable {
mLockIconCenter.x + mRadius,
mLockIconCenter.y + mRadius);
- setX(mSensorRect.left);
- setY(mSensorRect.top);
-
- final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
- (int) (mSensorRect.right - mSensorRect.left),
- (int) (mSensorRect.bottom - mSensorRect.top));
- lp.gravity = Gravity.CENTER;
+ final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ lp.width = (int) (mSensorRect.right - mSensorRect.left);
+ lp.height = (int) (mSensorRect.bottom - mSensorRect.top);
+ lp.topMargin = (int) mSensorRect.top;
+ lp.setMarginStart((int) mSensorRect.left);
setLayoutParams(lp);
}
@@ -114,5 +133,6 @@ public class LockIconView extends FrameLayout implements Dumpable {
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("Center in px (x, y)= (" + mLockIconCenter.x + ", " + mLockIconCenter.y + ")");
pw.println("Radius in pixels: " + mRadius);
+ pw.println("topLeft= (" + getX() + ", " + getY() + ")");
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 509ac8a6d9fe..52ebf2fa09d0 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -19,6 +19,8 @@ package com.android.keyguard;
import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
import android.content.Context;
import android.content.res.Configuration;
@@ -32,6 +34,7 @@ import android.media.AudioAttributes;
import android.os.Process;
import android.os.Vibrator;
import android.util.DisplayMetrics;
+import android.util.MathUtils;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
@@ -46,6 +49,7 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -58,6 +62,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.airbnb.lottie.LottieAnimationView;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Objects;
@@ -67,7 +73,8 @@ import javax.inject.Inject;
/**
* Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen.
*
- * This view will only be shown if the user has UDFPS or FaceAuth enrolled
+ * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock
+ * icon will show a set distance from the bottom of the device.
*/
@StatusBarComponent.StatusBarScope
public class LockIconViewController extends ViewController<LockIconView> implements Dumpable {
@@ -92,6 +99,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull private final DelayableExecutor mExecutor;
private boolean mUdfpsEnrolled;
+ @NonNull private LottieAnimationView mAodFp;
+
@NonNull private final AnimatedVectorDrawable mFpToUnlockIcon;
@NonNull private final AnimatedVectorDrawable mLockToUnlockIcon;
@NonNull private final Drawable mLockIcon;
@@ -99,6 +108,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull private CharSequence mUnlockedLabel;
@NonNull private CharSequence mLockedLabel;
@Nullable private final Vibrator mVibrator;
+ @Nullable private final AuthRippleController mAuthRippleController;
private boolean mIsDozing;
private boolean mIsBouncerShowing;
@@ -109,6 +119,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private boolean mIsKeyguardShowing;
private boolean mUserUnlockedWithBiometric;
private Runnable mCancelDelayedUpdateVisibilityRunnable;
+ private Runnable mOnGestureDetectedRunnable;
private boolean mUdfpsSupported;
private float mHeightPixels;
@@ -118,6 +129,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private boolean mShowUnlockIcon;
private boolean mShowLockIcon;
+ // for udfps when strong auth is required or unlocked on AOD
+ private boolean mShowAODFpIcon;
+ private final int mMaxBurnInOffsetX;
+ private final int mMaxBurnInOffsetY;
+ private float mInterpolatedDarkAmount;
+
private boolean mDownDetected;
private boolean mDetectedLongPress;
private final Rect mSensorTouchLocation = new Rect();
@@ -135,7 +152,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@NonNull AccessibilityManager accessibilityManager,
@NonNull ConfigurationController configurationController,
@NonNull @Main DelayableExecutor executor,
- @Nullable Vibrator vibrator
+ @Nullable Vibrator vibrator,
+ @Nullable AuthRippleController authRippleController
) {
super(view);
mStatusBarStateController = statusBarStateController;
@@ -148,8 +166,15 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mConfigurationController = configurationController;
mExecutor = executor;
mVibrator = vibrator;
+ mAuthRippleController = authRippleController;
final Context context = view.getContext();
+ mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp);
+ mMaxBurnInOffsetX = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x);
+ mMaxBurnInOffsetY = context.getResources()
+ .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
+
mUnlockIcon = mView.getContext().getResources().getDrawable(
R.drawable.ic_unlock,
mView.getContext().getTheme());
@@ -173,15 +198,14 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
protected void onViewAttached() {
- // we check this here instead of onInit since the FingerprintManager + FaceManager may not
- // have started up yet onInit
- mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
-
+ updateIsUdfpsEnrolled();
updateConfiguration();
updateKeyguardShowing();
mUserUnlockedWithBiometric = false;
+
mIsBouncerShowing = mKeyguardViewController.isBouncerShowing();
mIsDozing = mStatusBarStateController.isDozing();
+ mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount();
mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen();
mStatusBarState = mStatusBarStateController.getState();
@@ -189,15 +213,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
updateColors();
mConfigurationController.addCallback(mConfigurationListener);
+ mAuthController.addCallback(mAuthControllerCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mDownDetected = false;
+ updateBurnInOffsets();
updateVisibility();
}
@Override
protected void onViewDetached() {
+ mAuthController.removeCallback(mAuthControllerCallback);
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
@@ -227,7 +254,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mCancelDelayedUpdateVisibilityRunnable = null;
}
- if (!mIsKeyguardShowing) {
+ if (!mIsKeyguardShowing && !mIsDozing) {
mView.setVisibility(View.INVISIBLE);
return;
}
@@ -238,6 +265,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen()
&& (!mUdfpsEnrolled || !mRunningFPS);
mShowUnlockIcon = mCanDismissLockScreen && isLockScreen();
+ mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS;
final CharSequence prevContentDescription = mView.getContentDescription();
if (mShowLockIcon) {
@@ -260,10 +288,22 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
}
mView.setVisibility(View.VISIBLE);
mView.setContentDescription(mUnlockedLabel);
+ } else if (mShowAODFpIcon) {
+ mView.setImageDrawable(null);
+ mView.setContentDescription(null);
+ mAodFp.setVisibility(View.VISIBLE);
+ mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel);
+ mView.setVisibility(View.VISIBLE);
} else {
mView.setVisibility(View.INVISIBLE);
mView.setContentDescription(null);
}
+
+ if (!mShowAODFpIcon) {
+ mAodFp.setVisibility(View.INVISIBLE);
+ mAodFp.setContentDescription(null);
+ }
+
if (!Objects.equals(prevContentDescription, mView.getContentDescription())
&& mView.getContentDescription() != null) {
mView.announceForAccessibility(mView.getContentDescription());
@@ -305,7 +345,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
}
private void updateColors() {
- mView.updateColorAndBackgroundVisibility(mUdfpsSupported);
+ mView.updateColorAndBackgroundVisibility();
}
private void updateConfiguration() {
@@ -340,10 +380,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.println("mUdfpsSupported: " + mUdfpsSupported);
pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled);
pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing);
pw.println(" mShowUnlockIcon: " + mShowUnlockIcon);
pw.println(" mShowLockIcon: " + mShowLockIcon);
+ pw.println(" mShowAODFpIcon: " + mShowAODFpIcon);
pw.println(" mIsDozing: " + mIsDozing);
pw.println(" mIsBouncerShowing: " + mIsBouncerShowing);
pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric);
@@ -351,17 +393,59 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen);
pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState));
pw.println(" mQsExpanded: " + mQsExpanded);
+ pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount);
if (mView != null) {
mView.dump(fd, pw, args);
}
}
+ /** Every minute, update the aod icon's burn in offset */
+ public void dozeTimeTick() {
+ updateBurnInOffsets();
+ }
+
+ private void updateBurnInOffsets() {
+ float offsetX = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */)
+ - mMaxBurnInOffsetX, mInterpolatedDarkAmount);
+ float offsetY = MathUtils.lerp(0f,
+ getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
+ - mMaxBurnInOffsetY, mInterpolatedDarkAmount);
+ float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
+
+ mAodFp.setTranslationX(offsetX);
+ mAodFp.setTranslationY(offsetY);
+ mAodFp.setProgress(progress);
+ mAodFp.setAlpha(255 * mInterpolatedDarkAmount);
+ }
+
+ private void updateIsUdfpsEnrolled() {
+ boolean wasUdfpsSupported = mUdfpsSupported;
+ boolean wasUdfpsEnrolled = mUdfpsEnrolled;
+
+ mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null;
+ mView.setUseBackground(mUdfpsSupported);
+
+ mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) {
+ updateVisibility();
+ }
+ }
+
private StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ mInterpolatedDarkAmount = eased;
+ updateBurnInOffsets();
+ }
+
+ @Override
public void onDozingChanged(boolean isDozing) {
mIsDozing = isDozing;
+ updateBurnInOffsets();
+ updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -435,7 +519,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(
KeyguardUpdateMonitor.getCurrentUser());
}
- mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled();
+ updateIsUdfpsEnrolled();
updateVisibility();
}
@@ -481,8 +565,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
// intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or
// MotionEvent.ACTION_UP (see #onTouchEvent)
- mDownDetected = true;
- if (mVibrator != null) {
+ if (mVibrator != null && !mDownDetected) {
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
@@ -490,6 +573,8 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
"lockIcon-onDown",
VIBRATION_SONIFICATION_ATTRIBUTES);
}
+
+ mDownDetected = true;
return true;
}
@@ -497,8 +582,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
if (!wasClickableOnDownEvent()) {
return;
}
+ mDetectedLongPress = true;
- if (mVibrator != null) {
+ if (onAffordanceClick() && mVibrator != null) {
+ // only vibrate if the click went through and wasn't intercepted by falsing
mVibrator.vibrate(
Process.myUid(),
getContext().getOpPackageName(),
@@ -506,8 +593,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
"lockIcon-onLongPress",
VIBRATION_SONIFICATION_ATTRIBUTES);
}
- mDetectedLongPress = true;
- onAffordanceClick();
}
public boolean onSingleTapUp(MotionEvent e) {
@@ -531,15 +616,27 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
return mDownDetected;
}
- private void onAffordanceClick() {
+ /**
+ * Whether we tried to launch the affordance.
+ *
+ * If falsing intercepts the click, returns false.
+ */
+ private boolean onAffordanceClick() {
if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
- return;
+ return false;
}
// pre-emptively set to true to hide view
mIsBouncerShowing = true;
+ if (mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) {
+ mAuthRippleController.showRipple(FINGERPRINT);
+ }
updateVisibility();
+ if (mOnGestureDetectedRunnable != null) {
+ mOnGestureDetectedRunnable.run();
+ }
mKeyguardViewController.showBouncer(/* scrim */ true);
+ return true;
}
});
@@ -548,16 +645,18 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
* in a 'clickable' state
* @return whether to intercept the touch event
*/
- public boolean onTouchEvent(MotionEvent event) {
+ public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) {
if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY())
- && mView.getVisibility() == View.VISIBLE) {
+ && (mView.getVisibility() == View.VISIBLE
+ || mAodFp.getVisibility() == View.VISIBLE)) {
+ mOnGestureDetectedRunnable = onGestureDetectedRunnable;
mGestureDetector.onTouchEvent(event);
}
// we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP
// or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV
// after the lock icon disappears on device entry
- if (mDownDetected && mDetectedLongPress) {
+ if (mDownDetected) {
if (event.getAction() == MotionEvent.ACTION_CANCEL
|| event.getAction() == MotionEvent.ACTION_UP) {
mDownDetected = false;
@@ -577,4 +676,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
+
+ private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ updateIsUdfpsEnrolled();
+ updateConfiguration();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
index 3a0357d2a284..4331f52eb1a2 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardQsUserSwitchComponent.java
@@ -16,7 +16,8 @@
package com.android.keyguard.dagger;
-import com.android.systemui.statusbar.phone.UserAvatarView;
+import android.widget.FrameLayout;
+
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import dagger.BindsInstance;
@@ -31,8 +32,7 @@ public interface KeyguardQsUserSwitchComponent {
/** Simple factory for {@link KeyguardUserSwitcherComponent}. */
@Subcomponent.Factory
interface Factory {
- KeyguardQsUserSwitchComponent build(
- @BindsInstance UserAvatarView userAvatarView);
+ KeyguardQsUserSwitchComponent build(@BindsInstance FrameLayout userAvatarContainer);
}
/** Builds a {@link KeyguardQsUserSwitchController}. */
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 76f30a80114a..00b33a416df4 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -65,6 +65,7 @@ import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.PowerUI;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.qs.ReduceBrightColorsController;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenrecord.RecordingController;
@@ -364,6 +365,7 @@ public class Dependency {
@Inject Lazy<UiEventLogger> mUiEventLogger;
@Inject Lazy<FeatureFlags> mFeatureFlagsLazy;
@Inject Lazy<StatusBarContentInsetsProvider> mContentInsetsProviderLazy;
+ @Inject Lazy<InternetDialogFactory> mInternetDialogFactory;
@Inject
public Dependency() {
@@ -578,6 +580,7 @@ public class Dependency {
mProviders.put(PrivacyDotViewController.class, mPrivacyDotViewControllerLazy::get);
mProviders.put(EdgeBackGestureHandler.Factory.class,
mEdgeBackGestureHandlerFactoryLazy::get);
+ mProviders.put(InternetDialogFactory.class, mInternetDialogFactory::get);
mProviders.put(UiEventLogger.class, mUiEventLogger::get);
mProviders.put(FeatureFlags.class, mFeatureFlagsLazy::get);
mProviders.put(StatusBarContentInsetsProvider.class, mContentInsetsProviderLazy::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index a68f79604b25..8379ccf3154a 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -20,6 +20,8 @@ import android.app.WallpaperColors;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
@@ -29,7 +31,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Size;
-import android.view.DisplayInfo;
import android.view.SurfaceHolder;
import android.view.WindowManager;
@@ -90,7 +91,7 @@ public class ImageWallpaper extends WallpaperService {
mMiniBitmap = null;
}
- class GLEngine extends Engine {
+ class GLEngine extends Engine implements DisplayListener {
// Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
// set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
@VisibleForTesting
@@ -102,15 +103,15 @@ public class ImageWallpaper extends WallpaperService {
private EglHelper mEglHelper;
private final Runnable mFinishRenderingTask = this::finishRendering;
private boolean mNeedRedraw;
- private int mWidth = 1;
- private int mHeight = 1;
+
+ private boolean mDisplaySizeValid = false;
+ private int mDisplayWidth = 1;
+ private int mDisplayHeight = 1;
+
private int mImgWidth = 1;
private int mImgHeight = 1;
- private float mPageWidth = 1.f;
- private float mPageOffset = 1.f;
- GLEngine() {
- }
+ GLEngine() { }
@VisibleForTesting
GLEngine(Handler handler) {
@@ -119,18 +120,29 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
+ Trace.beginSection("ImageWallpaper.Engine#onCreate");
mEglHelper = getEglHelperInstance();
// Deferred init renderer because we need to get wallpaper by display context.
mRenderer = getRendererInstance();
setFixedSizeAllowed(true);
updateSurfaceSize();
- Rect window = getDisplayContext()
- .getSystemService(WindowManager.class)
- .getCurrentWindowMetrics()
- .getBounds();
- mHeight = window.height();
- mWidth = window.width();
mRenderer.setOnBitmapChanged(this::updateMiniBitmap);
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .registerDisplayListener(this, mWorker.getThreadHandler());
+ Trace.endSection();
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) { }
+
+ @Override
+ public void onDisplayRemoved(int displayId) { }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ if (displayId == getDisplayContext().getDisplayId()) {
+ mDisplaySizeValid = false;
+ }
}
EglHelper getEglHelperInstance() {
@@ -154,26 +166,10 @@ public class ImageWallpaper extends WallpaperService {
if (pages == mPages) return;
mPages = pages;
if (mMiniBitmap == null || mMiniBitmap.isRecycled()) return;
- updateShift();
mWorker.getThreadHandler().post(() ->
computeAndNotifyLocalColors(new ArrayList<>(mColorAreas), mMiniBitmap));
}
- private void updateShift() {
- if (mImgHeight == 0) {
- mPageOffset = 0;
- mPageWidth = 1;
- return;
- }
- // calculate shift
- DisplayInfo displayInfo = new DisplayInfo();
- getDisplayContext().getDisplay().getDisplayInfo(displayInfo);
- int screenWidth = displayInfo.getNaturalWidth();
- float imgWidth = Math.min(mImgWidth > 0 ? screenWidth / (float) mImgWidth : 1.f, 1.f);
- mPageWidth = imgWidth;
- mPageOffset = (1 - imgWidth) / (float) (mPages - 1);
- }
-
private void updateMiniBitmap(Bitmap b) {
if (b == null) return;
int size = Math.min(b.getWidth(), b.getHeight());
@@ -203,13 +199,22 @@ public class ImageWallpaper extends WallpaperService {
}
@Override
+ public boolean shouldWaitForEngineShown() {
+ return true;
+ }
+
+ @Override
public void onDestroy() {
+ getDisplayContext().getSystemService(DisplayManager.class)
+ .unregisterDisplayListener(this);
mMiniBitmap = null;
mWorker.getThreadHandler().post(() -> {
+ Trace.beginSection("ImageWallpaper.Engine#onDestroy");
mRenderer.finish();
mRenderer = null;
mEglHelper.finish();
mEglHelper = null;
+ Trace.endSection();
});
}
@@ -268,6 +273,16 @@ public class ImageWallpaper extends WallpaperService {
* (1-Wr)].
*/
private RectF pageToImgRect(RectF area) {
+ if (!mDisplaySizeValid) {
+ Rect window = getDisplayContext()
+ .getSystemService(WindowManager.class)
+ .getCurrentWindowMetrics()
+ .getBounds();
+ mDisplayWidth = window.width();
+ mDisplayHeight = window.height();
+ mDisplaySizeValid = true;
+ }
+
// Width of a page for the caller of this API.
float virtualPageWidth = 1f / (float) mPages;
float leftPosOnPage = (area.left % virtualPageWidth) / virtualPageWidth;
@@ -275,12 +290,24 @@ public class ImageWallpaper extends WallpaperService {
int currentPage = (int) Math.floor(area.centerX() / virtualPageWidth);
RectF imgArea = new RectF();
+
+ if (mImgWidth == 0 || mImgHeight == 0 || mDisplayWidth <= 0 || mDisplayHeight <= 0) {
+ return imgArea;
+ }
+
imgArea.bottom = area.bottom;
imgArea.top = area.top;
+
+ float imageScale = Math.min(((float) mImgHeight) / mDisplayHeight, 1);
+ float mappedScreenWidth = mDisplayWidth * imageScale;
+ float pageWidth = Math.min(1.0f,
+ mImgWidth > 0 ? mappedScreenWidth / (float) mImgWidth : 1.f);
+ float pageOffset = (1 - pageWidth) / (float) (mPages - 1);
+
imgArea.left = MathUtils.constrain(
- leftPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+ leftPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
imgArea.right = MathUtils.constrain(
- rightPosOnPage * mPageWidth + currentPage * mPageOffset, 0, 1);
+ rightPosOnPage * pageWidth + currentPage * pageOffset, 0, 1);
if (imgArea.left > imgArea.right) {
// take full page
imgArea.left = 0;
@@ -293,7 +320,6 @@ public class ImageWallpaper extends WallpaperService {
private List<WallpaperColors> getLocalWallpaperColors(@NonNull List<RectF> areas,
Bitmap b) {
List<WallpaperColors> colors = new ArrayList<>(areas.size());
- updateShift();
for (int i = 0; i < areas.size(); i++) {
RectF area = pageToImgRect(areas.get(i));
if (area == null || !LOCAL_COLOR_BOUNDS.contains(area)) {
@@ -322,8 +348,10 @@ public class ImageWallpaper extends WallpaperService {
public void onSurfaceCreated(SurfaceHolder holder) {
if (mWorker == null) return;
mWorker.getThreadHandler().post(() -> {
+ Trace.beginSection("ImageWallpaper#onSurfaceCreated");
mEglHelper.init(holder, needSupportWideColorGamut());
mRenderer.onSurfaceCreated();
+ Trace.endSection();
});
}
@@ -340,9 +368,11 @@ public class ImageWallpaper extends WallpaperService {
}
private void drawFrame() {
+ Trace.beginSection("ImageWallpaper#drawFrame");
preRender();
requestRender();
postRender();
+ Trace.endSection();
}
public void preRender() {
@@ -409,6 +439,7 @@ public class ImageWallpaper extends WallpaperService {
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#postRender");
scheduleFinishRendering();
+ reportEngineShown(false /* waitForEngineShown */);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index affad7a57d86..8c63f7bec23c 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -438,6 +438,12 @@ public class SwipeHelper implements Gefingerpoken {
private boolean mCancelled;
@Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mCallback.onBeginDrag(animView);
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
index 81a13a236685..408201558a9b 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistOrbController.java
@@ -57,7 +57,9 @@ public class AssistOrbController {
public void run() {
mView.removeCallbacks(this);
mView.show(false /* show */, true /* animate */, () -> {
- mWindowManager.removeView(mView);
+ if (mView.isAttachedToWindow()) {
+ mWindowManager.removeView(mView);
+ }
});
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index c9e67715decb..5616a00592f2 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -40,10 +40,10 @@ import com.android.systemui.people.widget.PeopleBackupHelper
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
*/
-class BackupHelper : BackupAgentHelper() {
+open class BackupHelper : BackupAgentHelper() {
companion object {
- private const val TAG = "BackupHelper"
+ const val TAG = "BackupHelper"
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 3f61d3c6af9a..fd37b3509a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -126,6 +126,7 @@ public class AuthContainerView extends LinearLayout
boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
+ long mRequestId;
@BiometricMultiSensorMode int mMultiSensorConfig;
}
@@ -172,6 +173,12 @@ public class AuthContainerView extends LinearLayout
return this;
}
+ /** Unique id for this request. */
+ public Builder setRequestId(long requestId) {
+ mConfig.mRequestId = requestId;
+ return this;
+ }
+
/** The multi-sensor mode. */
public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
mConfig.mMultiSensorConfig = multiSensorConfig;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 0ce1846e7745..0790af94f287 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -42,6 +42,7 @@ import android.hardware.face.FaceManager;
import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Bundle;
@@ -49,6 +50,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.util.SparseBooleanArray;
import android.view.MotionEvent;
import android.view.WindowManager;
@@ -76,6 +78,9 @@ import kotlin.Unit;
/**
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
+ *
+ * Also coordinates biometric-related things, such as UDFPS, with
+ * {@link com.android.keyguard.KeyguardUpdateMonitor}
*/
@SysUISingleton
public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@@ -115,6 +120,8 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
@Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
+ @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
+
private class BiometricTaskStackListener extends TaskStackListener {
@Override
public void onTaskStackChanged() {
@@ -122,6 +129,21 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
+ private final FingerprintStateListener mFingerprintStateListener =
+ new FingerprintStateListener() {
+ @Override
+ public void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments) {
+ Log.d(TAG, "onEnrollmentsChanged, userId: " + userId
+ + ", sensorId: " + sensorId
+ + ", hasEnrollments: " + hasEnrollments);
+ for (FingerprintSensorPropertiesInternal prop : mUdfpsProps) {
+ if (prop.sensorId == sensorId) {
+ mUdfpsEnrolledForUser.put(userId, hasEnrollments);
+ }
+ }
+ }
+ };
+
@NonNull
private final IFingerprintAuthenticatorsRegisteredCallback
mFingerprintAuthenticatorsRegisteredCallback =
@@ -436,6 +458,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mUdfpsControllerFactory = udfpsControllerFactory;
mSidefpsControllerFactory = sidefpsControllerFactory;
mWindowManager = windowManager;
+ mUdfpsEnrolledForUser = new SparseBooleanArray();
mOrientationListener = new BiometricOrientationEventListener(context,
() -> {
onOrientationChanged();
@@ -474,6 +497,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
if (mFingerprintManager != null) {
mFingerprintManager.addAuthenticatorsRegisteredCallback(
mFingerprintAuthenticatorsRegisteredCallback);
+ mFingerprintManager.registerFingerprintStateListener(mFingerprintStateListener);
}
mTaskStackListener = new BiometricTaskStackListener();
@@ -501,7 +525,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
@Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
@@ -515,6 +539,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
+ ", credentialAllowed: " + credentialAllowed
+ ", requireConfirmation: " + requireConfirmation
+ ", operationId: " + operationId
+ + ", requestId: " + requestId
+ ", multiSensorConfig: " + multiSensorConfig);
}
SomeArgs args = SomeArgs.obtain();
@@ -526,6 +551,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
+ args.arg8 = requestId;
args.argi2 = multiSensorConfig;
boolean skipAnimation = false;
@@ -629,6 +655,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
if (mCurrentDialog == null) {
// Could be possible if the caller canceled authentication after credential success
// but before the client was notified.
+ if (DEBUG) Log.d(TAG, "dialog already gone");
return;
}
@@ -670,7 +697,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
return false;
}
- return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
+ return mUdfpsEnrolledForUser.get(userId);
}
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
@@ -683,6 +710,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
final int userId = args.argi1;
final String opPackageName = (String) args.arg6;
final long operationId = (long) args.arg7;
+ final long requestId = (long) args.arg8;
final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
// Create a new dialog but do not replace the current one yet.
@@ -695,6 +723,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
opPackageName,
skipAnimation,
operationId,
+ requestId,
multiSensorConfig);
if (newDialog == null) {
@@ -772,7 +801,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
- boolean skipIntro, long operationId,
+ boolean skipIntro, long operationId, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
@@ -782,11 +811,16 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
.setOperationId(operationId)
+ .setRequestId(requestId)
.setMultiSensorConfig(multiSensorConfig)
.build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
}
- interface Callback {
+ /**
+ * AuthController callback used to receive signal for when biometric authenticators are
+ * registered.
+ */
+ public interface Callback {
/**
* Called when authenticators are registered. If authenticators are already
* registered before this call, this callback will never be triggered.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 1df8ad5e51fb..2630f119de00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -16,14 +16,19 @@
package com.android.systemui.biometrics
+import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType
+import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.settingslib.Utils
+import com.android.systemui.R
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.statusbar.CircleReveal
import com.android.systemui.statusbar.LightRevealEffect
import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -34,9 +39,14 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.ViewController
import java.io.PrintWriter
import javax.inject.Inject
+import javax.inject.Provider
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+
+private const val WAKE_AND_UNLOCK_FADE_DURATION = 180L
/***
* Controls the ripple effect that shows when authentication is successful.
@@ -49,37 +59,65 @@ class AuthRippleController @Inject constructor(
private val authController: AuthController,
private val configurationController: ConfigurationController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val keyguardStateController: KeyguardStateController,
+ private val wakefulnessLifecycle: WakefulnessLifecycle,
private val commandRegistry: CommandRegistry,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val bypassController: KeyguardBypassController,
private val biometricUnlockController: BiometricUnlockController,
+ private val udfpsControllerProvider: Provider<UdfpsController>,
+ private val statusBarStateController: StatusBarStateController,
rippleView: AuthRippleView?
-) : ViewController<AuthRippleView>(rippleView) {
+) : ViewController<AuthRippleView>(rippleView), KeyguardStateController.Callback,
+ WakefulnessLifecycle.Observer {
+
+ @VisibleForTesting
+ internal var startLightRevealScrimOnKeyguardFadingAway = false
var fingerprintSensorLocation: PointF? = null
private var faceSensorLocation: PointF? = null
private var circleReveal: LightRevealEffect? = null
+ private var udfpsController: UdfpsController? = null
+
+ private var dwellScale = 2f
+ private var expandedDwellScale = 2.5f
+ private var aodDwellScale = 1.9f
+ private var aodExpandedDwellScale = 2.3f
+ private var udfpsRadius: Float = -1f
+
+ override fun onInit() {
+ mView.setAlphaInDuration(sysuiContext.resources.getInteger(
+ R.integer.auth_ripple_alpha_in_duration).toLong())
+ }
+
@VisibleForTesting
public override fun onViewAttached() {
+ authController.addCallback(authControllerCallback)
updateRippleColor()
updateSensorLocation()
- authController.addCallback(authControllerCallback)
+ updateUdfpsDependentParams()
+ udfpsController?.addCallback(udfpsControllerCallback)
configurationController.addCallback(configurationChangedListener)
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+ keyguardStateController.addCallback(this)
+ wakefulnessLifecycle.addObserver(this)
commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() }
}
@VisibleForTesting
public override fun onViewDetached() {
+ udfpsController?.removeCallback(udfpsControllerCallback)
authController.removeCallback(authControllerCallback)
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
configurationController.removeCallback(configurationChangedListener)
+ keyguardStateController.removeCallback(this)
+ wakefulnessLifecycle.removeObserver(this)
commandRegistry.unregisterCommand("auth-ripple")
notificationShadeWindowController.setForcePluginOpen(false, this)
}
- private fun showRipple(biometricSourceType: BiometricSourceType?) {
+ fun showRipple(biometricSourceType: BiometricSourceType?) {
if (!keyguardUpdateMonitor.isKeyguardVisible ||
keyguardUpdateMonitor.userNeedsStrongAuth()) {
return
@@ -88,43 +126,61 @@ class AuthRippleController @Inject constructor(
if (biometricSourceType == BiometricSourceType.FINGERPRINT &&
fingerprintSensorLocation != null) {
mView.setSensorLocation(fingerprintSensorLocation!!)
- showRipple()
+ showUnlockedRipple()
} else if (biometricSourceType == BiometricSourceType.FACE &&
faceSensorLocation != null) {
if (!bypassController.canBypass()) {
return
}
mView.setSensorLocation(faceSensorLocation!!)
- showRipple()
+ showUnlockedRipple()
}
}
- private fun showRipple() {
+ private fun showUnlockedRipple() {
notificationShadeWindowController.setForcePluginOpen(true, this)
- val biometricUnlockMode = biometricUnlockController.mode
- val useCircleReveal = circleReveal != null &&
- (biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK ||
- biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING ||
- biometricUnlockMode == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+ val useCircleReveal = circleReveal != null && biometricUnlockController.isWakeAndUnlock
val lightRevealScrim = statusBar.lightRevealScrim
if (useCircleReveal) {
lightRevealScrim?.revealEffect = circleReveal!!
+ startLightRevealScrimOnKeyguardFadingAway = true
}
- mView.startRipple(
+ mView.startUnlockedRipple(
/* end runnable */
Runnable {
notificationShadeWindowController.setForcePluginOpen(false, this)
- },
- /* circleReveal */
- if (useCircleReveal) {
- lightRevealScrim
- } else {
- null
}
)
}
+ override fun onKeyguardFadingAwayChanged() {
+ if (keyguardStateController.isKeyguardFadingAway) {
+ val lightRevealScrim = statusBar.lightRevealScrim
+ if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) {
+ val revealAnimator = ValueAnimator.ofFloat(.1f, 1f).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = RIPPLE_ANIMATION_DURATION
+ startDelay = keyguardStateController.keyguardFadingAwayDelay
+ addUpdateListener { animator ->
+ if (lightRevealScrim.revealEffect != circleReveal) {
+ // if the something else took over the reveal, let's do nothing.
+ return@addUpdateListener
+ }
+ lightRevealScrim.revealAmount = animator.animatedValue as Float
+ }
+ }
+ revealAnimator.start()
+ startLightRevealScrimOnKeyguardFadingAway = false
+ }
+ }
+ }
+
+ override fun onStartedGoingToSleep() {
+ // reset the light reveal start in case we were pending an unlock
+ startLightRevealScrimOnKeyguardFadingAway = false
+ }
+
fun updateSensorLocation() {
fingerprintSensorLocation = authController.fingerprintSensorLocation
faceSensorLocation = authController.faceAuthSensorLocation
@@ -146,7 +202,23 @@ class AuthRippleController @Inject constructor(
Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor)
}
- val keyguardUpdateMonitorCallback =
+ private fun showDwellRipple() {
+ if (statusBarStateController.isDozing) {
+ mView.startDwellRipple(
+ /* startRadius */ udfpsRadius,
+ /* endRadius */ udfpsRadius * aodDwellScale,
+ /* expandedRadius */ udfpsRadius * aodExpandedDwellScale,
+ /* isDozing */ true)
+ } else {
+ mView.startDwellRipple(
+ /* startRadius */ udfpsRadius,
+ /* endRadius */ udfpsRadius * dwellScale,
+ /* expandedRadius */ udfpsRadius * expandedDwellScale,
+ /* isDozing */ false)
+ }
+ }
+
+ private val keyguardUpdateMonitorCallback =
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthenticated(
userId: Int,
@@ -155,9 +227,13 @@ class AuthRippleController @Inject constructor(
) {
showRipple(biometricSourceType)
}
+
+ override fun onBiometricAuthFailed(biometricSourceType: BiometricSourceType?) {
+ mView.retractRipple()
+ }
}
- val configurationChangedListener =
+ private val configurationChangedListener =
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
updateSensorLocation()
@@ -173,14 +249,70 @@ class AuthRippleController @Inject constructor(
}
}
- private val authControllerCallback = AuthController.Callback { updateSensorLocation() }
+ private val udfpsControllerCallback =
+ object : UdfpsController.Callback {
+ override fun onFingerDown() {
+ if (fingerprintSensorLocation == null) {
+ Log.e("AuthRipple", "fingerprintSensorLocation=null onFingerDown. " +
+ "Skip showing dwell ripple")
+ return
+ }
+
+ mView.setSensorLocation(fingerprintSensorLocation!!)
+ showDwellRipple()
+ }
+
+ override fun onFingerUp() {
+ mView.retractRipple()
+ }
+ }
+
+ private val authControllerCallback = AuthController.Callback {
+ updateSensorLocation()
+ updateUdfpsDependentParams()
+ }
+
+ private fun updateUdfpsDependentParams() {
+ authController.udfpsProps?.let {
+ if (it.size > 0) {
+ udfpsRadius = it[0].sensorRadius.toFloat()
+ udfpsController = udfpsControllerProvider.get()
+
+ if (mView.isAttachedToWindow) {
+ udfpsController?.addCallback(udfpsControllerCallback)
+ }
+ }
+ }
+ }
inner class AuthRippleCommand : Command {
+ fun printLockScreenDwellInfo(pw: PrintWriter) {
+ pw.println("lock screen dwell ripple: " +
+ "\n\tsensorLocation=$fingerprintSensorLocation" +
+ "\n\tdwellScale=$dwellScale" +
+ "\n\tdwellExpand=$expandedDwellScale")
+ }
+
+ fun printAodDwellInfo(pw: PrintWriter) {
+ pw.println("aod dwell ripple: " +
+ "\n\tsensorLocation=$fingerprintSensorLocation" +
+ "\n\tdwellScale=$aodDwellScale" +
+ "\n\tdwellExpand=$aodExpandedDwellScale")
+ }
+
override fun execute(pw: PrintWriter, args: List<String>) {
if (args.isEmpty()) {
invalidCommand(pw)
} else {
when (args[0]) {
+ "dwell" -> {
+ showDwellRipple()
+ if (statusBarStateController.isDozing) {
+ printAodDwellInfo(pw)
+ } else {
+ printLockScreenDwellInfo(pw)
+ }
+ }
"fingerprint" -> {
pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation")
showRipple(BiometricSourceType.FINGERPRINT)
@@ -199,7 +331,7 @@ class AuthRippleController @Inject constructor(
pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " +
args[2].toFloat())
mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat()))
- showRipple()
+ showUnlockedRipple()
}
else -> invalidCommand(pw)
}
@@ -209,6 +341,7 @@ class AuthRippleController @Inject constructor(
override fun help(pw: PrintWriter) {
pw.println("Usage: adb shell cmd statusbar auth-ripple <command>")
pw.println("Available commands:")
+ pw.println(" dwell")
pw.println(" fingerprint")
pw.println(" face")
pw.println(" custom <x-location: int> <y-location: int>")
@@ -219,4 +352,8 @@ class AuthRippleController @Inject constructor(
help(pw)
}
}
+
+ companion object {
+ const val RIPPLE_ANIMATION_DURATION: Long = 1533
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 95ea81003ecb..c6d26ffb9957 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -27,21 +27,40 @@ import android.util.AttributeSet
import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
-import com.android.systemui.statusbar.LightRevealScrim
+import com.android.systemui.animation.Interpolators
import com.android.systemui.statusbar.charging.RippleShader
-private const val RIPPLE_ANIMATION_DURATION: Long = 1533
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
/**
- * Expanding ripple effect on the transition from biometric authentication success to showing
+ * Expanding ripple effect
+ * - startUnlockedRipple for the transition from biometric authentication success to showing
* launcher.
+ * - startDwellRipple for the ripple expansion out when the user has their finger down on the UDFPS
+ * sensor area
+ * - retractRipple for the ripple animation inwards to signal a failure
*/
class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- private var rippleInProgress: Boolean = false
+ private val retractInterpolator = PathInterpolator(.05f, .93f, .1f, 1f)
+
+ private val dwellPulseDuration = 50L
+ private val dwellAlphaDuration = dwellPulseDuration
+ private val dwellAlpha: Float = 1f
+ private val dwellExpandDuration = 1200L - dwellPulseDuration
+
+ private val aodDwellPulseDuration = 50L
+ private var aodDwellAlphaDuration = aodDwellPulseDuration
+ private var aodDwellAlpha: Float = .8f
+ private var aodDwellExpandDuration = 1200L - aodDwellPulseDuration
+
+ private val retractDuration = 400L
+ private var alphaInDuration: Long = 0
+ private var unlockedRippleInProgress: Boolean = false
private val rippleShader = RippleShader()
private val ripplePaint = Paint()
- private var radius: Float = 0.0f
+ private var retractAnimator: Animator? = null
+ private var dwellPulseOutAnimator: Animator? = null
+ private var radius: Float = 0f
set(value) {
rippleShader.radius = value
field = value
@@ -62,51 +81,200 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
fun setSensorLocation(location: PointF) {
origin = location
- radius = maxOf(location.x, location.y, width - location.x, height - location.y)
- .toFloat()
+ radius = maxOf(location.x, location.y, width - location.x, height - location.y).toFloat()
}
- fun startRipple(onAnimationEnd: Runnable?, lightReveal: LightRevealScrim?) {
- if (rippleInProgress) {
- return // Ignore if ripple effect is already playing
+ fun setAlphaInDuration(duration: Long) {
+ alphaInDuration = duration
+ }
+
+ /**
+ * Animate ripple inwards back to radius 0
+ */
+ fun retractRipple() {
+ if (retractAnimator?.isRunning == true) {
+ return // let the animation finish
+ }
+
+ if (dwellPulseOutAnimator?.isRunning == true) {
+ val retractRippleAnimator = ValueAnimator.ofFloat(rippleShader.progress, 0f)
+ .apply {
+ interpolator = retractInterpolator
+ duration = retractDuration
+ addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.time = now.toFloat()
+
+ invalidate()
+ }
+ }
+
+ val retractAlphaAnimator = ValueAnimator.ofInt(255, 0).apply {
+ interpolator = Interpolators.LINEAR
+ duration = retractDuration
+ addUpdateListener { animator ->
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ rippleShader.color,
+ animator.animatedValue as Int
+ )
+ invalidate()
+ }
+ }
+
+ retractAnimator = AnimatorSet().apply {
+ playTogether(retractRippleAnimator, retractAlphaAnimator)
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ dwellPulseOutAnimator?.cancel()
+ rippleShader.shouldFadeOutRipple = false
+ visibility = VISIBLE
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ visibility = GONE
+ resetRippleAlpha()
+ }
+ })
+ start()
+ }
+ }
+ }
+
+ /**
+ * Ripple that moves animates from an outer ripple ring of
+ * startRadius => endRadius => expandedRadius
+ */
+ fun startDwellRipple(
+ startRadius: Float,
+ endRadius: Float,
+ expandedRadius: Float,
+ isDozing: Boolean
+ ) {
+ if (unlockedRippleInProgress || dwellPulseOutAnimator?.isRunning == true) {
+ return
}
- val rippleAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
- interpolator = PathInterpolator(0.4f, 0f, 0f, 1f)
- duration = RIPPLE_ANIMATION_DURATION
+ // we divide by 4 because the desired startRadius and endRadius is for the ripple's outer
+ // ring see RippleShader
+ val startDwellProgress = startRadius / radius / 4f
+ val endInitialDwellProgress = endRadius / radius / 4f
+ val endExpandDwellProgress = expandedRadius / radius / 4f
+
+ val alpha = if (isDozing) aodDwellAlpha else dwellAlpha
+ val pulseOutEndAlpha = (255 * alpha).toInt()
+ val expandDwellEndAlpha = kotlin.math.min((255 * (alpha + .25f)).toInt(), 255)
+ val dwellPulseOutRippleAnimator = ValueAnimator.ofFloat(startDwellProgress,
+ endInitialDwellProgress).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = if (isDozing) aodDwellPulseDuration else dwellPulseDuration
addUpdateListener { animator ->
val now = animator.currentPlayTime
rippleShader.progress = animator.animatedValue as Float
rippleShader.time = now.toFloat()
- lightReveal?.revealAmount = animator.animatedValue as Float
invalidate()
}
}
- val revealAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
- interpolator = rippleAnimator.interpolator
- startDelay = 10
- duration = rippleAnimator.duration
+ val dwellPulseOutAlphaAnimator = ValueAnimator.ofInt(0, pulseOutEndAlpha).apply {
+ interpolator = Interpolators.LINEAR
+ duration = if (isDozing) aodDwellAlphaDuration else dwellAlphaDuration
addUpdateListener { animator ->
- lightReveal?.revealAmount = animator.animatedValue as Float
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ rippleShader.color,
+ animator.animatedValue as Int
+ )
+ invalidate()
}
}
- val alphaInAnimator = ValueAnimator.ofInt(0, 127).apply {
- duration = 167
+ // slowly animate outwards until we receive a call to retractRipple or startUnlockedRipple
+ val expandDwellRippleAnimator = ValueAnimator.ofFloat(endInitialDwellProgress,
+ endExpandDwellProgress).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
+ addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.time = now.toFloat()
+
+ invalidate()
+ }
+ }
+
+ val expandDwellAlphaAnimator = ValueAnimator.ofInt(pulseOutEndAlpha, expandDwellEndAlpha)
+ .apply {
+ interpolator = Interpolators.LINEAR
+ duration = if (isDozing) aodDwellExpandDuration else dwellExpandDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
- rippleShader.color,
- animator.animatedValue as Int
+ rippleShader.color,
+ animator.animatedValue as Int
)
invalidate()
}
}
- val alphaOutAnimator = ValueAnimator.ofInt(127, 0).apply {
- startDelay = 417
- duration = 1116
+ val initialDwellPulseOutAnimator = AnimatorSet().apply {
+ playTogether(dwellPulseOutRippleAnimator, dwellPulseOutAlphaAnimator)
+ }
+ val expandDwellAnimator = AnimatorSet().apply {
+ playTogether(expandDwellRippleAnimator, expandDwellAlphaAnimator)
+ }
+
+ dwellPulseOutAnimator = AnimatorSet().apply {
+ playSequentially(
+ initialDwellPulseOutAnimator,
+ expandDwellAnimator
+ )
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?) {
+ retractAnimator?.cancel()
+ rippleShader.shouldFadeOutRipple = false
+ visibility = VISIBLE
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ visibility = GONE
+ resetRippleAlpha()
+ }
+ })
+ start()
+ }
+ }
+
+ /**
+ * Ripple that bursts outwards from the position of the sensor to the edges of the screen
+ */
+ fun startUnlockedRipple(onAnimationEnd: Runnable?) {
+ if (unlockedRippleInProgress) {
+ return // Ignore if ripple effect is already playing
+ }
+
+ var rippleStart = 0f
+ var alphaDuration = alphaInDuration
+ if (dwellPulseOutAnimator?.isRunning == true || retractAnimator?.isRunning == true) {
+ rippleStart = rippleShader.progress
+ alphaDuration = 0
+ dwellPulseOutAnimator?.cancel()
+ retractAnimator?.cancel()
+ }
+
+ val rippleAnimator = ValueAnimator.ofFloat(rippleStart, 1f).apply {
+ interpolator = Interpolators.LINEAR_OUT_SLOW_IN
+ duration = AuthRippleController.RIPPLE_ANIMATION_DURATION
+ addUpdateListener { animator ->
+ val now = animator.currentPlayTime
+ rippleShader.progress = animator.animatedValue as Float
+ rippleShader.time = now.toFloat()
+
+ invalidate()
+ }
+ }
+
+ val alphaInAnimator = ValueAnimator.ofInt(0, 255).apply {
+ duration = alphaDuration
addUpdateListener { animator ->
rippleShader.color = ColorUtils.setAlphaComponent(
rippleShader.color,
@@ -119,19 +287,18 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
val animatorSet = AnimatorSet().apply {
playTogether(
rippleAnimator,
- revealAnimator,
- alphaInAnimator,
- alphaOutAnimator
+ alphaInAnimator
)
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
- rippleInProgress = true
+ unlockedRippleInProgress = true
+ rippleShader.shouldFadeOutRipple = true
visibility = VISIBLE
}
override fun onAnimationEnd(animation: Animator?) {
onAnimationEnd?.run()
- rippleInProgress = false
+ unlockedRippleInProgress = false
visibility = GONE
}
})
@@ -139,8 +306,16 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
animatorSet.start()
}
+ fun resetRippleAlpha() {
+ rippleShader.color = ColorUtils.setAlphaComponent(
+ rippleShader.color,
+ 255
+ )
+ }
+
fun setColor(color: Int) {
rippleShader.color = color
+ resetRippleAlpha()
}
override fun onDraw(canvas: Canvas?) {
@@ -148,7 +323,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 1.5f
+ (1 - rippleShader.progress)) * radius * 2f
canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e612fb4712fc..420ae531b3dd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -39,11 +39,9 @@ import android.hardware.fingerprint.IUdfpsOverlayController;
import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
import android.media.AudioAttributes;
import android.os.Handler;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -64,7 +62,6 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -72,12 +69,16 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.util.time.SystemClock;
+import java.util.HashSet;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Inject;
@@ -91,7 +92,7 @@ import kotlin.Unit;
* controls/manages all UDFPS sensors. In other words, a single controller is registered with
* {@link com.android.server.biometrics.sensors.fingerprint.FingerprintService}, and interfaces such
* as {@link FingerprintManager#onPointerDown(int, int, int, float, float)} or
- * {@link IUdfpsOverlayController#showUdfpsOverlay(int)}should all have
+ * {@link IUdfpsOverlayController#showUdfpsOverlay(int)} should all have
* {@code sensorId} parameters.
*/
@SuppressWarnings("deprecation")
@@ -115,9 +116,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final DumpManager mDumpManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@Nullable private final Vibrator mVibrator;
- @NonNull private final Handler mMainHandler;
@NonNull private final FalsingManager mFalsingManager;
@NonNull private final PowerManager mPowerManager;
@NonNull private final AccessibilityManager mAccessibilityManager;
@@ -125,6 +124,9 @@ public class UdfpsController implements DozeReceiver {
@Nullable private final UdfpsHbmProvider mHbmProvider;
@NonNull private final KeyguardBypassController mKeyguardBypassController;
@NonNull private final ConfigurationController mConfigurationController;
+ @NonNull private final SystemClock mSystemClock;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
@VisibleForTesting @NonNull final BiometricOrientationEventListener mOrientationListener;
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@@ -157,12 +159,14 @@ public class UdfpsController implements DozeReceiver {
private Runnable mAodInterruptRunnable;
private boolean mOnFingerDown;
private boolean mAttemptedToDismissKeyguard;
+ private Set<Callback> mCallbacks = new HashSet<>();
@VisibleForTesting
public static final AudioAttributes VIBRATION_SONIFICATION_ATTRIBUTES =
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ // vibration will bypass battery saver mode:
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
.build();
public static final VibrationEffect EFFECT_CLICK =
@@ -238,7 +242,7 @@ public class UdfpsController implements DozeReceiver {
final UdfpsEnrollHelper enrollHelper;
if (reason == IUdfpsOverlayController.REASON_ENROLL_FIND_SENSOR
|| reason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING) {
- enrollHelper = new UdfpsEnrollHelper(mContext, reason);
+ enrollHelper = new UdfpsEnrollHelper(mContext, mFingerprintManager, reason);
} else {
enrollHelper = null;
}
@@ -449,19 +453,19 @@ public class UdfpsController implements DozeReceiver {
final String touchInfo = String.format(
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
- final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime;
+ final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
if (!isIlluminationRequested && !mGoodCaptureReceived &&
!exceedsVelocityThreshold) {
onFingerDown((int) event.getRawX(), (int) event.getRawY(), minor,
major);
Log.v(TAG, "onTouch | finger down: " + touchInfo);
- mTouchLogTime = SystemClock.elapsedRealtime();
- mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ mTouchLogTime = mSystemClock.elapsedRealtime();
+ mPowerManager.userActivity(mSystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
- mTouchLogTime = SystemClock.elapsedRealtime();
+ mTouchLogTime = mSystemClock.elapsedRealtime();
}
} else {
Log.v(TAG, "onTouch | finger outside");
@@ -512,7 +516,6 @@ public class UdfpsController implements DozeReceiver {
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull DumpManager dumpManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull FalsingManager falsingManager,
@NonNull PowerManager powerManager,
@NonNull AccessibilityManager accessibilityManager,
@@ -525,11 +528,12 @@ public class UdfpsController implements DozeReceiver {
@NonNull KeyguardBypassController keyguardBypassController,
@NonNull DisplayManager displayManager,
@Main Handler mainHandler,
- @NonNull ConfigurationController configurationController) {
+ @NonNull ConfigurationController configurationController,
+ @NonNull SystemClock systemClock,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mExecution = execution;
// TODO (b/185124905): inject main handler and vibrator once done prototyping
- mMainHandler = new Handler(Looper.getMainLooper());
mVibrator = vibrator;
mInflater = inflater;
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
@@ -543,7 +547,6 @@ public class UdfpsController implements DozeReceiver {
mKeyguardViewManager = statusBarKeyguardViewManager;
mDumpManager = dumpManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mKeyguardViewMediator = keyguardViewMediator;
mFalsingManager = falsingManager;
mPowerManager = powerManager;
mAccessibilityManager = accessibilityManager;
@@ -561,6 +564,8 @@ public class UdfpsController implements DozeReceiver {
mainHandler);
mKeyguardBypassController = keyguardBypassController;
mConfigurationController = configurationController;
+ mSystemClock = systemClock;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
@@ -650,6 +655,20 @@ public class UdfpsController implements DozeReceiver {
}
}
+ private boolean shouldRotate(@Nullable UdfpsAnimationViewController animation) {
+ if (!(animation instanceof UdfpsKeyguardViewController)) {
+ // always rotate view if we're not on the keyguard
+ return true;
+ }
+
+ // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded
+ if (mKeyguardUpdateMonitor.isGoingToSleep() || !mKeyguardStateController.isOccluded()) {
+ return false;
+ }
+
+ return true;
+ }
+
private WindowManager.LayoutParams computeLayoutParams(
@Nullable UdfpsAnimationViewController animation) {
final int paddingX = animation != null ? animation.getPaddingX() : 0;
@@ -673,9 +692,11 @@ public class UdfpsController implements DozeReceiver {
// Transform dimensions if the device is in landscape mode
switch (mContext.getDisplay().getRotation()) {
case Surface.ROTATION_90:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_90");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_90");
}
mCoreLayoutParams.x = mSensorProps.sensorLocationY - mSensorProps.sensorRadius
- paddingX;
@@ -684,9 +705,11 @@ public class UdfpsController implements DozeReceiver {
break;
case Surface.ROTATION_270:
- if (animation instanceof UdfpsKeyguardViewController
- && mKeyguardUpdateMonitor.isGoingToSleep()) {
+ if (!shouldRotate(animation)) {
+ Log.v(TAG, "skip rotating udfps location ROTATION_270");
break;
+ } else {
+ Log.v(TAG, "rotate udfps location ROTATION_270");
}
mCoreLayoutParams.x = p.x - mSensorProps.sensorLocationY - mSensorProps.sensorRadius
- paddingX;
@@ -703,15 +726,21 @@ public class UdfpsController implements DozeReceiver {
return mCoreLayoutParams;
}
+
private void onOrientationChanged() {
// When the configuration changes it's almost always necessary to destroy and re-create
// the overlay's window to pass it the new LayoutParams.
// Hiding the overlay will destroy its window. It's safe to hide the overlay regardless
// of whether it is already hidden.
+ final boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
hideUdfpsOverlay();
+
// If the overlay needs to be shown, this will re-create and show the overlay with the
// updated LayoutParams. Otherwise, the overlay will remain hidden.
updateOverlay();
+ if (wasShowingAltAuth) {
+ mKeyguardViewManager.showGenericBouncer(true);
+ }
}
private void showUdfpsOverlay(@NonNull ServerRequest request) {
@@ -759,6 +788,7 @@ public class UdfpsController implements DozeReceiver {
UdfpsEnrollView enrollView = (UdfpsEnrollView) mInflater.inflate(
R.layout.udfps_enroll_view, null);
mView.addView(enrollView);
+ enrollView.updateSensorLocation(mSensorProps);
return new UdfpsEnrollViewController(
enrollView,
mServerRequest.mEnrollHelper,
@@ -776,11 +806,12 @@ public class UdfpsController implements DozeReceiver {
mStatusBar,
mKeyguardViewManager,
mKeyguardUpdateMonitor,
- mFgExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
this
);
case IUdfpsOverlayController.REASON_AUTH_BP:
@@ -816,10 +847,14 @@ public class UdfpsController implements DozeReceiver {
Log.v(TAG, "hideUdfpsOverlay | removing window");
// Reset the controller back to its starting state.
onFingerUp();
+ boolean wasShowingAltAuth = mKeyguardViewManager.isShowingAlternateAuth();
mWindowManager.removeView(mView);
mView.setOnTouchListener(null);
mView.setOnHoverListener(null);
mView.setAnimationViewController(null);
+ if (wasShowingAltAuth) {
+ mKeyguardViewManager.resetAlternateAuth(true);
+ }
mAccessibilityManager.removeTouchExplorationStateChangeListener(
mTouchExplorationStateChangeListener);
mView = null;
@@ -841,6 +876,10 @@ public class UdfpsController implements DozeReceiver {
return;
}
+ if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ return;
+ }
+
mAodInterruptRunnable = () -> {
mIsAodInterruptActive = true;
// Since the sensor that triggers the AOD interrupt doesn't provide
@@ -860,6 +899,20 @@ public class UdfpsController implements DozeReceiver {
}
/**
+ * Add a callback for fingerUp and fingerDown events
+ */
+ public void addCallback(Callback cb) {
+ mCallbacks.add(cb);
+ }
+
+ /**
+ * Remove callback
+ */
+ public void removeCallback(Callback cb) {
+ mCallbacks.remove(cb);
+ }
+
+ /**
* Cancel updfs scan affordances - ability to hide the HbmSurfaceView (white circle) before
* user explicitly lifts their finger. Generally, this should be called whenever udfps fails
* or errors.
@@ -913,6 +966,10 @@ public class UdfpsController implements DozeReceiver {
mFingerprintManager.onUiReady(mSensorProps.sensorId);
Trace.endAsyncSection("UdfpsController.e2e.startIllumination", 0);
});
+
+ for (Callback cb : mCallbacks) {
+ cb.onFingerDown();
+ }
}
private void onFingerUp() {
@@ -925,6 +982,9 @@ public class UdfpsController implements DozeReceiver {
}
if (mOnFingerDown) {
mFingerprintManager.onPointerUp(mSensorProps.sensorId);
+ for (Callback cb : mCallbacks) {
+ cb.onFingerUp();
+ }
}
mOnFingerDown = false;
if (mView.isIlluminationRequested()) {
@@ -945,4 +1005,19 @@ public class UdfpsController implements DozeReceiver {
mView.setOnTouchListener(mOnTouchListener);
}
}
+
+ /**
+ * Callback for fingerUp and fingerDown events.
+ */
+ public interface Callback {
+ /**
+ * Called onFingerUp events. Will only be called if the finger was previously down.
+ */
+ void onFingerUp();
+
+ /**
+ * Called onFingerDown events.
+ */
+ void onFingerDown();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
index ea69b1d626ae..2034ff35be70 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollDrawable.java
@@ -16,9 +16,11 @@
package com.android.systemui.biometrics;
+import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -26,11 +28,17 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.TypedValue;
import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
/**
@@ -39,11 +47,20 @@ import com.android.systemui.R;
public class UdfpsEnrollDrawable extends UdfpsDrawable {
private static final String TAG = "UdfpsAnimationEnroll";
- private static final long ANIM_DURATION = 800;
+ private static final long HINT_COLOR_ANIM_DELAY_MS = 233L;
+ private static final long HINT_COLOR_ANIM_DURATION_MS = 517L;
+ private static final long HINT_WIDTH_ANIM_DURATION_MS = 233L;
+ private static final long TARGET_ANIM_DURATION_LONG = 800L;
+ private static final long TARGET_ANIM_DURATION_SHORT = 600L;
// 1 + SCALE_MAX is the maximum that the moving target will animate to
private static final float SCALE_MAX = 0.25f;
- @NonNull private final UdfpsEnrollProgressBarDrawable mProgressDrawable;
+ private static final float HINT_PADDING_DP = 10f;
+ private static final float HINT_MAX_WIDTH_DP = 6f;
+ private static final float HINT_ANGLE = 40f;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
@NonNull private final Drawable mMovingTargetFpIcon;
@NonNull private final Paint mSensorOutlinePaint;
@NonNull private final Paint mBlueFill;
@@ -52,18 +69,41 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
@Nullable private UdfpsEnrollHelper mEnrollHelper;
// Moving target animator set
- @Nullable AnimatorSet mAnimatorSet;
+ @Nullable AnimatorSet mTargetAnimatorSet;
// Moving target location
float mCurrentX;
float mCurrentY;
// Moving target size
float mCurrentScale = 1.f;
+ @ColorInt private final int mHintColorFaded;
+ @ColorInt private final int mHintColorHighlight;
+ private final float mHintMaxWidthPx;
+ private final float mHintPaddingPx;
+
+ @NonNull private final Animator.AnimatorListener mTargetAnimListener;
+
+ private boolean mShouldShowTipHint = false;
+ @NonNull private final Paint mTipHintPaint;
+ @Nullable private AnimatorSet mTipHintAnimatorSet;
+ @Nullable private ValueAnimator mTipHintColorAnimator;
+ @Nullable private ValueAnimator mTipHintWidthAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintColorUpdateListener;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mTipHintWidthUpdateListener;
+ @NonNull private final Animator.AnimatorListener mTipHintPulseListener;
+
+ private boolean mShouldShowEdgeHint = false;
+ @NonNull private final Paint mEdgeHintPaint;
+ @Nullable private AnimatorSet mEdgeHintAnimatorSet;
+ @Nullable private ValueAnimator mEdgeHintColorAnimator;
+ @Nullable private ValueAnimator mEdgeHintWidthAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintColorUpdateListener;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mEdgeHintWidthUpdateListener;
+ @NonNull private final Animator.AnimatorListener mEdgeHintPulseListener;
+
UdfpsEnrollDrawable(@NonNull Context context) {
super(context);
- mProgressDrawable = new UdfpsEnrollProgressBarDrawable(context, this);
-
mSensorOutlinePaint = new Paint(0 /* flags */);
mSensorOutlinePaint.setAntiAlias(true);
mSensorOutlinePaint.setColor(mContext.getColor(R.color.udfps_enroll_icon));
@@ -80,6 +120,117 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
mMovingTargetFpIcon.mutate();
mFingerprintDrawable.setTint(mContext.getColor(R.color.udfps_enroll_icon));
+
+ mHintColorFaded = getHintColorFaded(context);
+ mHintColorHighlight = context.getColor(R.color.udfps_enroll_progress);
+ mHintMaxWidthPx = Utils.dpToPixels(context, HINT_MAX_WIDTH_DP);
+ mHintPaddingPx = Utils.dpToPixels(context, HINT_PADDING_DP);
+
+ mTargetAnimListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ updateTipHintVisibility();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+
+ mTipHintPaint = new Paint(0 /* flags */);
+ mTipHintPaint.setAntiAlias(true);
+ mTipHintPaint.setColor(mHintColorFaded);
+ mTipHintPaint.setStyle(Paint.Style.STROKE);
+ mTipHintPaint.setStrokeCap(Paint.Cap.ROUND);
+ mTipHintPaint.setStrokeWidth(0f);
+ mTipHintColorUpdateListener = animation -> {
+ mTipHintPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mTipHintWidthUpdateListener = animation -> {
+ mTipHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mTipHintPulseListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(() -> {
+ mTipHintColorAnimator =
+ ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorFaded);
+ mTipHintColorAnimator.setInterpolator(new LinearInterpolator());
+ mTipHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+ mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+ mTipHintColorAnimator.start();
+ }, HINT_COLOR_ANIM_DELAY_MS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+
+ mEdgeHintPaint = new Paint(0 /* flags */);
+ mEdgeHintPaint.setAntiAlias(true);
+ mEdgeHintPaint.setColor(mHintColorFaded);
+ mEdgeHintPaint.setStyle(Paint.Style.STROKE);
+ mEdgeHintPaint.setStrokeCap(Paint.Cap.ROUND);
+ mEdgeHintPaint.setStrokeWidth(0f);
+ mEdgeHintColorUpdateListener = animation -> {
+ mEdgeHintPaint.setColor((int) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mEdgeHintWidthUpdateListener = animation -> {
+ mEdgeHintPaint.setStrokeWidth((float) animation.getAnimatedValue());
+ invalidateSelf();
+ };
+ mEdgeHintPulseListener = new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {}
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mHandler.postDelayed(() -> {
+ mEdgeHintColorAnimator =
+ ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorFaded);
+ mEdgeHintColorAnimator.setInterpolator(new LinearInterpolator());
+ mEdgeHintColorAnimator.setDuration(HINT_COLOR_ANIM_DURATION_MS);
+ mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+ mEdgeHintColorAnimator.start();
+ }, HINT_COLOR_ANIM_DELAY_MS);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {}
+ };
+ }
+
+ @ColorInt
+ private static int getHintColorFaded(@NonNull Context context) {
+ final TypedValue tv = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tv, true);
+ final int alpha = (int) (tv.getFloat() * 255f);
+
+ final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+ final TypedArray ta = context.obtainStyledAttributes(attrs);
+ try {
+ @ColorInt final int color = ta.getColor(0, context.getColor(R.color.white_disabled));
+ return ColorUtils.setAlphaComponent(color, alpha);
+ } finally {
+ ta.recycle();
+ }
}
void setEnrollHelper(@NonNull UdfpsEnrollHelper helper) {
@@ -100,59 +251,164 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
}
void onEnrollmentProgress(int remaining, int totalSteps) {
- mProgressDrawable.setEnrollmentProgress(remaining, totalSteps);
+ if (mEnrollHelper == null) {
+ return;
+ }
- if (mEnrollHelper.isCenterEnrollmentComplete()) {
- if (mAnimatorSet != null && mAnimatorSet.isRunning()) {
- mAnimatorSet.end();
+ if (!mEnrollHelper.isCenterEnrollmentStage()) {
+ if (mTargetAnimatorSet != null && mTargetAnimatorSet.isRunning()) {
+ mTargetAnimatorSet.end();
}
final PointF point = mEnrollHelper.getNextGuidedEnrollmentPoint();
+ if (mCurrentX != point.x || mCurrentY != point.y) {
+ final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
+ x.addUpdateListener(animation -> {
+ mCurrentX = (float) animation.getAnimatedValue();
+ invalidateSelf();
+ });
+
+ final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
+ y.addUpdateListener(animation -> {
+ mCurrentY = (float) animation.getAnimatedValue();
+ invalidateSelf();
+ });
+
+ final boolean isMovingToCenter = point.x == 0f && point.y == 0f;
+ final long duration = isMovingToCenter
+ ? TARGET_ANIM_DURATION_SHORT
+ : TARGET_ANIM_DURATION_LONG;
+
+ final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
+ scale.setDuration(duration);
+ scale.addUpdateListener(animation -> {
+ // Grow then shrink
+ mCurrentScale = 1
+ + SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
+ invalidateSelf();
+ });
+
+ mTargetAnimatorSet = new AnimatorSet();
+
+ mTargetAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mTargetAnimatorSet.setDuration(duration);
+ mTargetAnimatorSet.addListener(mTargetAnimListener);
+ mTargetAnimatorSet.playTogether(x, y, scale);
+ mTargetAnimatorSet.start();
+ } else {
+ updateTipHintVisibility();
+ }
+ } else {
+ updateTipHintVisibility();
+ }
+
+ updateEdgeHintVisibility();
+ }
+
+ private void updateTipHintVisibility() {
+ final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isTipEnrollmentStage();
+ if (mShouldShowTipHint == shouldShow) {
+ return;
+ }
+ mShouldShowTipHint = shouldShow;
+
+ if (mTipHintWidthAnimator != null && mTipHintWidthAnimator.isRunning()) {
+ mTipHintWidthAnimator.cancel();
+ }
+
+ final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+ mTipHintWidthAnimator = ValueAnimator.ofFloat(mTipHintPaint.getStrokeWidth(), targetWidth);
+ mTipHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mTipHintWidthAnimator.addUpdateListener(mTipHintWidthUpdateListener);
+
+ if (shouldShow) {
+ startTipHintPulseAnimation();
+ } else {
+ mTipHintWidthAnimator.start();
+ }
+ }
- final ValueAnimator x = ValueAnimator.ofFloat(mCurrentX, point.x);
- x.addUpdateListener(animation -> {
- mCurrentX = (float) animation.getAnimatedValue();
- invalidateSelf();
- });
-
- final ValueAnimator y = ValueAnimator.ofFloat(mCurrentY, point.y);
- y.addUpdateListener(animation -> {
- mCurrentY = (float) animation.getAnimatedValue();
- invalidateSelf();
- });
-
- final ValueAnimator scale = ValueAnimator.ofFloat(0, (float) Math.PI);
- scale.setDuration(ANIM_DURATION);
- scale.addUpdateListener(animation -> {
- // Grow then shrink
- mCurrentScale = 1 +
- SCALE_MAX * (float) Math.sin((float) animation.getAnimatedValue());
- invalidateSelf();
- });
-
- mAnimatorSet = new AnimatorSet();
-
- mAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
- mAnimatorSet.setDuration(ANIM_DURATION);
- mAnimatorSet.playTogether(x, y, scale);
- mAnimatorSet.start();
+ private void updateEdgeHintVisibility() {
+ final boolean shouldShow = mEnrollHelper != null && mEnrollHelper.isEdgeEnrollmentStage();
+ if (mShouldShowEdgeHint == shouldShow) {
+ return;
+ }
+ mShouldShowEdgeHint = shouldShow;
+
+ if (mEdgeHintWidthAnimator != null && mEdgeHintWidthAnimator.isRunning()) {
+ mEdgeHintWidthAnimator.cancel();
+ }
+
+ final float targetWidth = shouldShow ? mHintMaxWidthPx : 0f;
+ mEdgeHintWidthAnimator =
+ ValueAnimator.ofFloat(mEdgeHintPaint.getStrokeWidth(), targetWidth);
+ mEdgeHintWidthAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mEdgeHintWidthAnimator.addUpdateListener(mEdgeHintWidthUpdateListener);
+
+ if (shouldShow) {
+ startEdgeHintPulseAnimation();
+ } else {
+ mEdgeHintWidthAnimator.start();
}
}
- void onLastStepAcquired() {
- mProgressDrawable.onLastStepAcquired();
+ private void startTipHintPulseAnimation() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mTipHintAnimatorSet != null && mTipHintAnimatorSet.isRunning()) {
+ mTipHintAnimatorSet.cancel();
+ }
+ if (mTipHintColorAnimator != null && mTipHintColorAnimator.isRunning()) {
+ mTipHintColorAnimator.cancel();
+ }
+
+ mTipHintColorAnimator = ValueAnimator.ofArgb(mTipHintPaint.getColor(), mHintColorHighlight);
+ mTipHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mTipHintColorAnimator.addUpdateListener(mTipHintColorUpdateListener);
+ mTipHintColorAnimator.addListener(mTipHintPulseListener);
+
+ mTipHintAnimatorSet = new AnimatorSet();
+ mTipHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mTipHintAnimatorSet.playTogether(mTipHintColorAnimator, mTipHintWidthAnimator);
+ mTipHintAnimatorSet.start();
+ }
+
+ private void startEdgeHintPulseAnimation() {
+ mHandler.removeCallbacksAndMessages(null);
+ if (mEdgeHintAnimatorSet != null && mEdgeHintAnimatorSet.isRunning()) {
+ mEdgeHintAnimatorSet.cancel();
+ }
+ if (mEdgeHintColorAnimator != null && mEdgeHintColorAnimator.isRunning()) {
+ mEdgeHintColorAnimator.cancel();
+ }
+
+ mEdgeHintColorAnimator =
+ ValueAnimator.ofArgb(mEdgeHintPaint.getColor(), mHintColorHighlight);
+ mEdgeHintColorAnimator.setDuration(HINT_WIDTH_ANIM_DURATION_MS);
+ mEdgeHintColorAnimator.addUpdateListener(mEdgeHintColorUpdateListener);
+ mEdgeHintColorAnimator.addListener(mEdgeHintPulseListener);
+
+ mEdgeHintAnimatorSet = new AnimatorSet();
+ mEdgeHintAnimatorSet.setInterpolator(new AccelerateDecelerateInterpolator());
+ mEdgeHintAnimatorSet.playTogether(mEdgeHintColorAnimator, mEdgeHintWidthAnimator);
+ mEdgeHintAnimatorSet.start();
+ }
+
+ private boolean isTipHintVisible() {
+ return mTipHintPaint.getStrokeWidth() > 0f;
+ }
+
+ private boolean isEdgeHintVisible() {
+ return mEdgeHintPaint.getStrokeWidth() > 0f;
}
@Override
public void draw(@NonNull Canvas canvas) {
- mProgressDrawable.draw(canvas);
-
if (isIlluminationShowing()) {
return;
}
// Draw moving target
- if (mEnrollHelper.isCenterEnrollmentComplete()) {
+ if (mEnrollHelper != null && !mEnrollHelper.isCenterEnrollmentStage()) {
canvas.save();
canvas.translate(mCurrentX, mCurrentY);
@@ -172,11 +428,59 @@ public class UdfpsEnrollDrawable extends UdfpsDrawable {
mFingerprintDrawable.setAlpha(mAlpha);
mSensorOutlinePaint.setAlpha(mAlpha);
}
- }
- @Override
- public void onBoundsChange(@NonNull Rect rect) {
- mProgressDrawable.setBounds(rect);
+ // Draw the finger tip or edges hint.
+ if (isTipHintVisible() || isEdgeHintVisible()) {
+ canvas.save();
+
+ // Make arcs start from the top, rather than the right.
+ canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+
+ final float halfSensorHeight = Math.abs(mSensorRect.bottom - mSensorRect.top) / 2f;
+ final float halfSensorWidth = Math.abs(mSensorRect.right - mSensorRect.left) / 2f;
+ final float hintXOffset = halfSensorWidth + mHintPaddingPx;
+ final float hintYOffset = halfSensorHeight + mHintPaddingPx;
+
+ if (isTipHintVisible()) {
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mTipHintPaint);
+ }
+
+ if (isEdgeHintVisible()) {
+ // Draw right edge hint.
+ canvas.rotate(-90f, mSensorRect.centerX(), mSensorRect.centerY());
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mEdgeHintPaint);
+
+ // Draw left edge hint.
+ canvas.rotate(180f, mSensorRect.centerX(), mSensorRect.centerY());
+ canvas.drawArc(
+ mSensorRect.centerX() - hintXOffset,
+ mSensorRect.centerY() - hintYOffset,
+ mSensorRect.centerX() + hintXOffset,
+ mSensorRect.centerY() + hintYOffset,
+ -HINT_ANGLE / 2f,
+ HINT_ANGLE,
+ false /* useCenter */,
+ mEdgeHintPaint);
+ }
+
+ canvas.restore();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
index 6a918a6c8d39..caec1d517761 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollHelper.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Build;
import android.os.UserHandle;
@@ -44,15 +45,14 @@ public class UdfpsEnrollHelper {
private static final String NEW_COORDS_OVERRIDE =
"com.android.systemui.biometrics.UdfpsNewCoords";
- // Enroll with two center touches before going to guided enrollment
- private static final int NUM_CENTER_TOUCHES = 2;
-
interface Listener {
void onEnrollmentProgress(int remaining, int totalSteps);
+ void onEnrollmentHelp(int remaining, int totalSteps);
void onLastStepAcquired();
}
@NonNull private final Context mContext;
+ @NonNull private final FingerprintManager mFingerprintManager;
// IUdfpsOverlayController reason
private final int mEnrollReason;
private final boolean mAccessibilityEnabled;
@@ -65,10 +65,15 @@ public class UdfpsEnrollHelper {
// interface makes no promises about monotonically increasing by one each time.
private int mLocationsEnrolled = 0;
+ private int mCenterTouchCount = 0;
+
@Nullable Listener mListener;
- public UdfpsEnrollHelper(@NonNull Context context, int reason) {
+ public UdfpsEnrollHelper(@NonNull Context context,
+ @NonNull FingerprintManager fingerprintManager, int reason) {
+
mContext = context;
+ mFingerprintManager = fingerprintManager;
mEnrollReason = reason;
final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
@@ -117,6 +122,14 @@ public class UdfpsEnrollHelper {
}
}
+ int getStageCount() {
+ return mFingerprintManager.getEnrollStageCount();
+ }
+
+ int getStageThresholdSteps(int totalSteps, int stageIndex) {
+ return Math.round(totalSteps * mFingerprintManager.getEnrollStageThreshold(stageIndex));
+ }
+
boolean shouldShowProgressBar() {
return mEnrollReason == IUdfpsOverlayController.REASON_ENROLL_ENROLLING;
}
@@ -128,6 +141,9 @@ public class UdfpsEnrollHelper {
if (remaining != mRemainingSteps) {
mLocationsEnrolled++;
+ if (isCenterEnrollmentStage()) {
+ mCenterTouchCount++;
+ }
}
mRemainingSteps = remaining;
@@ -138,7 +154,9 @@ public class UdfpsEnrollHelper {
}
void onEnrollmentHelp() {
-
+ if (mListener != null) {
+ mListener.onEnrollmentHelp(mRemainingSteps, mTotalSteps);
+ }
}
void setListener(Listener listener) {
@@ -152,19 +170,41 @@ public class UdfpsEnrollHelper {
}
}
- boolean isCenterEnrollmentComplete() {
+ boolean isCenterEnrollmentStage() {
+ if (mTotalSteps == -1 || mRemainingSteps == -1) {
+ return true;
+ }
+ return mTotalSteps - mRemainingSteps < getStageThresholdSteps(mTotalSteps, 0);
+ }
+
+ boolean isGuidedEnrollmentStage() {
+ if (mAccessibilityEnabled || mTotalSteps == -1 || mRemainingSteps == -1) {
+ return false;
+ }
+ final int progressSteps = mTotalSteps - mRemainingSteps;
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 0)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 1);
+ }
+
+ boolean isTipEnrollmentStage() {
if (mTotalSteps == -1 || mRemainingSteps == -1) {
return false;
- } else if (mAccessibilityEnabled) {
+ }
+ final int progressSteps = mTotalSteps - mRemainingSteps;
+ return progressSteps >= getStageThresholdSteps(mTotalSteps, 1)
+ && progressSteps < getStageThresholdSteps(mTotalSteps, 2);
+ }
+
+ boolean isEdgeEnrollmentStage() {
+ if (mTotalSteps == -1 || mRemainingSteps == -1) {
return false;
}
- final int stepsEnrolled = mTotalSteps - mRemainingSteps;
- return stepsEnrolled >= NUM_CENTER_TOUCHES;
+ return mTotalSteps - mRemainingSteps >= getStageThresholdSteps(mTotalSteps, 2);
}
@NonNull
PointF getNextGuidedEnrollmentPoint() {
- if (mAccessibilityEnabled) {
+ if (mAccessibilityEnabled || !isGuidedEnrollmentStage()) {
return new PointF(0f, 0f);
}
@@ -174,7 +214,7 @@ public class UdfpsEnrollHelper {
SCALE_OVERRIDE, SCALE,
UserHandle.USER_CURRENT);
}
- final int index = mLocationsEnrolled - NUM_CENTER_TOUCHES;
+ final int index = mLocationsEnrolled - mCenterTouchCount;
final PointF originalPoint = mGuidedEnrollmentPoints
.get(index % mGuidedEnrollmentPoints.size());
return new PointF(originalPoint.x * scale, originalPoint.y * scale);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
index 4195009937c2..b2a54097539d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarDrawable.java
@@ -16,116 +16,129 @@
package com.android.systemui.biometrics;
-import android.animation.ValueAnimator;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
-import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.Log;
-import android.util.TypedValue;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.systemui.R;
+import java.util.ArrayList;
+import java.util.List;
/**
* UDFPS enrollment progress bar.
*/
public class UdfpsEnrollProgressBarDrawable extends Drawable {
+ private static final String TAG = "UdfpsProgressBar";
- private static final String TAG = "UdfpsEnrollProgressBarDrawable";
-
- private static final float PROGRESS_BAR_THICKNESS_DP = 12;
+ private static final float SEGMENT_GAP_ANGLE = 12f;
@NonNull private final Context mContext;
- @NonNull private final UdfpsEnrollDrawable mParent;
- @NonNull private final Paint mBackgroundCirclePaint;
- @NonNull private final Paint mProgressPaint;
- @Nullable private ValueAnimator mProgressAnimator;
- private float mProgress;
- private int mRotation; // After last step, rotate the progress bar once
- private boolean mLastStepAcquired;
+ @Nullable private UdfpsEnrollHelper mEnrollHelper;
+ @NonNull private List<UdfpsEnrollProgressBarSegment> mSegments = new ArrayList<>();
+ private int mTotalSteps = 1;
+ private int mProgressSteps = 0;
+ private boolean mIsShowingHelp = false;
- public UdfpsEnrollProgressBarDrawable(@NonNull Context context,
- @NonNull UdfpsEnrollDrawable parent) {
+ public UdfpsEnrollProgressBarDrawable(@NonNull Context context) {
mContext = context;
- mParent = parent;
-
- mBackgroundCirclePaint = new Paint();
- mBackgroundCirclePaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
- mBackgroundCirclePaint.setColor(context.getColor(R.color.white_disabled));
- mBackgroundCirclePaint.setAntiAlias(true);
- mBackgroundCirclePaint.setStyle(Paint.Style.STROKE);
-
- // Background circle color + alpha
- TypedArray tc = context.obtainStyledAttributes(
- new int[] {android.R.attr.colorControlNormal});
- int tintColor = tc.getColor(0, mBackgroundCirclePaint.getColor());
- mBackgroundCirclePaint.setColor(tintColor);
- tc.recycle();
- TypedValue alpha = new TypedValue();
- context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
- mBackgroundCirclePaint.setAlpha((int) (alpha.getFloat() * 255));
-
- // Progress should not be color extracted
- mProgressPaint = new Paint();
- mProgressPaint.setStrokeWidth(Utils.dpToPixels(context, PROGRESS_BAR_THICKNESS_DP));
- mProgressPaint.setColor(context.getColor(R.color.udfps_enroll_progress));
- mProgressPaint.setAntiAlias(true);
- mProgressPaint.setStyle(Paint.Style.STROKE);
- mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
}
- void setEnrollmentProgress(int remaining, int totalSteps) {
- // Add one so that the first steps actually changes progress, but also so that the last
- // step ends at 1.0
- final float progress = (totalSteps - remaining + 1) / (float) (totalSteps + 1);
- setEnrollmentProgress(progress);
+ void setEnrollHelper(@Nullable UdfpsEnrollHelper enrollHelper) {
+ mEnrollHelper = enrollHelper;
+ if (enrollHelper != null) {
+ final int stageCount = enrollHelper.getStageCount();
+ mSegments = new ArrayList<>(stageCount);
+ float startAngle = SEGMENT_GAP_ANGLE / 2f;
+ final float sweepAngle = (360f / stageCount) - SEGMENT_GAP_ANGLE;
+ final Runnable invalidateRunnable = this::invalidateSelf;
+ for (int index = 0; index < stageCount; index++) {
+ mSegments.add(new UdfpsEnrollProgressBarSegment(mContext, getBounds(), startAngle,
+ sweepAngle, SEGMENT_GAP_ANGLE, invalidateRunnable));
+ startAngle += sweepAngle + SEGMENT_GAP_ANGLE;
+ }
+ invalidateSelf();
+ }
+ }
+
+ void onEnrollmentProgress(int remaining, int totalSteps) {
+ mTotalSteps = totalSteps;
+ updateState(getProgressSteps(remaining, totalSteps), false /* isShowingHelp */);
+ }
+
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ updateState(getProgressSteps(remaining, totalSteps), true /* isShowingHelp */);
+ }
+
+ void onLastStepAcquired() {
+ updateState(mTotalSteps, false /* isShowingHelp */);
+ }
+
+ private static int getProgressSteps(int remaining, int totalSteps) {
+ // Show some progress for the initial touch.
+ return Math.max(1, totalSteps - remaining);
+ }
+
+ private void updateState(int progressSteps, boolean isShowingHelp) {
+ updateProgress(progressSteps);
+ updateFillColor(isShowingHelp);
}
- private void setEnrollmentProgress(float progress) {
- if (mLastStepAcquired) {
+ private void updateProgress(int progressSteps) {
+ if (mProgressSteps == progressSteps) {
return;
}
+ mProgressSteps = progressSteps;
- long animationDuration = 150;
-
- if (progress == 1.f) {
- animationDuration = 400;
- final ValueAnimator rotationAnimator = ValueAnimator.ofInt(0, 400);
- rotationAnimator.setDuration(animationDuration);
- rotationAnimator.addUpdateListener(animation -> {
- Log.d(TAG, "Rotation: " + mRotation);
- mRotation = (int) animation.getAnimatedValue();
- mParent.invalidateSelf();
- });
- rotationAnimator.start();
+ if (mEnrollHelper == null) {
+ Log.e(TAG, "updateState: UDFPS enroll helper was null");
+ return;
}
- if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
- mProgressAnimator.cancel();
+ int index = 0;
+ int prevThreshold = 0;
+ while (index < mSegments.size()) {
+ final UdfpsEnrollProgressBarSegment segment = mSegments.get(index);
+ final int thresholdSteps = mEnrollHelper.getStageThresholdSteps(mTotalSteps, index);
+ if (progressSteps >= thresholdSteps && segment.getProgress() < 1f) {
+ segment.updateProgress(1f);
+ break;
+ } else if (progressSteps >= prevThreshold && progressSteps < thresholdSteps) {
+ final int relativeSteps = progressSteps - prevThreshold;
+ final int relativeThreshold = thresholdSteps - prevThreshold;
+ final float segmentProgress = (float) relativeSteps / (float) relativeThreshold;
+ segment.updateProgress(segmentProgress);
+ break;
+ }
+
+ index++;
+ prevThreshold = thresholdSteps;
}
- mProgressAnimator = ValueAnimator.ofFloat(mProgress, progress);
- mProgressAnimator.setDuration(animationDuration);
- mProgressAnimator.addUpdateListener(animation -> {
- mProgress = (float) animation.getAnimatedValue();
- // Use the parent to invalidate, since it's the one that's attached as the view's
- // drawable and has its callback set automatically. Invalidating via
- // `this.invalidateSelf` actually does not invoke draw(), since this drawable's callback
- // is not really set.
- mParent.invalidateSelf();
- });
- mProgressAnimator.start();
+ if (progressSteps >= mTotalSteps) {
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.startCompletionAnimation();
+ }
+ } else {
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.cancelCompletionAnimation();
+ }
+ }
}
- void onLastStepAcquired() {
- setEnrollmentProgress(1.f);
- mLastStepAcquired = true;
+ private void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.updateFillColor(isShowingHelp);
+ }
}
@Override
@@ -133,43 +146,22 @@ public class UdfpsEnrollProgressBarDrawable extends Drawable {
canvas.save();
// Progress starts from the top, instead of the right
- canvas.rotate(-90 + mRotation, getBounds().centerX(), getBounds().centerY());
-
- // Progress bar "background track"
- final float halfPaddingPx = Utils.dpToPixels(mContext, PROGRESS_BAR_THICKNESS_DP) / 2;
- canvas.drawArc(halfPaddingPx,
- halfPaddingPx,
- getBounds().right - halfPaddingPx,
- getBounds().bottom - halfPaddingPx,
- 0,
- 360,
- false,
- mBackgroundCirclePaint
- );
-
- final float progress = 360.f * mProgress;
- // Progress
- canvas.drawArc(halfPaddingPx,
- halfPaddingPx,
- getBounds().right - halfPaddingPx,
- getBounds().bottom - halfPaddingPx,
- 0,
- progress,
- false,
- mProgressPaint
- );
+ canvas.rotate(-90f, getBounds().centerX(), getBounds().centerY());
+
+ // Draw each of the enroll segments.
+ for (final UdfpsEnrollProgressBarSegment segment : mSegments) {
+ segment.draw(canvas);
+ }
canvas.restore();
}
@Override
public void setAlpha(int alpha) {
-
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
new file mode 100644
index 000000000000..bd6ab4443630
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollProgressBarSegment.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.util.TypedValue;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.R;
+
+/**
+ * A single segment of the UDFPS enrollment progress bar.
+ */
+public class UdfpsEnrollProgressBarSegment {
+ private static final String TAG = "UdfpsProgressBarSegment";
+
+ private static final long FILL_COLOR_ANIMATION_DURATION_MS = 200L;
+ private static final long PROGRESS_ANIMATION_DURATION_MS = 400L;
+ private static final long OVER_SWEEP_ANIMATION_DELAY_MS = 200L;
+ private static final long OVER_SWEEP_ANIMATION_DURATION_MS = 200L;
+
+ private static final float STROKE_WIDTH_DP = 12f;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ @NonNull private final Rect mBounds;
+ @NonNull private final Runnable mInvalidateRunnable;
+ private final float mStartAngle;
+ private final float mSweepAngle;
+ private final float mMaxOverSweepAngle;
+ private final float mStrokeWidthPx;
+ @ColorInt private final int mProgressColor;
+ @ColorInt private final int mHelpColor;
+
+ @NonNull private final Paint mBackgroundPaint;
+ @NonNull private final Paint mProgressPaint;
+
+ private float mProgress = 0f;
+ private float mAnimatedProgress = 0f;
+ @Nullable private ValueAnimator mProgressAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mProgressUpdateListener;
+
+ private boolean mIsShowingHelp = false;
+ @Nullable private ValueAnimator mFillColorAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mFillColorUpdateListener;
+
+ private float mOverSweepAngle = 0f;
+ @Nullable private ValueAnimator mOverSweepAnimator;
+ @Nullable private ValueAnimator mOverSweepReverseAnimator;
+ @NonNull private final ValueAnimator.AnimatorUpdateListener mOverSweepUpdateListener;
+ @NonNull private final Runnable mOverSweepAnimationRunnable;
+
+ public UdfpsEnrollProgressBarSegment(@NonNull Context context, @NonNull Rect bounds,
+ float startAngle, float sweepAngle, float maxOverSweepAngle,
+ @NonNull Runnable invalidateRunnable) {
+
+ mBounds = bounds;
+ mInvalidateRunnable = invalidateRunnable;
+ mStartAngle = startAngle;
+ mSweepAngle = sweepAngle;
+ mMaxOverSweepAngle = maxOverSweepAngle;
+ mStrokeWidthPx = Utils.dpToPixels(context, STROKE_WIDTH_DP);
+ mProgressColor = context.getColor(R.color.udfps_enroll_progress);
+ mHelpColor = context.getColor(R.color.udfps_enroll_progress_help);
+
+ mBackgroundPaint = new Paint();
+ mBackgroundPaint.setStrokeWidth(mStrokeWidthPx);
+ mBackgroundPaint.setColor(context.getColor(R.color.white_disabled));
+ mBackgroundPaint.setAntiAlias(true);
+ mBackgroundPaint.setStyle(Paint.Style.STROKE);
+ mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ // Background paint color + alpha
+ final int[] attrs = new int[] {android.R.attr.colorControlNormal};
+ final TypedArray ta = context.obtainStyledAttributes(attrs);
+ @ColorInt final int tintColor = ta.getColor(0, mBackgroundPaint.getColor());
+ mBackgroundPaint.setColor(tintColor);
+ ta.recycle();
+ TypedValue alpha = new TypedValue();
+ context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, alpha, true);
+ mBackgroundPaint.setAlpha((int) (alpha.getFloat() * 255f));
+
+ // Progress should not be color extracted
+ mProgressPaint = new Paint();
+ mProgressPaint.setStrokeWidth(mStrokeWidthPx);
+ mProgressPaint.setColor(mProgressColor);
+ mProgressPaint.setAntiAlias(true);
+ mProgressPaint.setStyle(Paint.Style.STROKE);
+ mProgressPaint.setStrokeCap(Paint.Cap.ROUND);
+
+ mProgressUpdateListener = animation -> {
+ mAnimatedProgress = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+
+ mFillColorUpdateListener = animation -> {
+ mProgressPaint.setColor((int) animation.getAnimatedValue());
+ mInvalidateRunnable.run();
+ };
+
+ mOverSweepUpdateListener = animation -> {
+ mOverSweepAngle = (float) animation.getAnimatedValue();
+ mInvalidateRunnable.run();
+ };
+ mOverSweepAnimationRunnable = () -> {
+ if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+ mOverSweepAnimator.cancel();
+ }
+ mOverSweepAnimator = ValueAnimator.ofFloat(mOverSweepAngle, mMaxOverSweepAngle);
+ mOverSweepAnimator.setDuration(OVER_SWEEP_ANIMATION_DURATION_MS);
+ mOverSweepAnimator.addUpdateListener(mOverSweepUpdateListener);
+ mOverSweepAnimator.start();
+ };
+ }
+
+ /**
+ * Draws this segment to the given canvas.
+ */
+ public void draw(@NonNull Canvas canvas) {
+ final float halfPaddingPx = mStrokeWidthPx / 2f;
+
+ if (mAnimatedProgress < 1f) {
+ // Draw the unfilled background color of the segment.
+ canvas.drawArc(
+ halfPaddingPx,
+ halfPaddingPx,
+ mBounds.right - halfPaddingPx,
+ mBounds.bottom - halfPaddingPx,
+ mStartAngle,
+ mSweepAngle,
+ false /* useCenter */,
+ mBackgroundPaint);
+ }
+
+ if (mAnimatedProgress > 0f) {
+ // Draw the filled progress portion of the segment.
+ canvas.drawArc(
+ halfPaddingPx,
+ halfPaddingPx,
+ mBounds.right - halfPaddingPx,
+ mBounds.bottom - halfPaddingPx,
+ mStartAngle,
+ mSweepAngle * mAnimatedProgress + mOverSweepAngle,
+ false /* useCenter */,
+ mProgressPaint);
+ }
+ }
+
+ /**
+ * @return The fill progress of this segment, in the range [0, 1]. If fill progress is being
+ * animated, returns the value it is animating to.
+ */
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Updates the fill progress of this segment, animating if necessary.
+ *
+ * @param progress The new fill progress, in the range [0, 1].
+ */
+ public void updateProgress(float progress) {
+ updateProgress(progress, PROGRESS_ANIMATION_DURATION_MS);
+ }
+
+ private void updateProgress(float progress, long animationDurationMs) {
+ if (mProgress == progress) {
+ return;
+ }
+ mProgress = progress;
+
+ if (mProgressAnimator != null && mProgressAnimator.isRunning()) {
+ mProgressAnimator.cancel();
+ }
+
+ mProgressAnimator = ValueAnimator.ofFloat(mAnimatedProgress, progress);
+ mProgressAnimator.setDuration(animationDurationMs);
+ mProgressAnimator.addUpdateListener(mProgressUpdateListener);
+ mProgressAnimator.start();
+ }
+
+ /**
+ * Updates the fill color of this segment, animating if necessary.
+ *
+ * @param isShowingHelp Whether fill color should indicate that a help message is being shown.
+ */
+ public void updateFillColor(boolean isShowingHelp) {
+ if (mIsShowingHelp == isShowingHelp) {
+ return;
+ }
+ mIsShowingHelp = isShowingHelp;
+
+ if (mFillColorAnimator != null && mFillColorAnimator.isRunning()) {
+ mFillColorAnimator.cancel();
+ }
+
+ @ColorInt final int targetColor = isShowingHelp ? mHelpColor : mProgressColor;
+ mFillColorAnimator = ValueAnimator.ofArgb(mProgressPaint.getColor(), targetColor);
+ mFillColorAnimator.setDuration(FILL_COLOR_ANIMATION_DURATION_MS);
+ mFillColorAnimator.addUpdateListener(mFillColorUpdateListener);
+ mFillColorAnimator.start();
+ }
+
+ /**
+ * Queues and runs the completion animation for this segment.
+ */
+ public void startCompletionAnimation() {
+ final boolean hasCallback = mHandler.hasCallbacks(mOverSweepAnimationRunnable);
+ if (hasCallback || mOverSweepAngle >= mMaxOverSweepAngle) {
+ Log.d(TAG, "startCompletionAnimation skipped: hasCallback = " + hasCallback
+ + ", mOverSweepAngle = " + mOverSweepAngle);
+ return;
+ }
+
+ // Reset sweep angle back to zero if the animation is being rolled back.
+ if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+ mOverSweepReverseAnimator.cancel();
+ mOverSweepAngle = 0f;
+ }
+
+ // Clear help color and start filling the segment if it isn't already.
+ if (mAnimatedProgress < 1f) {
+ updateProgress(1f, OVER_SWEEP_ANIMATION_DELAY_MS);
+ updateFillColor(false /* isShowingHelp */);
+ }
+
+ // Queue the animation to run after fill completes.
+ mHandler.postDelayed(mOverSweepAnimationRunnable, OVER_SWEEP_ANIMATION_DELAY_MS);
+ }
+
+ /**
+ * Cancels (and reverses, if necessary) a queued or running completion animation.
+ */
+ public void cancelCompletionAnimation() {
+ // Cancel the animation if it's queued or running.
+ mHandler.removeCallbacks(mOverSweepAnimationRunnable);
+ if (mOverSweepAnimator != null && mOverSweepAnimator.isRunning()) {
+ mOverSweepAnimator.cancel();
+ }
+
+ // Roll back the animation if it has at least partially run.
+ if (mOverSweepAngle > 0f) {
+ if (mOverSweepReverseAnimator != null && mOverSweepReverseAnimator.isRunning()) {
+ mOverSweepReverseAnimator.cancel();
+ }
+
+ final float completion = mOverSweepAngle / mMaxOverSweepAngle;
+ final long proratedDuration = (long) (OVER_SWEEP_ANIMATION_DURATION_MS * completion);
+ mOverSweepReverseAnimator = ValueAnimator.ofFloat(mOverSweepAngle, 0f);
+ mOverSweepReverseAnimator.setDuration(proratedDuration);
+ mOverSweepReverseAnimator.addUpdateListener(mOverSweepUpdateListener);
+ mOverSweepReverseAnimator.start();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
index 2cdf49d6fc3c..64b096867ec1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollView.java
@@ -17,9 +17,12 @@
package com.android.systemui.biometrics;
import android.content.Context;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull;
@@ -32,20 +35,25 @@ import com.android.systemui.R;
*/
public class UdfpsEnrollView extends UdfpsAnimationView {
@NonNull private final UdfpsEnrollDrawable mFingerprintDrawable;
+ @NonNull private final UdfpsEnrollProgressBarDrawable mFingerprintProgressDrawable;
@NonNull private final Handler mHandler;
@NonNull private ImageView mFingerprintView;
+ @NonNull private ImageView mFingerprintProgressView;
public UdfpsEnrollView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mFingerprintDrawable = new UdfpsEnrollDrawable(mContext);
+ mFingerprintProgressDrawable = new UdfpsEnrollProgressBarDrawable(context);
mHandler = new Handler(Looper.getMainLooper());
}
@Override
protected void onFinishInflate() {
mFingerprintView = findViewById(R.id.udfps_enroll_animation_fp_view);
+ mFingerprintProgressView = findViewById(R.id.udfps_enroll_animation_fp_progress_view);
mFingerprintView.setImageDrawable(mFingerprintDrawable);
+ mFingerprintProgressView.setImageDrawable(mFingerprintProgressDrawable);
}
@Override
@@ -53,15 +61,34 @@ public class UdfpsEnrollView extends UdfpsAnimationView {
return mFingerprintDrawable;
}
+ void updateSensorLocation(@NonNull FingerprintSensorPropertiesInternal sensorProps) {
+ View fingerprintAccessibilityView = findViewById(R.id.udfps_enroll_accessibility_view);
+ final int sensorHeight = sensorProps.sensorRadius * 2;
+ final int sensorWidth = sensorHeight;
+ ViewGroup.LayoutParams params = fingerprintAccessibilityView.getLayoutParams();
+ params.width = sensorWidth;
+ params.height = sensorHeight;
+ fingerprintAccessibilityView.setLayoutParams(params);
+ fingerprintAccessibilityView.requestLayout();
+ }
+
void setEnrollHelper(UdfpsEnrollHelper enrollHelper) {
+ mFingerprintProgressDrawable.setEnrollHelper(enrollHelper);
mFingerprintDrawable.setEnrollHelper(enrollHelper);
}
void onEnrollmentProgress(int remaining, int totalSteps) {
- mHandler.post(() -> mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps));
+ mHandler.post(() -> {
+ mFingerprintProgressDrawable.onEnrollmentProgress(remaining, totalSteps);
+ mFingerprintDrawable.onEnrollmentProgress(remaining, totalSteps);
+ });
+ }
+
+ void onEnrollmentHelp(int remaining, int totalSteps) {
+ mHandler.post(() -> mFingerprintProgressDrawable.onEnrollmentHelp(remaining, totalSteps));
}
void onLastStepAcquired() {
- mHandler.post(mFingerprintDrawable::onLastStepAcquired);
+ mHandler.post(mFingerprintProgressDrawable::onLastStepAcquired);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
index 3dab010d917c..6cdd1c8b0d4e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsEnrollViewController.java
@@ -33,16 +33,21 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
@NonNull private final UdfpsEnrollHelper mEnrollHelper;
@NonNull private final UdfpsEnrollHelper.Listener mEnrollHelperListener =
new UdfpsEnrollHelper.Listener() {
- @Override
- public void onEnrollmentProgress(int remaining, int totalSteps) {
- mView.onEnrollmentProgress(remaining, totalSteps);
- }
+ @Override
+ public void onEnrollmentProgress(int remaining, int totalSteps) {
+ mView.onEnrollmentProgress(remaining, totalSteps);
+ }
- @Override
- public void onLastStepAcquired() {
- mView.onLastStepAcquired();
- }
- };
+ @Override
+ public void onEnrollmentHelp(int remaining, int totalSteps) {
+ mView.onEnrollmentHelp(remaining, totalSteps);
+ }
+
+ @Override
+ public void onLastStepAcquired() {
+ mView.onLastStepAcquired();
+ }
+ };
protected UdfpsEnrollViewController(
@NonNull UdfpsEnrollView view,
@@ -74,7 +79,7 @@ public class UdfpsEnrollViewController extends UdfpsAnimationViewController<Udfp
@NonNull
@Override
public PointF getTouchTranslation() {
- if (!mEnrollHelper.isCenterEnrollmentComplete()) {
+ if (!mEnrollHelper.isGuidedEnrollmentStage()) {
return new PointF(0, 0);
} else {
return mEnrollHelper.getNextGuidedEnrollmentPoint();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
index d46426a03621..9015396d26ab 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.java
@@ -155,6 +155,13 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
updateAlpha();
}
+ /**
+ * @return alpha between 0 and 255
+ */
+ int getUnpausedAlpha() {
+ return mAlpha;
+ }
+
@Override
protected int updateAlpha() {
int alpha = super.updateAlpha();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index 4896305daa2e..c128e5e2ab30 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -26,31 +26,33 @@ import android.view.MotionEvent;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-
/**
* Class that coordinates non-HBM animations during keyguard authentication.
*/
public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
@NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @NonNull private final DelayableExecutor mExecutor;
- @NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
@NonNull private final ConfigurationController mConfigurationController;
+ @NonNull private final SystemClock mSystemClock;
+ @NonNull private final KeyguardStateController mKeyguardStateController;
@NonNull private final UdfpsController mUdfpsController;
+ @NonNull private final UnlockedScreenOffAnimationController
+ mUnlockedScreenOffAnimationController;
private boolean mShowingUdfpsBouncer;
private boolean mUdfpsRequested;
@@ -59,6 +61,9 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
private int mStatusBarState;
private float mTransitionToFullShadeProgress;
private float mLastDozeAmount;
+ private long mLastUdfpsBouncerShowTime = -1;
+ private float mStatusBarExpansion;
+ private boolean mLaunchTransitionFadingAway;
/**
* hidden amount of pin/pattern/password bouncer
@@ -74,20 +79,22 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@NonNull StatusBar statusBar,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
- @NonNull DelayableExecutor mainDelayableExecutor,
@NonNull DumpManager dumpManager,
- @NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull LockscreenShadeTransitionController transitionController,
@NonNull ConfigurationController configurationController,
+ @NonNull SystemClock systemClock,
+ @NonNull KeyguardStateController keyguardStateController,
+ @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
@NonNull UdfpsController udfpsController) {
super(view, statusBarStateController, statusBar, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mExecutor = mainDelayableExecutor;
- mKeyguardViewMediator = keyguardViewMediator;
mLockScreenShadeTransitionController = transitionController;
mConfigurationController = configurationController;
+ mSystemClock = systemClock;
+ mKeyguardStateController = keyguardStateController;
mUdfpsController = udfpsController;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
}
@Override
@@ -96,6 +103,12 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
@Override
+ public void onInit() {
+ super.onInit();
+ mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
+ }
+
+ @Override
protected void onViewAttached() {
super.onViewAttached();
final float dozeAmount = mStatusBarStateController.getDozeAmount();
@@ -105,16 +118,20 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mUdfpsRequested = false;
+ mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
mStatusBarState = mStatusBarStateController.getState();
mQsExpanded = mKeyguardViewManager.isQsExpanded();
mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
mIsBouncerVisible = mKeyguardViewManager.bouncerIsOrWillBeShowing();
mConfigurationController.addCallback(mConfigurationListener);
+ mStatusBar.addExpansionChangedListener(mStatusBarExpansionChangedListener);
updateAlpha();
updatePauseAuth();
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
+ mUnlockedScreenOffAnimationController.addCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -122,13 +139,16 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
super.onViewDetached();
mFaceDetectRunning = false;
+ mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
mStatusBarStateController.removeCallback(mStateListener);
mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
mConfigurationController.removeCallback(mConfigurationListener);
+ mStatusBar.removeExpansionChangedListener(mStatusBarExpansionChangedListener);
if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
}
+ mUnlockedScreenOffAnimationController.removeCallback(mUnlockedScreenOffCallback);
}
@Override
@@ -140,9 +160,11 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
pw.println("mQsExpanded=" + mQsExpanded);
pw.println("mIsBouncerVisible=" + mIsBouncerVisible);
pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
- pw.println("mAlpha=" + mView.getAlpha());
+ pw.println("mStatusBarExpansion=" + mStatusBarExpansion);
+ pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
pw.println("mUdfpsRequested=" + mUdfpsRequested);
pw.println("mView.mUdfpsRequested=" + mView.mUdfpsRequested);
+ pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
}
/**
@@ -154,10 +176,13 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
return false;
}
+ boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
mShowingUdfpsBouncer = show;
- updatePauseAuth();
if (mShowingUdfpsBouncer) {
- if (mStatusBarState == StatusBarState.SHADE_LOCKED) {
+ mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
+ }
+ if (mShowingUdfpsBouncer) {
+ if (udfpsAffordanceWasNotShowing) {
mView.animateInUdfpsBouncer(null);
}
@@ -170,6 +195,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
} else {
mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
}
+ updateAlpha();
+ updatePauseAuth();
return true;
}
@@ -189,6 +216,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
return false;
}
+ if (mLaunchTransitionFadingAway) {
+ return true;
+ }
+
if (mStatusBarState != KEYGUARD) {
return true;
}
@@ -218,16 +249,25 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
* If we were previously showing the udfps bouncer, hide it and instead show the regular
* (pin/pattern/password) bouncer.
*
- * Does nothing if we weren't previously showing the udfps bouncer.
+ * Does nothing if we weren't previously showing the UDFPS bouncer.
*/
private void maybeShowInputBouncer() {
- if (mShowingUdfpsBouncer) {
+ if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
mKeyguardViewManager.showBouncer(true);
mKeyguardViewManager.resetAlternateAuth(false);
}
}
/**
+ * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
+ * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
+ * bouncer.
+ */
+ private boolean hasUdfpsBouncerShownWithMinTime() {
+ return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
+ }
+
+ /**
* Set the progress we're currently transitioning to the full shade. 0.0f means we're not
* transitioning yet, while 1.0f means we've fully dragged down.
*/
@@ -237,12 +277,17 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
private void updateAlpha() {
- // fade icon on transition to showing bouncer
+ // fade icon on transitions to showing the status bar, but if mUdfpsRequested, then
+ // the keyguard is occluded by some application - so instead use the input bouncer
+ // hidden amount to determine the fade
+ float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mStatusBarExpansion;
int alpha = mShowingUdfpsBouncer ? 255
: (int) MathUtils.constrain(
- MathUtils.map(.5f, .9f, 0f, 255f, mInputBouncerHiddenAmount),
+ MathUtils.map(.5f, .9f, 0f, 255f, expansion),
0f, 255f);
- alpha *= (1.0f - mTransitionToFullShadeProgress);
+ if (!mShowingUdfpsBouncer) {
+ alpha *= (1.0f - mTransitionToFullShadeProgress);
+ }
mView.setUnpausedAlpha(alpha);
}
@@ -287,6 +332,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
public void requestUdfps(boolean request, int color) {
mUdfpsRequested = request;
mView.requestUdfps(request, color);
+ updateAlpha();
updatePauseAuth();
}
@@ -356,4 +402,26 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mView.updateColor();
}
};
+
+ private final StatusBar.ExpansionChangedListener mStatusBarExpansionChangedListener =
+ new StatusBar.ExpansionChangedListener() {
+ @Override
+ public void onExpansionChanged(float expansion, boolean expanded) {
+ mStatusBarExpansion = expansion;
+ updateAlpha();
+ }
+ };
+
+ private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onLaunchTransitionFadingAwayChanged() {
+ mLaunchTransitionFadingAway =
+ mKeyguardStateController.isLaunchTransitionFadingAway();
+ updatePauseAuth();
+ }
+ };
+
+ private final UnlockedScreenOffAnimationController.Callback mUnlockedScreenOffCallback =
+ (linear, eased) -> mStateListener.onDozeAmountChanged(linear, eased);
}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
index e1349f2aba6d..40c28fab51b8 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/ZigZagClassifier.java
@@ -21,6 +21,7 @@ import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHT
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_PRIMARY_DEVIANCE;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.BRIGHTLINE_FALSING_ZIGZAG_Y_SECONDARY_DEVIANCE;
import static com.android.systemui.classifier.Classifier.BRIGHTNESS_SLIDER;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.SHADE_DRAG;
import android.graphics.Point;
@@ -89,7 +90,9 @@ class ZigZagClassifier extends FalsingClassifier {
Result calculateFalsingResult(
@Classifier.InteractionType int interactionType,
double historyBelief, double historyConfidence) {
- if (interactionType == BRIGHTNESS_SLIDER || interactionType == SHADE_DRAG) {
+ if (interactionType == BRIGHTNESS_SLIDER
+ || interactionType == SHADE_DRAG
+ || interactionType == LOCK_ICON) {
return Result.passed(0);
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index 4104e3184efd..46a03e809b06 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -16,6 +16,10 @@
package com.android.systemui.controls.ui
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
@@ -23,18 +27,25 @@ import android.view.WindowInsets
import android.view.WindowInsets.Type
import com.android.systemui.R
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
- * Displays Device Controls inside an activity
+ * Displays Device Controls inside an activity. This activity is available to be displayed over the
+ * lockscreen if the user has allowed it via
+ * [android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]. This activity will be
+ * destroyed on SCREEN_OFF events, due to issues with occluded activities over lockscreen as well as
+ * user expectations for the activity to not continue running.
*/
class ControlsActivity @Inject constructor(
- private val uiController: ControlsUiController
+ private val uiController: ControlsUiController,
+ private val broadcastDispatcher: BroadcastDispatcher
) : LifecycleActivity() {
private lateinit var parent: ViewGroup
+ private lateinit var broadcastReceiver: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -62,6 +73,8 @@ class ControlsActivity @Inject constructor(
WindowInsets.CONSUMED
}
}
+
+ initBroadcastReceiver()
}
override fun onResume() {
@@ -83,4 +96,25 @@ class ControlsActivity @Inject constructor(
uiController.hide()
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+
+ broadcastDispatcher.unregisterReceiver(broadcastReceiver)
+ }
+
+ private fun initBroadcastReceiver() {
+ broadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val action = intent.getAction()
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ finish()
+ }
+ }
+ }
+
+ val filter = IntentFilter()
+ filter.addAction(Intent.ACTION_SCREEN_OFF)
+ broadcastDispatcher.registerReceiver(broadcastReceiver, filter)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 557bca830f6c..5a52fd0516ea 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -256,13 +256,13 @@ class ControlsUiControllerImpl @Inject constructor (
// Force animations when transitioning from a dialog to an activity
intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
- if (keyguardStateController.isUnlocked()) {
+ if (keyguardStateController.isShowing()) {
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
+ } else {
activityContext.startActivity(
intent,
ActivityOptions.makeSceneTransitionAnimation(activityContext as Activity).toBundle()
)
- } else {
- activityStarter.postStartActivityDismissingKeyguard(intent, 0 /* delay */)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index c97a30e6e13e..79ee6a8e8830 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -198,9 +198,11 @@ public class DependencyProvider {
@SysUISingleton
@Provides
static ThemeOverlayApplier provideThemeOverlayManager(Context context,
- @Background Executor bgExecutor, OverlayManager overlayManager,
+ @Background Executor bgExecutor,
+ @Main Executor mainExecutor,
+ OverlayManager overlayManager,
DumpManager dumpManager) {
- return new ThemeOverlayApplier(overlayManager, bgExecutor,
+ return new ThemeOverlayApplier(overlayManager, bgExecutor, mainExecutor,
context.getString(R.string.launcher_overlayable_package),
context.getString(R.string.themepicker_overlayable_package), dumpManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 41964652ac49..845d7dc26ee5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -214,6 +214,14 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends display state delayed by UDFPS event to the logs
+ * @param delayedDisplayState the display screen state that was delayed
+ */
+ public void traceDisplayStateDelayedByUdfps(int delayedDisplayState) {
+ mLogger.logDisplayStateDelayedByUdfps(delayedDisplayState);
+ }
+
+ /**
* Appends display state changed event to the logs
* @param displayState new DozeMachine state
*/
@@ -267,6 +275,13 @@ public class DozeLog implements Dumpable {
}
/**
+ * Appends sensor event dropped event to logs
+ */
+ public void traceSensorEventDropped(int sensorEvent, String reason) {
+ mLogger.logSensorEventDropped(sensorEvent, reason);
+ }
+
+ /**
* Appends pulse dropped event to logs
* @param reason why the pulse was dropped
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 9bc74be9b9c3..dc186182432f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -160,6 +160,14 @@ class DozeLogger @Inject constructor(
})
}
+ fun logDisplayStateDelayedByUdfps(delayedDisplayState: Int) {
+ buffer.log(TAG, INFO, {
+ str1 = Display.stateToString(delayedDisplayState)
+ }, {
+ "Delaying display state change to: $str1 due to UDFPS activity"
+ })
+ }
+
fun logDisplayStateChanged(displayState: Int) {
buffer.log(TAG, INFO, {
str1 = Display.stateToString(displayState)
@@ -197,6 +205,15 @@ class DozeLogger @Inject constructor(
})
}
+ fun logSensorEventDropped(sensorEvent: Int, reason: String) {
+ buffer.log(TAG, INFO, {
+ int1 = sensorEvent
+ str1 = reason
+ }, {
+ "SensorEvent [$int1] dropped, reason=$str1"
+ })
+ }
+
fun logPulseDropped(reason: String) {
buffer.log(TAG, INFO, {
str1 = reason
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 470d2f364c1c..058f37a2ef38 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -29,13 +29,14 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.Display;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.BrightnessSensor;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.util.sensors.AsyncSensorManager;
import java.io.PrintWriter;
@@ -55,6 +56,16 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
"com.android.systemui.doze.AOD_BRIGHTNESS";
protected static final String BRIGHTNESS_BUCKET = "brightness_bucket";
+ /**
+ * Just before the screen times out from user inactivity, DisplayPowerController dims the screen
+ * brightness to the lower of {@link #mScreenBrightnessDim}, or the current brightness minus
+ * DisplayPowerController#SCREEN_DIM_MINIMUM_REDUCTION_FLOAT.
+ *
+ * This value is 0.04f * 255, which converts SCREEN_DIM_MINIMUM_REDUCTION_FLOAT to the integer
+ * brightness values used by doze.
+ */
+ private static final int SCREEN_DIM_MINIMUM_REDUCTION_INT = 10;
+
private final Context mContext;
private final DozeMachine.Service mDozeService;
private final DozeHost mDozeHost;
@@ -63,6 +74,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
private final Optional<Sensor> mLightSensorOptional;
private final WakefulnessLifecycle mWakefulnessLifecycle;
private final DozeParameters mDozeParameters;
+ private final DockManager mDockManager;
private final int[] mSensorToBrightness;
private final int[] mSensorToScrimOpacity;
private final int mScreenBrightnessDim;
@@ -81,13 +93,17 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
*/
private int mDebugBrightnessBucket = -1;
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+
@Inject
public DozeScreenBrightness(Context context, @WrappedService DozeMachine.Service service,
AsyncSensorManager sensorManager,
@BrightnessSensor Optional<Sensor> lightSensorOptional, DozeHost host, Handler handler,
AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
WakefulnessLifecycle wakefulnessLifecycle,
- DozeParameters dozeParameters) {
+ DozeParameters dozeParameters,
+ DockManager dockManager,
+ UnlockedScreenOffAnimationController unlockedScreenOffAnimationController) {
mContext = context;
mDozeService = service;
mSensorManager = sensorManager;
@@ -96,6 +112,8 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
mDozeParameters = dozeParameters;
mDozeHost = host;
mHandler = handler;
+ mDockManager = dockManager;
+ mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mDefaultDozeBrightness = alwaysOnDisplayPolicy.defaultDozeBrightness;
mScreenBrightnessDim = alwaysOnDisplayPolicy.dimBrightness;
@@ -107,7 +125,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
switch (newState) {
case INITIALIZED:
+ resetBrightnessToDefault();
+ break;
+ case DOZE_AOD:
+ case DOZE_REQUEST_PULSE:
+ case DOZE_AOD_DOCKED:
+ setLightSensorEnabled(true);
+ break;
case DOZE:
+ setLightSensorEnabled(false);
resetBrightnessToDefault();
break;
case FINISH:
@@ -120,15 +146,6 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
}
}
- @Override
- public void onScreenState(int state) {
- if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) {
- setLightSensorEnabled(true);
- } else {
- setLightSensorEnabled(false);
- }
- }
-
private void onDestroy() {
setLightSensorEnabled(false);
}
@@ -146,14 +163,15 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
}
}
- private void updateBrightnessAndReady(boolean force) {
+ public void updateBrightnessAndReady(boolean force) {
if (force || mRegistered || mDebugBrightnessBucket != -1) {
int sensorValue = mDebugBrightnessBucket == -1
? mLastSensorValue : mDebugBrightnessBucket;
int brightness = computeBrightness(sensorValue);
boolean brightnessReady = brightness > 0;
if (brightnessReady) {
- mDozeService.setDozeScreenBrightness(clampToUserSetting(brightness));
+ mDozeService.setDozeScreenBrightness(
+ clampToDimBrightnessForScreenOff(clampToUserSetting(brightness)));
}
int scrimOpacity = -1;
@@ -205,13 +223,21 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
/**
* Clamp the brightness to the dim brightness value used by PowerManagerService just before the
* device times out and goes to sleep, if we are sleeping from a timeout. This ensures that we
- * don't raise the brightness back to the user setting before playing the screen off animation.
+ * don't raise the brightness back to the user setting before or during the screen off
+ * animation.
*/
private int clampToDimBrightnessForScreenOff(int brightness) {
- if (mDozeParameters.shouldControlUnlockedScreenOff()
+ if (mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
&& mWakefulnessLifecycle.getLastSleepReason()
== PowerManager.GO_TO_SLEEP_REASON_TIMEOUT) {
- return Math.min(mScreenBrightnessDim, brightness);
+ return Math.max(
+ PowerManager.BRIGHTNESS_OFF,
+ // Use the lower of either the dim brightness, or the current brightness reduced
+ // by the minimum dim amount. This is the same logic used in
+ // DisplayPowerController#updatePowerState to apply a minimum dim amount.
+ Math.min(
+ brightness - SCREEN_DIM_MINIMUM_REDUCTION_INT,
+ mScreenBrightnessDim));
} else {
return brightness;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 8c50a16b566f..038be48b53ee 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -26,6 +26,11 @@ import android.os.Handler;
import android.util.Log;
import android.view.Display;
+import androidx.annotation.Nullable;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.doze.dagger.WrappedService;
@@ -34,6 +39,7 @@ import com.android.systemui.util.wakelock.SettableWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Controls the screen when dozing.
@@ -56,23 +62,64 @@ public class DozeScreenState implements DozeMachine.Part {
*/
public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2500;
+ /**
+ * Add an extra delay to the transition to DOZE when udfps is current activated before
+ * the display state transitions from ON => DOZE.
+ */
+ public static final int UDFPS_DISPLAY_STATE_DELAY = 1200;
+
private final DozeMachine.Service mDozeService;
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
private final DozeParameters mParameters;
private final DozeHost mDozeHost;
+ private final AuthController mAuthController;
+ private final Provider<UdfpsController> mUdfpsControllerProvider;
+ @Nullable private UdfpsController mUdfpsController;
+ private final DozeLog mDozeLog;
+ private final DozeScreenBrightness mDozeScreenBrightness;
private int mPendingScreenState = Display.STATE_UNKNOWN;
private SettableWakeLock mWakeLock;
@Inject
- public DozeScreenState(@WrappedService DozeMachine.Service service, @Main Handler handler,
- DozeHost host, DozeParameters parameters, WakeLock wakeLock) {
+ public DozeScreenState(
+ @WrappedService DozeMachine.Service service,
+ @Main Handler handler,
+ DozeHost host,
+ DozeParameters parameters,
+ WakeLock wakeLock,
+ AuthController authController,
+ Provider<UdfpsController> udfpsControllerProvider,
+ DozeLog dozeLog,
+ DozeScreenBrightness dozeScreenBrightness) {
mDozeService = service;
mHandler = handler;
mParameters = parameters;
mDozeHost = host;
mWakeLock = new SettableWakeLock(wakeLock, TAG);
+ mAuthController = authController;
+ mUdfpsControllerProvider = udfpsControllerProvider;
+ mDozeLog = dozeLog;
+ mDozeScreenBrightness = dozeScreenBrightness;
+
+ updateUdfpsController();
+ if (mUdfpsController == null) {
+ mAuthController.addCallback(new AuthController.Callback() {
+ @Override
+ public void onAllAuthenticatorsRegistered() {
+ updateUdfpsController();
+ }
+ });
+ }
+ }
+
+ private void updateUdfpsController() {
+ if (mAuthController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())) {
+ mUdfpsController = mUdfpsControllerProvider.get();
+ } else {
+ mUdfpsController = null;
+ }
}
@Override
@@ -110,21 +157,28 @@ public class DozeScreenState implements DozeMachine.Part {
mPendingScreenState = screenState;
// Delay screen state transitions even longer while animations are running.
- boolean shouldDelayTransition = newState == DOZE_AOD
+ boolean shouldDelayTransitionEnteringDoze = newState == DOZE_AOD
&& mParameters.shouldControlScreenOff() && !turningOn;
- if (shouldDelayTransition) {
+ // Delay screen state transition longer if UDFPS is actively authenticating a fp
+ boolean shouldDelayTransitionForUDFPS = newState == DOZE_AOD
+ && mUdfpsController != null && mUdfpsController.isFingerDown();
+
+ if (shouldDelayTransitionEnteringDoze || shouldDelayTransitionForUDFPS) {
mWakeLock.setAcquired(true);
}
if (!messagePending) {
if (DEBUG) {
Log.d(TAG, "Display state changed to " + screenState + " delayed by "
- + (shouldDelayTransition ? ENTER_DOZE_DELAY : 1));
+ + (shouldDelayTransitionEnteringDoze ? ENTER_DOZE_DELAY : 1));
}
- if (shouldDelayTransition) {
+ if (shouldDelayTransitionEnteringDoze) {
mHandler.postDelayed(mApplyPendingScreenState, ENTER_DOZE_DELAY);
+ } else if (shouldDelayTransitionForUDFPS) {
+ mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+ mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
} else {
mHandler.post(mApplyPendingScreenState);
}
@@ -139,6 +193,12 @@ public class DozeScreenState implements DozeMachine.Part {
}
private void applyPendingScreenState() {
+ if (mUdfpsController != null && mUdfpsController.isFingerDown()) {
+ mDozeLog.traceDisplayStateDelayedByUdfps(mPendingScreenState);
+ mHandler.postDelayed(mApplyPendingScreenState, UDFPS_DISPLAY_STATE_DELAY);
+ return;
+ }
+
applyScreenState(mPendingScreenState);
mPendingScreenState = Display.STATE_UNKNOWN;
}
@@ -147,6 +207,12 @@ public class DozeScreenState implements DozeMachine.Part {
if (screenState != Display.STATE_UNKNOWN) {
if (DEBUG) Log.d(TAG, "setDozeScreenState(" + screenState + ")");
mDozeService.setDozeScreenState(screenState);
+ if (screenState == Display.STATE_DOZE) {
+ // If we're entering doze, update the doze screen brightness. We might have been
+ // clamping it to the dim brightness during the screen off animation, and we should
+ // now change it to the brightness we actually want according to the sensor.
+ mDozeScreenBrightness.updateBrightnessAndReady(false /* force */);
+ }
mPendingScreenState = Display.STATE_UNKNOWN;
mWakeLock.setAcquired(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index b2db86f16104..55e6154b829d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -456,13 +456,24 @@ public class DozeSensors {
public void updateListening() {
if (!mConfigured || mSensor == null) return;
- if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)
- && !mRegistered) {
- mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
- if (DEBUG) Log.d(TAG, "requestTriggerSensor " + mRegistered);
+ if (mRequested && !mDisabled && (enabledBySetting() || mIgnoresSetting)) {
+ if (!mRegistered) {
+ mRegistered = mSensorManager.requestTriggerSensor(this, mSensor);
+ if (DEBUG) {
+ Log.d(TAG, "requestTriggerSensor[" + mSensor
+ + "] " + mRegistered);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "requestTriggerSensor[" + mSensor
+ + "] already registered");
+ }
+ }
} else if (mRegistered) {
final boolean rt = mSensorManager.cancelTriggerSensor(this, mSensor);
- if (DEBUG) Log.d(TAG, "cancelTriggerSensor " + rt);
+ if (DEBUG) {
+ Log.d(TAG, "cancelTriggerSensor[" + mSensor + "] " + rt);
+ }
mRegistered = false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 455f3c0d6ac2..756adca724e9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -41,6 +41,7 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeMachine.State;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -92,6 +93,7 @@ public class DozeTriggers implements DozeMachine.Part {
private final BroadcastDispatcher mBroadcastDispatcher;
private final AuthController mAuthController;
private final DelayableExecutor mMainExecutor;
+ private final KeyguardStateController mKeyguardStateController;
private final UiEventLogger mUiEventLogger;
private long mNotificationPulseTime;
@@ -179,7 +181,8 @@ public class DozeTriggers implements DozeMachine.Part {
DozeLog dozeLog, BroadcastDispatcher broadcastDispatcher,
SecureSettings secureSettings, AuthController authController,
@Main DelayableExecutor mainExecutor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyguardStateController keyguardStateController) {
mContext = context;
mDozeHost = dozeHost;
mConfig = config;
@@ -198,6 +201,7 @@ public class DozeTriggers implements DozeMachine.Part {
mAuthController = authController;
mMainExecutor = mainExecutor;
mUiEventLogger = uiEventLogger;
+ mKeyguardStateController = keyguardStateController;
}
@Override
@@ -294,6 +298,7 @@ public class DozeTriggers implements DozeMachine.Part {
proximityCheckThenCall((result) -> {
if (result != null && result) {
// In pocket, drop event.
+ mDozeLog.traceSensorEventDropped(pulseReason, "prox reporting near");
return;
}
if (isDoubleTap || isTap) {
@@ -302,6 +307,10 @@ public class DozeTriggers implements DozeMachine.Part {
}
gentleWakeUp(pulseReason);
} else if (isPickup) {
+ if (shouldDropPickupEvent()) {
+ mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded");
+ return;
+ }
gentleWakeUp(pulseReason);
} else if (isUdfpsLongPress) {
final State state = mMachine.getState();
@@ -320,7 +329,7 @@ public class DozeTriggers implements DozeMachine.Part {
}, true /* alreadyPerformedProxCheck */, pulseReason);
}
- if (isPickup) {
+ if (isPickup && !shouldDropPickupEvent()) {
final long timeSinceNotification =
SystemClock.elapsedRealtime() - mNotificationPulseTime;
final boolean withinVibrationThreshold =
@@ -329,6 +338,10 @@ public class DozeTriggers implements DozeMachine.Part {
}
}
+ private boolean shouldDropPickupEvent() {
+ return mKeyguardStateController.isOccluded();
+ }
+
private void gentleWakeUp(int reason) {
// Log screen wake up reason (lift/pickup, tap, double-tap)
Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index bfa478088cad..5b327bd5e4c1 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -34,7 +34,7 @@ import javax.inject.Inject
* See [DumpHandler] for more information on how and when this information is dumped.
*/
@SysUISingleton
-class DumpManager @Inject constructor() {
+open class DumpManager @Inject constructor() {
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
index 28f63b07e584..6561bd5a5323 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt
@@ -25,8 +25,12 @@ import javax.inject.Inject
* Proxy to make {@link SystemProperties} easily testable.
*/
@SysUISingleton
-class SystemPropertiesHelper @Inject constructor() {
+open class SystemPropertiesHelper @Inject constructor() {
fun getBoolean(name: String, default: Boolean): Boolean {
return SystemProperties.getBoolean(name, default)
}
+
+ fun set(name: String, value: Int) {
+ SystemProperties.set(name, value.toString())
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index bc4ced452630..1b4a47e4bf39 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -62,6 +62,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.view.RotationPolicy;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -172,7 +173,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar) {
+ StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
super(context,
windowManagerFuncs,
@@ -204,7 +206,8 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
sysUiState,
handler,
packageManager,
- statusBar);
+ statusBar,
+ keyguardUpdateMonitor);
mLockPatternUtils = lockPatternUtils;
mKeyguardStateController = keyguardStateController;
@@ -266,7 +269,7 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
this::getWalletViewController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, isKeyguardShowing(), mPowerAdapter, getEventLogger(),
- getStatusBar());
+ getStatusBar(), getKeyguardUpdateMonitor(), mLockPatternUtils);
if (shouldShowLockMessage(dialog)) {
dialog.showLockMessage();
@@ -334,12 +337,13 @@ public class GlobalActionsDialog extends GlobalActionsDialogLite
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- StatusBar statusBar) {
+ StatusBar statusBar, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ LockPatternUtils lockPatternUtils) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions,
adapter, overflowAdapter, sysuiColorExtractor, statusBarService,
notificationShadeWindowController, sysuiState, onRotateCallback,
keyguardShowing, powerAdapter, uiEventLogger, null,
- statusBar);
+ statusBar, keyguardUpdateMonitor, lockPatternUtils);
mWalletFactory = walletFactory;
// Update window attributes
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 06e74821869e..9ada54b9ef6f 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -84,6 +84,7 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
@@ -108,6 +109,7 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.EmergencyAffordanceManager;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.animation.Interpolators;
@@ -170,6 +172,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ // See NotificationManagerService#scheduleDurationReachedLocked
+ private static final long TOAST_FADE_TIME = 333;
+ // See NotificationManagerService.LONG_DELAY
+ private static final int TOAST_VISIBLE_TIME = 3500;
+
private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs;
private final AudioManager mAudioManager;
@@ -231,6 +238,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
protected Handler mMainHandler;
private int mSmallestScreenWidthDp;
private final StatusBar mStatusBar;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -338,7 +346,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
SysUiState sysUiState,
@Main Handler handler,
PackageManager packageManager,
- StatusBar statusBar) {
+ StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor) {
mContext = context;
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -369,6 +378,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mMainHandler = handler;
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mStatusBar = statusBar;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -422,6 +432,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
return mStatusBar;
}
+ protected KeyguardUpdateMonitor getKeyguardUpdateMonitor() {
+ return mKeyguardUpdateMonitor;
+ }
+
/**
* Show the global actions dialog (creating if necessary)
*
@@ -653,7 +667,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mAdapter, mOverflowAdapter, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger,
- mInfoProvider, mStatusBar);
+ mInfoProvider, mStatusBar, mKeyguardUpdateMonitor, mLockPatternUtils);
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -2122,6 +2136,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private GlobalActionsInfoProvider mInfoProvider;
private GestureDetector mGestureDetector;
private StatusBar mStatusBar;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private LockPatternUtils mLockPatternUtils;
protected ViewGroup mContainer;
@@ -2173,7 +2189,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
NotificationShadeWindowController notificationShadeWindowController,
SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
- @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar) {
+ @Nullable GlobalActionsInfoProvider infoProvider, StatusBar statusBar,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
super(context, themeRes);
mContext = context;
mAdapter = adapter;
@@ -2188,6 +2205,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mUiEventLogger = uiEventLogger;
mInfoProvider = infoProvider;
mStatusBar = statusBar;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2308,6 +2327,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
if (mInfoProvider != null && mInfoProvider.shouldShowMessage()) {
mInfoProvider.addPanel(mContext, mContainer, mAdapter.getCount(), () -> dismiss());
}
+
+ // If user entered from the lock screen and smart lock was enabled, disable it
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ boolean userHasTrust = mKeyguardUpdateMonitor.getUserHasTrust(user);
+ if (mKeyguardShowing && userHasTrust) {
+ mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
+ showSmartLockDisabledMessage();
+ }
}
protected void fixNavBarClipping() {
@@ -2319,6 +2346,37 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
contentParent.setClipToPadding(false);
}
+ private void showSmartLockDisabledMessage() {
+ // Since power menu is the top window, make a Toast-like view that will show up
+ View message = LayoutInflater.from(mContext)
+ .inflate(com.android.systemui.R.layout.global_actions_toast, mContainer, false);
+
+ // Set up animation
+ AccessibilityManager mAccessibilityManager =
+ (AccessibilityManager) getContext().getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final int visibleTime = mAccessibilityManager.getRecommendedTimeoutMillis(
+ TOAST_VISIBLE_TIME, AccessibilityManager.FLAG_CONTENT_TEXT);
+ message.setVisibility(View.VISIBLE);
+ message.setAlpha(0f);
+ mContainer.addView(message);
+
+ // Fade in
+ message.animate()
+ .alpha(1f)
+ .setDuration(TOAST_FADE_TIME)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Then fade out
+ message.animate()
+ .alpha(0f)
+ .setDuration(TOAST_FADE_TIME)
+ .setStartDelay(visibleTime);
+ }
+ });
+ }
+
@Override
protected void onStart() {
super.onStart();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index a641ad4b338b..c4508e043c7d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -97,7 +97,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
int backgroundAlpha = (int) (ScrimController.BUSY_SCRIM_ALPHA * 255);
background.setAlpha(backgroundAlpha);
mBlurUtils.applyBlur(d.getWindow().getDecorView().getViewRootImpl(),
- mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
+ (int) mBlurUtils.blurRadiusOfRatio(1), backgroundAlpha == 255);
} else {
float backgroundAlpha = mContext.getResources().getFloat(
com.android.systemui.R.dimen.shutdown_scrim_behind_alpha);
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index d30783c29f92..a51ec54ebcce 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -102,7 +102,6 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer {
@Override
public Size reportSurfaceSize() {
- mTexture.use(null /* consumer */);
mSurfaceSize.set(mTexture.getTextureDimensions());
return new Size(mSurfaceSize.width(), mSurfaceSize.height());
}
@@ -124,6 +123,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer {
private final WallpaperManager mWallpaperManager;
private Bitmap mBitmap;
private boolean mWcgContent;
+ private boolean mTextureUsed;
private WallpaperTexture(WallpaperManager wallpaperManager) {
mWallpaperManager = wallpaperManager;
@@ -141,6 +141,7 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer {
mWallpaperManager.forgetLoadedWallpaper();
if (mBitmap != null) {
mDimensions.set(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ mTextureUsed = true;
} else {
Log.w(TAG, "Can't get bitmap");
}
@@ -171,6 +172,9 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer {
}
private Rect getTextureDimensions() {
+ if (!mTextureUsed) {
+ mDimensions.set(mWallpaperManager.peekBitmapDimensions());
+ }
return mDimensions;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
index 2d215e0f1f62..a424674ed252 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewController.java
@@ -100,8 +100,7 @@ public class KeyguardIndicationRotateTextViewController extends
*/
public void updateIndication(@IndicationType int type, KeyguardIndication newIndication,
boolean updateImmediately) {
- if (type == INDICATION_TYPE_NOW_PLAYING
- || type == INDICATION_TYPE_REVERSE_CHARGING) {
+ if (type == INDICATION_TYPE_REVERSE_CHARGING) {
// temporarily don't show here, instead use AmbientContainer b/181049781
return;
}
@@ -303,7 +302,6 @@ public class KeyguardIndicationRotateTextViewController extends
public static final int INDICATION_TYPE_TRUST = 6;
public static final int INDICATION_TYPE_RESTING = 7;
public static final int INDICATION_TYPE_USER_LOCKED = 8;
- public static final int INDICATION_TYPE_NOW_PLAYING = 9;
public static final int INDICATION_TYPE_REVERSE_CHARGING = 10;
@IntDef({
@@ -317,7 +315,6 @@ public class KeyguardIndicationRotateTextViewController extends
INDICATION_TYPE_TRUST,
INDICATION_TYPE_RESTING,
INDICATION_TYPE_USER_LOCKED,
- INDICATION_TYPE_NOW_PLAYING,
INDICATION_TYPE_REVERSE_CHARGING,
})
@Retention(RetentionPolicy.SOURCE)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ee3d40edc2eb..1a8af3bb650b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -818,6 +818,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
private final KeyguardStateController mKeyguardStateController;
private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
+ private boolean mWallpaperSupportsAmbientMode;
/**
* Injected constructor. See {@link KeyguardModule}.
@@ -2089,13 +2090,14 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
int flags = 0;
if (mKeyguardViewControllerLazy.get().shouldDisableWindowAnimationsForUnlock()
- || (mWakeAndUnlocking && !mPulsing)
- || isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe()) {
+ || mWakeAndUnlocking && !mWallpaperSupportsAmbientMode) {
flags |= WindowManagerPolicyConstants
.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
}
if (mKeyguardViewControllerLazy.get().isGoingToNotificationShade()
- || (mWakeAndUnlocking && mPulsing)) {
+ || mWakeAndUnlocking && mWallpaperSupportsAmbientMode) {
+ // When the wallpaper supports ambient mode, the scrim isn't fully opaque during
+ // wake and unlock and we should fade in the app on top of the wallpaper
flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
}
if (mKeyguardViewControllerLazy.get().isUnlockWithWallpaper()) {
@@ -2784,6 +2786,15 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable,
mPulsing = pulsing;
}
+ /**
+ * Set if the wallpaper supports ambient mode. This is used to trigger the right animation.
+ * In case it does support it, we have to fade in the incoming app, otherwise we'll reveal it
+ * with the light reveal scrim.
+ */
+ public void setWallpaperSupportsAmbientMode(boolean supportsAmbientMode) {
+ mWallpaperSupportsAmbientMode = supportsAmbientMode;
+ }
+
private static class StartKeyguardExitAnimParams {
@WindowManager.TransitionOldType int mTransit;
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 2facf3dedd18..c743fe125cf7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -212,14 +212,32 @@ class MediaCarouselController @Inject constructor(
isSsReactivated: Boolean
) {
if (addOrUpdatePlayer(key, oldKey, data)) {
+ // Log card received if a new resumable media card is added
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ false,
it.surfaceForSmartspaceLogging,
rank = MediaPlayerData.getMediaPlayerIndex(key))
}
}
+ if (isSsReactivated) {
+ // If resumable media is reactivated by headphone connection, update instance
+ // id for each card and log a receive event.
+ MediaPlayerData.players().forEachIndexed { index, it ->
+ if (it.recommendationViewHolder == null) {
+ it.mInstanceId = SmallHash.hash(it.mUid +
+ systemClock.currentTimeMillis().toInt())
+ logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
+ it.mInstanceId,
+ it.mUid,
+ /* isRecommendationCard */ false,
+ it.surfaceForSmartspaceLogging,
+ rank = index)
+ }
+ }
+ }
if (mediaCarouselScrollHandler.visibleToUser &&
isSsReactivated && !mediaCarouselScrollHandler.qsExpanded) {
// It could happen that reactived media player isn't visible to user because
@@ -252,6 +270,7 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.getMediaPlayer(key)?.let {
logSmartspaceCardReported(759, // SMARTSPACE_CARD_RECEIVED
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging,
rank = MediaPlayerData.getMediaPlayerIndex(key))
@@ -261,6 +280,7 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.getMediaPlayerIndex(key)) {
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
it.mInstanceId,
+ it.mUid,
/* isRecommendationCard */ true,
it.surfaceForSmartspaceLogging)
}
@@ -339,9 +359,9 @@ class MediaCarouselController @Inject constructor(
if (activeMediaIndex != -1) {
previousVisiblePlayerKey?.let {
val previousVisibleIndex = MediaPlayerData.playerKeys()
- .indexOfFirst { key -> it == key }
+ .indexOfFirst { key -> it == key }
mediaCarouselScrollHandler
- .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
+ .scrollToPlayer(previousVisibleIndex, activeMediaIndex)
} ?: {
mediaCarouselScrollHandler.scrollToPlayer(destIndex = activeMediaIndex)
}
@@ -355,11 +375,11 @@ class MediaCarouselController @Inject constructor(
MediaPlayerData.moveIfExists(oldKey, key)
val existingPlayer = MediaPlayerData.getMediaPlayer(key)
val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
if (existingPlayer == null) {
var newPlayer = mediaControlPanelFactory.get()
newPlayer.attachPlayer(
- PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
+ PlayerViewHolder.create(LayoutInflater.from(context), mediaContent))
newPlayer.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
@@ -407,14 +427,14 @@ class MediaCarouselController @Inject constructor(
var newRecs = mediaControlPanelFactory.get()
newRecs.attachRecommendation(
- RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
+ RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent))
newRecs.mediaViewController.sizeChangedListener = this::updateCarouselDimensions
val lp = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT)
+ ViewGroup.LayoutParams.WRAP_CONTENT)
newRecs.recommendationViewHolder?.recommendations?.setLayoutParams(lp)
newRecs.bindRecommendation(data.copy(backgroundColor = bgColor))
val curVisibleMediaKey = MediaPlayerData.playerKeys()
- .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
+ .elementAtOrNull(mediaCarouselScrollHandler.visibleMediaIndex)
MediaPlayerData.addMediaRecommendation(key, data, newRecs, shouldPrioritize, systemClock)
updatePlayerToState(newRecs, noAnimation = true)
reorderAllPlayers(curVisibleMediaKey)
@@ -462,7 +482,7 @@ class MediaCarouselController @Inject constructor(
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
smartspaceMediaData?.let {
addSmartspaceMediaRecommendations(
- it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
+ it.targetId, it, MediaPlayerData.shouldPrioritizeSs)
}
} else {
removePlayer(key, dismissMediaData = false, dismissRecommendation = false)
@@ -585,7 +605,7 @@ class MediaCarouselController @Inject constructor(
?: endShowsActive
if (currentlyShowingOnlyActive != endShowsActive ||
((currentTransitionProgress != 1.0f && currentTransitionProgress != 0.0f) &&
- startShowsActive != endShowsActive)) {
+ startShowsActive != endShowsActive)) {
// Whenever we're transitioning from between differing states or the endstate differs
// we reset the translation
currentlyShowingOnlyActive = endShowsActive
@@ -696,23 +716,43 @@ class MediaCarouselController @Inject constructor(
}
logSmartspaceCardReported(800, // SMARTSPACE_CARD_SEEN
mediaControlPanel.mInstanceId,
+ mediaControlPanel.mUid,
isRecommendationCard,
mediaControlPanel.surfaceForSmartspaceLogging)
}
}
@JvmOverloads
+ /**
+ * Log Smartspace events
+ *
+ * @param eventId UI event id (e.g. 800 for SMARTSPACE_CARD_SEEN)
+ * @param instanceId id to uniquely identify a card, e.g. each headphone generates a new
+ * instanceId
+ * @param uid uid for the application that media comes from
+ * @param isRecommendationCard whether the card is media recommendation
+ * @param surface which display surface the media card is on (e.g. lockscreen, shade)
+ * @param interactedSubcardRank the rank for interacted media item for recommendation card, -1
+ * for tapping on card but not on any media item, 0 for first media item, 1 for second, etc.
+ * @param interactedSubcardCardinality how many media items were shown to the user when there
+ * is user interaction
+ * @param rank the rank for media card in the media carousel, starting from 0
+ *
+ */
fun logSmartspaceCardReported(
eventId: Int,
instanceId: Int,
+ uid: Int,
isRecommendationCard: Boolean,
surface: Int,
+ interactedSubcardRank: Int = 0,
+ interactedSubcardCardinality: Int = 0,
rank: Int = mediaCarouselScrollHandler.visibleMediaIndex
) {
// Only log media resume card when Smartspace data is available
if (!isRecommendationCard &&
- !mediaManager.smartspaceMediaData.isActive &&
- MediaPlayerData.smartspaceMediaData == null) {
+ !mediaManager.smartspaceMediaData.isActive &&
+ MediaPlayerData.smartspaceMediaData == null) {
return
}
@@ -720,13 +760,21 @@ class MediaCarouselController @Inject constructor(
SysUiStatsLog.write(SysUiStatsLog.SMARTSPACE_CARD_REPORTED,
eventId,
instanceId,
- if (isRecommendationCard)
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_MEDIA_RECOMMENDATIONS
- else
- SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__HEADPHONE_RESUME_MEDIA,
+ // Deprecated, replaced with AiAi feature type so we don't need to create logging
+ // card type for each new feature.
+ SysUiStatsLog.SMART_SPACE_CARD_REPORTED__CARD_TYPE__UNKNOWN_CARD,
surface,
rank,
- mediaContent.getChildCount())
+ mediaContent.getChildCount(),
+ if (isRecommendationCard)
+ 15 // MEDIA_RECOMMENDATION
+ else
+ 31, // MEDIA_RESUME
+ uid,
+ interactedSubcardRank,
+ interactedSubcardCardinality,
+ 0 // received_latency_millis
+ )
/* ktlint-disable max-line-length */
}
@@ -738,18 +786,20 @@ class MediaCarouselController @Inject constructor(
if (!recommendation.isEmpty()) {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
recommendation.get(0).mInstanceId,
+ recommendation.get(0).mUid,
true,
recommendation.get(0).surfaceForSmartspaceLogging,
- /* rank */-1)
+ rank = -1)
} else {
val visibleMediaIndex = mediaCarouselScrollHandler.visibleMediaIndex
if (MediaPlayerData.players().size > visibleMediaIndex) {
val player = MediaPlayerData.players().elementAt(visibleMediaIndex)
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
player.mInstanceId,
- false,
+ player.mUid,
+ false,
player.surfaceForSmartspaceLogging,
- /* rank */-1)
+ rank = -1)
}
}
mediaManager.onSwipeToDismiss()
@@ -768,7 +818,7 @@ class MediaCarouselController @Inject constructor(
@VisibleForTesting
internal object MediaPlayerData {
private val EMPTY = MediaData(-1, false, 0, null, null, null, null, null,
- emptyList(), emptyList(), "INVALID", null, null, null, true, null)
+ emptyList(), emptyList(), "INVALID", null, null, null, true, null)
// Whether should prioritize Smartspace card.
internal var shouldPrioritizeSs: Boolean = false
private set
@@ -776,18 +826,18 @@ internal object MediaPlayerData {
private set
data class MediaSortKey(
- // Whether the item represents a Smartspace media recommendation.
+ // Whether the item represents a Smartspace media recommendation.
val isSsMediaRec: Boolean,
val data: MediaData,
val updateTime: Long = 0
)
private val comparator =
- compareByDescending<MediaSortKey> { it.data.isPlaying }
- .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
- .thenByDescending { it.data.isLocalSession }
- .thenByDescending { !it.data.resumption }
- .thenByDescending { it.updateTime }
+ compareByDescending<MediaSortKey> { it.data.isPlaying }
+ .thenByDescending { if (shouldPrioritizeSs) it.isSsMediaRec else !it.isSsMediaRec }
+ .thenByDescending { it.data.isLocalSession }
+ .thenByDescending { !it.data.resumption }
+ .thenByDescending { it.updateTime }
private val mediaPlayers = TreeMap<MediaSortKey, MediaControlPanel>(comparator)
private val mediaData: MutableMap<String, MediaSortKey> = mutableMapOf()
@@ -808,7 +858,8 @@ internal object MediaPlayerData {
) {
shouldPrioritizeSs = shouldPrioritize
removeMediaPlayer(key)
- val sortKey = MediaSortKey(isSsMediaRec = true, EMPTY, clock.currentTimeMillis())
+ val sortKey = MediaSortKey(/* isSsMediaRec= */ true,
+ EMPTY.copy(isPlaying = false), clock.currentTimeMillis())
mediaData.put(key, sortKey)
mediaPlayers.put(sortKey, player)
smartspaceMediaData = data
@@ -888,4 +939,4 @@ internal object MediaPlayerData {
}
return false
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 15a70831b2f9..e7445f920ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -33,6 +33,7 @@ import android.graphics.drawable.Icon;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.os.Process;
import android.text.Layout;
import android.util.Log;
import android.view.View;
@@ -74,6 +75,8 @@ public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
private static final float DISABLED_ALPHA = 0.38f;
+ private static final String EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = "com.google"
+ + ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity";
private static final String EXTRAS_SMARTSPACE_INTENT =
"com.google.android.apps.gsa.smartspace.extra.SMARTSPACE_INTENT";
private static final int MEDIA_RECOMMENDATION_ITEMS_PER_ROW = 3;
@@ -111,6 +114,9 @@ public class MediaControlPanel {
private int mAlbumArtSize;
// Instance id for logging purpose.
protected int mInstanceId = -1;
+ // Uid for the media app.
+ protected int mUid = Process.INVALID_UID;
+ private int mSmartspaceMediaItemsCount;
private MediaCarouselController mMediaCarouselController;
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
@@ -248,7 +254,8 @@ public class MediaControlPanel {
openGuts();
return true;
} else {
- return false;
+ closeGuts();
+ return true;
}
});
mRecommendationViewHolder.getCancel().setOnClickListener(v -> {
@@ -266,7 +273,13 @@ public class MediaControlPanel {
}
mKey = key;
MediaSession.Token token = data.getToken();
- mInstanceId = SmallHash.hash(data.getPackageName());
+ PackageManager packageManager = mContext.getPackageManager();
+ try {
+ mUid = packageManager.getApplicationInfo(data.getPackageName(), 0 /* flags */).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to look up package name", e);
+ }
+ mInstanceId = SmallHash.hash(mUid);
mBackgroundColor = data.getBackgroundColor();
if (mToken == null || !mToken.equals(token)) {
@@ -360,27 +373,16 @@ public class MediaControlPanel {
final MediaDeviceData device = data.getDevice();
final int seamlessId = mPlayerViewHolder.getSeamless().getId();
- final int seamlessFallbackId = mPlayerViewHolder.getSeamlessFallback().getId();
- final boolean showFallback = device != null && !device.getEnabled();
- final int seamlessFallbackVisibility = showFallback ? View.VISIBLE : View.GONE;
- mPlayerViewHolder.getSeamlessFallback().setVisibility(seamlessFallbackVisibility);
- expandedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
- collapsedSet.setVisibility(seamlessFallbackId, seamlessFallbackVisibility);
- final int seamlessVisibility = showFallback ? View.GONE : View.VISIBLE;
- mPlayerViewHolder.getSeamless().setVisibility(seamlessVisibility);
- expandedSet.setVisibility(seamlessId, seamlessVisibility);
- collapsedSet.setVisibility(seamlessId, seamlessVisibility);
- final float seamlessAlpha = data.getResumption() ? DISABLED_ALPHA : 1.0f;
+ // Disable clicking on output switcher for invalid devices and resumption controls
+ final boolean seamlessDisabled = (device != null && !device.getEnabled())
+ || data.getResumption();
+ final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
expandedSet.setAlpha(seamlessId, seamlessAlpha);
collapsedSet.setAlpha(seamlessId, seamlessAlpha);
- // Disable clicking on output switcher for resumption controls.
- mPlayerViewHolder.getSeamless().setEnabled(!data.getResumption());
+ mPlayerViewHolder.getSeamless().setEnabled(!seamlessDisabled);
String deviceString = null;
- if (showFallback) {
- iconView.setImageDrawable(null);
- } else if (device != null) {
+ if (device != null && device.getEnabled()) {
Drawable icon = device.getIcon();
- iconView.setVisibility(View.VISIBLE);
if (icon instanceof AdaptiveIcon) {
AdaptiveIcon aIcon = (AdaptiveIcon) icon;
aIcon.setBackgroundColor(mBackgroundColor);
@@ -391,10 +393,9 @@ public class MediaControlPanel {
deviceString = device.getName();
} else {
// Reset to default
- Log.w(TAG, "device is null. Not binding output chip.");
- iconView.setVisibility(View.GONE);
- deviceString = mContext.getString(
- com.android.internal.R.string.ext_media_seamless_action);
+ Log.w(TAG, "Device is null or not enabled: " + device + ", not binding output chip.");
+ iconView.setImageResource(R.drawable.ic_media_home_devices);
+ deviceString = mContext.getString(R.string.media_seamless_other_device);
}
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
@@ -531,10 +532,11 @@ public class MediaControlPanel {
}
// Set up recommendation card's header.
- ApplicationInfo applicationInfo = null;
+ ApplicationInfo applicationInfo;
try {
applicationInfo = mContext.getPackageManager()
.getApplicationInfo(data.getPackageName(), 0 /* flags */);
+ mUid = applicationInfo.uid;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Fail to get media recommendation's app info", e);
return;
@@ -553,7 +555,8 @@ public class MediaControlPanel {
headerTitleText.setText(appLabel);
}
// Set up media rec card's tap action if applicable.
- setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction());
+ setSmartspaceRecItemOnClickListener(recommendationCard, data.getCardAction(),
+ /* interactedSubcardRank */ -1);
// Set up media rec card's accessibility label.
recommendationCard.setContentDescription(
mContext.getString(R.string.controls_media_smartspace_rec_description, appLabel));
@@ -567,7 +570,8 @@ public class MediaControlPanel {
ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
int mediaRecommendationNum = Math.min(mediaRecommendationList.size(),
MEDIA_RECOMMENDATION_MAX_NUM);
- for (int itemIndex = 0, uiComponentIndex = 0;
+ int uiComponentIndex = 0;
+ for (int itemIndex = 0;
itemIndex < mediaRecommendationNum && uiComponentIndex < mediaRecommendationNum;
itemIndex++) {
SmartspaceAction recommendation = mediaRecommendationList.get(itemIndex);
@@ -582,7 +586,16 @@ public class MediaControlPanel {
// Set up the media item's click listener if applicable.
ViewGroup mediaCoverContainer = mediaCoverContainers.get(uiComponentIndex);
- setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation);
+ setSmartspaceRecItemOnClickListener(mediaCoverContainer, recommendation,
+ uiComponentIndex);
+ // Bubble up the long-click event to the card.
+ mediaCoverContainer.setOnLongClickListener(v -> {
+ View parent = (View) v.getParent();
+ if (parent != null) {
+ parent.performLongClick();
+ }
+ return true;
+ });
// Set up the accessibility label for the media item.
String artistName = recommendation.getExtras()
@@ -614,10 +627,10 @@ public class MediaControlPanel {
mediaCoverItemsResIds.get(uiComponentIndex), true);
setVisibleAndAlpha(expandedSet,
mediaCoverContainersResIds.get(uiComponentIndex), true);
-
uiComponentIndex++;
}
+ mSmartspaceMediaItemsCount = uiComponentIndex;
// Set up long press to show guts setting panel.
mRecommendationViewHolder.getDismiss().setOnClickListener(v -> {
logSmartspaceCardReported(761, // SMARTSPACE_CARD_DISMISS
@@ -625,6 +638,22 @@ public class MediaControlPanel {
closeGuts();
mMediaDataManagerLazy.get().dismissSmartspaceRecommendation(
data.getTargetId(), MediaViewController.GUTS_ANIMATION_DURATION + 100L);
+
+ Intent dismissIntent = data.getDismissIntent();
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: "
+ + "extras missing dismiss_intent.");
+ return;
+ }
+
+ if (dismissIntent.getComponent() != null
+ && dismissIntent.getComponent().getClassName()
+ .equals(EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME)) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ mContext.startActivity(dismissIntent);
+ } else {
+ mContext.sendBroadcast(dismissIntent);
+ }
});
mController = null;
@@ -750,7 +779,8 @@ public class MediaControlPanel {
private void setSmartspaceRecItemOnClickListener(
@NonNull View view,
- @NonNull SmartspaceAction action) {
+ @NonNull SmartspaceAction action,
+ int interactedSubcardRank) {
if (view == null || action == null || action.getIntent() == null
|| action.getIntent().getExtras() == null) {
Log.e(TAG, "No tap action can be set up");
@@ -758,9 +788,10 @@ public class MediaControlPanel {
}
view.setOnClickListener(v -> {
- // When media recommendation card is shown, it will always be the top card.
logSmartspaceCardReported(760, // SMARTSPACE_CARD_CLICK
- /* isRecommendationCard */ true);
+ /* isRecommendationCard */ true,
+ interactedSubcardRank,
+ getSmartspaceSubCardCardinality());
if (shouldSmartspaceRecItemOpenInForeground(action)) {
// Request to unlock the device if the activity needs to be opened in foreground.
@@ -818,9 +849,28 @@ public class MediaControlPanel {
}
private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard) {
+ logSmartspaceCardReported(eventId, isRecommendationCard,
+ /* interactedSubcardRank */ 0,
+ /* interactedSubcardCardinality */ 0);
+ }
+
+ private void logSmartspaceCardReported(int eventId, boolean isRecommendationCard,
+ int interactedSubcardRank, int interactedSubcardCardinality) {
mMediaCarouselController.logSmartspaceCardReported(eventId,
mInstanceId,
+ mUid,
isRecommendationCard,
- getSurfaceForSmartspaceLogging());
+ getSurfaceForSmartspaceLogging(),
+ interactedSubcardRank,
+ interactedSubcardCardinality);
+ }
+
+ private int getSmartspaceSubCardCardinality() {
+ if (!mMediaCarouselController.getMediaCarouselScrollHandler().getQsExpanded()
+ && mSmartspaceMediaItemsCount > 3) {
+ return 3;
+ }
+
+ return mSmartspaceMediaItemsCount;
}
-}
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
index c8deb014f781..79206e86da09 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataFilter.kt
@@ -16,6 +16,7 @@
package com.android.systemui.media
+import android.content.Context
import android.os.SystemProperties
import android.util.Log
import com.android.internal.annotations.VisibleForTesting
@@ -32,6 +33,8 @@ import kotlin.collections.LinkedHashMap
private const val TAG = "MediaDataFilter"
private const val DEBUG = true
+private const val EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME = ("com.google" +
+ ".android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity")
private const val RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY = "resumable_media_max_age_seconds"
/**
@@ -51,6 +54,7 @@ internal val SMARTSPACE_MAX_AGE = SystemProperties
* background users (e.g. timeouts).
*/
class MediaDataFilter @Inject constructor(
+ private val context: Context,
private val broadcastDispatcher: BroadcastDispatcher,
private val mediaResumeListener: MediaResumeListener,
private val lockscreenUserManager: NotificationLockscreenUserManager,
@@ -229,6 +233,18 @@ class MediaDataFilter @Inject constructor(
mediaDataManager.setTimedOut(it, timedOut = true, forceUpdate = true)
}
if (smartspaceMediaData.isActive) {
+ val dismissIntent = smartspaceMediaData.dismissIntent
+ if (dismissIntent == null) {
+ Log.w(TAG, "Cannot create dismiss action click action: " +
+ "extras missing dismiss_intent.")
+ } else if (dismissIntent.getComponent() != null &&
+ dismissIntent.getComponent().getClassName()
+ == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME) {
+ // Dismiss the card Smartspace data through Smartspace trampoline activity.
+ context.startActivity(dismissIntent)
+ } else {
+ context.sendBroadcast(dismissIntent)
+ }
smartspaceMediaData = EMPTY_SMARTSPACE_MEDIA_DATA.copy(
targetId = smartspaceMediaData.targetId, isValid = smartspaceMediaData.isValid)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 0a28b47923da..eacdab6e537d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -76,12 +76,13 @@ private val ART_URIS = arrayOf(
private const val TAG = "MediaDataManager"
private const val DEBUG = true
+private const val EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY = "dismiss_intent"
private val LOADING = MediaData(-1, false, 0, null, null, null, null, null,
emptyList(), emptyList(), "INVALID", null, null, null, true, null)
@VisibleForTesting
internal val EMPTY_SMARTSPACE_MEDIA_DATA = SmartspaceMediaData("INVALID", false, false,
- "INVALID", null, emptyList(), 0)
+ "INVALID", null, emptyList(), null, 0)
fun isMediaNotification(sbn: StatusBarNotification): Boolean {
if (!sbn.notification.hasMediaSession()) {
@@ -212,8 +213,8 @@ class MediaDataManager(
mediaDataCombineLatest.addListener(mediaDataFilter)
// Set up links back into the pipeline for listeners that need to send events upstream.
- mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
- setTimedOut(token, timedOut) }
+ mediaTimeoutListener.timeoutCallback = { key: String, timedOut: Boolean ->
+ setTimedOut(key, timedOut) }
mediaResumeListener.setManager(this)
mediaDataFilter.mediaDataManager = this
@@ -414,14 +415,18 @@ class MediaDataManager(
* This will make the player not active anymore, hiding it from QQS and Keyguard.
* @see MediaData.active
*/
- internal fun setTimedOut(token: String, timedOut: Boolean, forceUpdate: Boolean = false) {
- mediaEntries[token]?.let {
+ internal fun setTimedOut(key: String, timedOut: Boolean, forceUpdate: Boolean = false) {
+ mediaEntries[key]?.let {
if (it.active == !timedOut && !forceUpdate) {
+ if (it.resumption) {
+ if (DEBUG) Log.d(TAG, "timing out resume player $key")
+ dismissMediaData(key, 0L /* delay */)
+ }
return
}
it.active = !timedOut
- if (DEBUG) Log.d(TAG, "Updating $token timedOut: $timedOut")
- onMediaDataLoaded(token, token, it)
+ if (DEBUG) Log.d(TAG, "Updating $key timedOut: $timedOut")
+ onMediaDataLoaded(key, key, it)
}
}
@@ -879,12 +884,22 @@ class MediaDataManager(
target: SmartspaceTarget,
isActive: Boolean
): SmartspaceMediaData {
+ var dismissIntent: Intent? = null
+ if (target.baseAction != null && target.baseAction.extras != null) {
+ dismissIntent = target
+ .baseAction
+ .extras
+ .getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent
+ }
packageName(target)?.let {
return SmartspaceMediaData(target.smartspaceTargetId, isActive, true, it,
- target.baseAction, target.iconGrid, 0)
+ target.baseAction, target.iconGrid,
+ dismissIntent, 0)
}
return EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = target.smartspaceTargetId, isActive = isActive)
+ .copy(targetId = target.smartspaceTargetId,
+ isActive = isActive,
+ dismissIntent = dismissIntent)
}
private fun packageName(target: SmartspaceTarget): String? {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
index ab568c8c5a85..608c784f5d39 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaResumeListener.kt
@@ -35,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.tuner.TunerService
import com.android.systemui.util.Utils
+import com.android.systemui.util.time.SystemClock
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.concurrent.ConcurrentLinkedQueue
@@ -53,11 +54,13 @@ class MediaResumeListener @Inject constructor(
@Background private val backgroundExecutor: Executor,
private val tunerService: TunerService,
private val mediaBrowserFactory: ResumeMediaBrowserFactory,
- dumpManager: DumpManager
+ dumpManager: DumpManager,
+ private val systemClock: SystemClock
) : MediaDataManager.Listener, Dumpable {
private var useMediaResumption: Boolean = Utils.useMediaResumption(context)
- private val resumeComponents: ConcurrentLinkedQueue<ComponentName> = ConcurrentLinkedQueue()
+ private val resumeComponents: ConcurrentLinkedQueue<Pair<ComponentName, Long>> =
+ ConcurrentLinkedQueue()
private lateinit var mediaDataManager: MediaDataManager
@@ -131,14 +134,32 @@ class MediaResumeListener @Inject constructor(
val listString = prefs.getString(MEDIA_PREFERENCE_KEY + currentUserId, null)
val components = listString?.split(ResumeMediaBrowser.DELIMITER.toRegex())
?.dropLastWhile { it.isEmpty() }
+ var needsUpdate = false
components?.forEach {
val info = it.split("/")
val packageName = info[0]
val className = info[1]
val component = ComponentName(packageName, className)
- resumeComponents.add(component)
+
+ val lastPlayed = if (info.size == 3) {
+ try {
+ info[2].toLong()
+ } catch (e: NumberFormatException) {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ } else {
+ needsUpdate = true
+ systemClock.currentTimeMillis()
+ }
+ resumeComponents.add(component to lastPlayed)
}
Log.d(TAG, "loaded resume components ${resumeComponents.toArray().contentToString()}")
+
+ if (needsUpdate) {
+ // Save any missing times that we had to fill in
+ writeSharedPrefs()
+ }
}
/**
@@ -149,9 +170,12 @@ class MediaResumeListener @Inject constructor(
return
}
+ val now = systemClock.currentTimeMillis()
resumeComponents.forEach {
- val browser = mediaBrowserFactory.create(mediaBrowserCallback, it)
- browser.findRecentMedia()
+ if (now.minus(it.second) <= RESUME_MEDIA_TIMEOUT) {
+ val browser = mediaBrowserFactory.create(mediaBrowserCallback, it.first)
+ browser.findRecentMedia()
+ }
}
}
@@ -234,18 +258,24 @@ class MediaResumeListener @Inject constructor(
*/
private fun updateResumptionList(componentName: ComponentName) {
// Remove if exists
- resumeComponents.remove(componentName)
+ resumeComponents.remove(resumeComponents.find { it.first.equals(componentName) })
// Insert at front of queue
- resumeComponents.add(componentName)
+ val currentTime = systemClock.currentTimeMillis()
+ resumeComponents.add(componentName to currentTime)
// Remove old components if over the limit
if (resumeComponents.size > ResumeMediaBrowser.MAX_RESUMPTION_CONTROLS) {
resumeComponents.remove()
}
- // Save changes
+ writeSharedPrefs()
+ }
+
+ private fun writeSharedPrefs() {
val sb = StringBuilder()
resumeComponents.forEach {
- sb.append(it.flattenToString())
+ sb.append(it.first.flattenToString())
+ sb.append("/")
+ sb.append(it.second)
sb.append(ResumeMediaBrowser.DELIMITER)
}
val prefs = context.getSharedPreferences(MEDIA_PREFERENCES, Context.MODE_PRIVATE)
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 9a3919326cbd..6f047712c954 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -20,6 +20,7 @@ import android.media.session.MediaController
import android.media.session.PlaybackState
import android.os.SystemProperties
import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
@@ -29,9 +30,15 @@ import javax.inject.Inject
private const val DEBUG = true
private const val TAG = "MediaTimeout"
-private val PAUSED_MEDIA_TIMEOUT = SystemProperties
+
+@VisibleForTesting
+val PAUSED_MEDIA_TIMEOUT = SystemProperties
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
+@VisibleForTesting
+val RESUME_MEDIA_TIMEOUT = SystemProperties
+ .getLong("debug.sysui.media_timeout_resume", TimeUnit.DAYS.toMillis(3))
+
/**
* Controller responsible for keeping track of playback states and expiring inactive streams.
*/
@@ -45,8 +52,9 @@ class MediaTimeoutListener @Inject constructor(
/**
* Callback representing that a media object is now expired:
- * @param token Media session unique identifier
- * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT}
+ * @param key Media control unique identifier
+ * @param timedOut True when expired for {@code PAUSED_MEDIA_TIMEOUT} for active media,
+ * or {@code RESUME_MEDIA_TIMEOUT} for resume media
*/
lateinit var timeoutCallback: (String, Boolean) -> Unit
@@ -122,6 +130,7 @@ class MediaTimeoutListener @Inject constructor(
var timedOut = false
var playing: Boolean? = null
+ var resumption: Boolean? = null
var destroyed = false
var mediaData: MediaData = data
@@ -159,12 +168,19 @@ class MediaTimeoutListener @Inject constructor(
}
override fun onSessionDestroyed() {
- // If the session is destroyed, the controller is no longer valid, and we will need to
- // recreate it if this key is updated later
if (DEBUG) {
Log.d(TAG, "Session destroyed for $key")
}
- destroy()
+
+ if (resumption == true) {
+ // Some apps create a session when MBS is queried. We should unregister the
+ // controller since it will no longer be valid, but don't cancel the timeout
+ mediaController?.unregisterCallback(this)
+ } else {
+ // For active controls, if the session is destroyed, clean up everything since we
+ // will need to recreate it if this key is updated later
+ destroy()
+ }
}
private fun processState(state: PlaybackState?, dispatchEvents: Boolean) {
@@ -173,20 +189,28 @@ class MediaTimeoutListener @Inject constructor(
}
val isPlaying = state != null && isPlayingState(state.state)
- if (playing == isPlaying && playing != null) {
+ val resumptionChanged = resumption != mediaData.resumption
+ if (playing == isPlaying && playing != null && !resumptionChanged) {
return
}
playing = isPlaying
+ resumption = mediaData.resumption
if (!isPlaying) {
if (DEBUG) {
- Log.v(TAG, "schedule timeout for $key")
+ Log.v(TAG, "schedule timeout for $key playing $isPlaying, $resumption")
}
- if (cancellation != null) {
+ if (cancellation != null && !resumptionChanged) {
+ // if the media changed resume state, we'll need to adjust the timeout length
if (DEBUG) Log.d(TAG, "cancellation already exists, continuing.")
return
}
- expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
+ expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption")
+ val timeout = if (mediaData.resumption) {
+ RESUME_MEDIA_TIMEOUT
+ } else {
+ PAUSED_MEDIA_TIMEOUT
+ }
cancellation = mainExecutor.executeDelayed({
cancellation = null
if (DEBUG) {
@@ -195,7 +219,7 @@ class MediaTimeoutListener @Inject constructor(
timedOut = true
// this event is async, so it's safe even when `dispatchEvents` is false
timeoutCallback(key, timedOut)
- }, PAUSED_MEDIA_TIMEOUT)
+ }, timeout)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
timedOut = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 791f59d3ae13..35603b6ef6cc 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -43,7 +43,6 @@ class PlayerViewHolder private constructor(itemView: View) {
val seamless = itemView.requireViewById<ViewGroup>(R.id.media_seamless)
val seamlessIcon = itemView.requireViewById<ImageView>(R.id.media_seamless_image)
val seamlessText = itemView.requireViewById<TextView>(R.id.media_seamless_text)
- val seamlessFallback = itemView.requireViewById<ImageView>(R.id.media_seamless_fallback)
// Seek bar
val seekBar = itemView.requireViewById<SeekBar>(R.id.media_progress_bar)
@@ -124,7 +123,6 @@ class PlayerViewHolder private constructor(itemView: View) {
R.id.header_title,
R.id.header_artist,
R.id.media_seamless,
- R.id.media_seamless_fallback,
R.id.notification_media_progress_time,
R.id.media_progress_bar,
R.id.action0,
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
index 9ac128920edb..61fdefd4579f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaData.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceAction
+import android.content.Intent
/** State of a Smartspace media recommendations view. */
data class SmartspaceMediaData(
@@ -45,6 +46,10 @@ data class SmartspaceMediaData(
*/
val recommendations: List<SmartspaceAction>,
/**
+ * Intent for the user's initiated dismissal.
+ */
+ val dismissIntent: Intent?,
+ /**
* View's background color.
*/
val backgroundColor: Int
diff --git a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
index b6c2ef1db82a..140a1fef93f7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SmartspaceMediaDataProvider.kt
@@ -1,10 +1,13 @@
package com.android.systemui.media
import android.app.smartspace.SmartspaceTarget
+import android.util.Log
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import javax.inject.Inject
+private const val TAG = "SsMediaDataProvider"
+
/** Provides SmartspaceTargets of media types for SystemUI media control. */
class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin {
@@ -31,6 +34,10 @@ class SmartspaceMediaDataProvider @Inject constructor() : BcSmartspaceDataPlugin
}
}
+ if (!mediaTargets.isEmpty()) {
+ Log.d(TAG, "Forwarding Smartspace media updates $mediaTargets")
+ }
+
smartspaceMediaTargets = mediaTargets
smartspaceMediaTargetListeners.forEach {
it.onSmartspaceTargetsUpdated(smartspaceMediaTargets)
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index cb11454e44ee..8e6eb02bb6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -410,6 +410,12 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange, Uri uri) {
+ // TODO(b/198002034): Content observers currently can still be called back after being
+ // unregistered, and in this case we can ignore the change if the nav bar has been
+ // destroyed already
+ if (mNavigationBarView == null) {
+ return;
+ }
updateAssistantEntrypoints();
}
};
@@ -601,6 +607,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
public void destroyView() {
+ setAutoHideController(/* autoHideController */ null);
mCommandQueue.removeCallback(this);
mContext.getSystemService(WindowManager.class).removeViewImmediate(
mNavigationBarView.getRootView());
@@ -651,7 +658,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
if (mIsOnDefaultDisplay) {
final RotationButtonController rotationButtonController =
mNavigationBarView.getRotationButtonController();
- rotationButtonController.addRotationCallback(mRotationWatcher);
+ rotationButtonController.setRotationCallback(mRotationWatcher);
// Reset user rotation pref to match that of the WindowManager if starting in locked
// mode. This will automatically happen when switching from auto-rotate to locked mode.
@@ -690,6 +697,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Override
public void onViewDetachedFromWindow(View v) {
+ final RotationButtonController rotationButtonController =
+ mNavigationBarView.getRotationButtonController();
+ rotationButtonController.setRotationCallback(null);
mNavigationBarView.getBarTransitions().destroy();
mNavigationBarView.getLightTransitionsController().destroy(mContext);
mOverviewProxyService.removeCallback(mOverviewProxyListener);
@@ -937,6 +947,11 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
@Override
public void onRotationProposal(final int rotation, boolean isValid) {
+ // The CommandQueue callbacks are added when the view is created to ensure we track other
+ // states, but until the view is attached (at the next traversal), the view's display is
+ // not valid. Just ignore the rotation in this case.
+ if (!mNavigationBarView.isAttachedToWindow()) return;
+
final int winRotation = mNavigationBarView.getDisplay().getRotation();
final boolean rotateSuggestionsDisabled = RotationButtonController
.hasDisable2RotateSuggestionFlag(mDisabledFlags2);
@@ -1516,7 +1531,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener,
}
/** Sets {@link AutoHideController} to the navigation bar. */
- public void setAutoHideController(AutoHideController autoHideController) {
+ private void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
if (mAutoHideController != null) {
mAutoHideController.setNavigationBar(mAutoHideUiElement);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index b9e9240b354a..1628c71ae005 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -395,7 +395,6 @@ public class NavigationBarController implements Callbacks,
void removeNavigationBar(int displayId) {
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
- navBar.setAutoHideController(/* autoHideController */ null);
navBar.destroyView();
mNavigationBars.remove(displayId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 61e803312b55..70c21e43b79a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -101,10 +101,7 @@ import java.util.function.Consumer;
public class NavigationBarView extends FrameLayout implements
NavigationModeController.ModeChangedListener {
final static boolean DEBUG = false;
- final static String TAG = "StatusBar/NavBarView";
-
- // slippery nav bar when everything is disabled, e.g. during setup
- final static boolean SLIPPERY_WHEN_DISABLED = true;
+ final static String TAG = "NavBarView";
final static boolean ALTERNATE_CAR_MODE_UI = false;
private final RegionSamplingHelper mRegionSamplingHelper;
@@ -274,7 +271,7 @@ public class NavigationBarView extends FrameLayout implements
};
private final Consumer<Boolean> mRotationButtonListener = (visible) -> {
- if (visible) {
+ if (visible && mAutoHideController != null) {
// If the button will actually become visible and the navbar is about to hide,
// tell the statusbar to keep it around for longer
mAutoHideController.touchAutoHide();
@@ -373,6 +370,12 @@ public class NavigationBarView extends FrameLayout implements
}
}
+ @Override
+ protected boolean onSetAlpha(int alpha) {
+ Log.e(TAG, "onSetAlpha", new Throwable());
+ return super.onSetAlpha(alpha);
+ }
+
public void setAutoHideController(AutoHideController autoHideController) {
mAutoHideController = autoHideController;
}
@@ -1313,6 +1316,7 @@ public class NavigationBarView extends FrameLayout implements
dumpButton(pw, "back", getBackButton());
dumpButton(pw, "home", getHomeButton());
+ dumpButton(pw, "handle", getHomeHandle());
dumpButton(pw, "rcnt", getRecentsButton());
dumpButton(pw, "rota", getRotateSuggestionButton());
dumpButton(pw, "a11y", getAccessibilityButton());
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
index 649ac875b4c2..8ea9ae3c21c5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/RotationButtonController.java
@@ -180,7 +180,7 @@ public class RotationButtonController {
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
}
- void addRotationCallback(Consumer<Integer> watcher) {
+ void setRotationCallback(Consumer<Integer> watcher) {
mRotWatcherListener = watcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
index a626681c0b01..d0aa710ecea6 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt
@@ -29,6 +29,7 @@ import android.util.Log
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -67,6 +68,7 @@ class PrivacyDialogController(
private val privacyLogger: PrivacyLogger,
private val keyguardStateController: KeyguardStateController,
private val appOpsController: AppOpsController,
+ private val uiEventLogger: UiEventLogger,
@VisibleForTesting private val dialogProvider: DialogProvider
) {
@@ -81,7 +83,8 @@ class PrivacyDialogController(
@Main uiExecutor: Executor,
privacyLogger: PrivacyLogger,
keyguardStateController: KeyguardStateController,
- appOpsController: AppOpsController
+ appOpsController: AppOpsController,
+ uiEventLogger: UiEventLogger
) : this(
permissionManager,
packageManager,
@@ -93,6 +96,7 @@ class PrivacyDialogController(
privacyLogger,
keyguardStateController,
appOpsController,
+ uiEventLogger,
defaultDialogProvider
)
@@ -105,6 +109,7 @@ class PrivacyDialogController(
private val onDialogDismissed = object : PrivacyDialog.OnDialogDismissed {
override fun onDialogDismissed() {
privacyLogger.logPrivacyDialogDismissed()
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
dialog = null
}
}
@@ -114,6 +119,8 @@ class PrivacyDialogController(
val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId))
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ userId, packageName)
privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
if (!keyguardStateController.isUnlocked) {
// If we are locked, hide the dialog so the user can unlock
@@ -155,7 +162,7 @@ class PrivacyDialogController(
val items = usage.mapNotNull {
val type = filterType(permGroupToPrivacyType(it.permGroupName))
val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
- userInfo?.let { ui ->
+ if (userInfo != null || it.isPhoneCall) {
type?.let { t ->
// Only try to get the app name if we actually need it
val appName = if (it.isPhoneCall) {
@@ -171,10 +178,14 @@ class PrivacyDialogController(
it.attribution,
it.lastAccess,
it.isActive,
- ui.isManagedProfile,
+ // If there's no user info, we're in a phoneCall in secondary user
+ userInfo?.isManagedProfile ?: false,
it.isPhoneCall
)
}
+ } else {
+ // No matching user or phone call
+ null
}
}
uiExecutor.execute {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
new file mode 100644
index 000000000000..3ecc5a5e5b00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.privacy
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
+ PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
+
+ @UiEvent(doc = "Privacy dialog is dismissed by user.")
+ PRIVACY_DIALOG_DISMISSED(905);
+
+ override fun getId() = _id
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
deleted file mode 100644
index 38b20ee45946..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.HOTSPOT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
-import static com.android.systemui.statusbar.phone.AutoTileManager.NIGHT;
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
-
-import android.content.Context;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
-import com.android.systemui.util.UserAwareController;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-import javax.inject.Inject;
-
-public class AutoAddTracker implements UserAwareController {
-
- private static final String[][] CONVERT_PREFS = {
- {Key.QS_HOTSPOT_ADDED, HOTSPOT},
- {Key.QS_DATA_SAVER_ADDED, SAVER},
- {Key.QS_INVERT_COLORS_ADDED, INVERSION},
- {Key.QS_WORK_ADDED, WORK},
- {Key.QS_NIGHTDISPLAY_ADDED, NIGHT},
- };
-
- private final ArraySet<String> mAutoAdded;
- private final Context mContext;
- private int mUserId;
-
- public AutoAddTracker(Context context, int userId) {
- mContext = context;
- mUserId = userId;
- mAutoAdded = new ArraySet<>(getAdded());
- }
-
- /**
- * Init method must be called after construction to start listening
- */
- public void initialize() {
- // TODO: remove migration code and shared preferences keys after P release
- if (mUserId == UserHandle.USER_SYSTEM) {
- for (String[] convertPref : CONVERT_PREFS) {
- if (Prefs.getBoolean(mContext, convertPref[0], false)) {
- setTileAdded(convertPref[1]);
- Prefs.remove(mContext, convertPref[0]);
- }
- }
- }
- mContext.getContentResolver().registerContentObserver(
- Secure.getUriFor(Secure.QS_AUTO_ADDED_TILES), false, mObserver,
- UserHandle.USER_ALL);
- }
-
- @Override
- public void changeUser(UserHandle newUser) {
- if (newUser.getIdentifier() == mUserId) {
- return;
- }
- mUserId = newUser.getIdentifier();
- mAutoAdded.clear();
- mAutoAdded.addAll(getAdded());
- }
-
- @Override
- public int getCurrentUserId() {
- return mUserId;
- }
-
- public boolean isAdded(String tile) {
- return mAutoAdded.contains(tile);
- }
-
- public void setTileAdded(String tile) {
- if (mAutoAdded.add(tile)) {
- saveTiles();
- }
- }
-
- public void setTileRemoved(String tile) {
- if (mAutoAdded.remove(tile)) {
- saveTiles();
- }
- }
-
- public void destroy() {
- mContext.getContentResolver().unregisterContentObserver(mObserver);
- }
-
- private void saveTiles() {
- Secure.putStringForUser(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES,
- TextUtils.join(",", mAutoAdded), mUserId);
- }
-
- private Collection<String> getAdded() {
- String current = Secure.getStringForUser(mContext.getContentResolver(),
- Secure.QS_AUTO_ADDED_TILES, mUserId);
- if (current == null) {
- return Collections.emptyList();
- }
- return Arrays.asList(current.split(","));
- }
-
- @VisibleForTesting
- protected final ContentObserver mObserver = new ContentObserver(new Handler()) {
- @Override
- public void onChange(boolean selfChange) {
- mAutoAdded.clear();
- mAutoAdded.addAll(getAdded());
- }
- };
-
- public static class Builder {
- private final Context mContext;
- private int mUserId;
-
- @Inject
- public Builder(Context context) {
- mContext = context;
- }
-
- public Builder setUserId(int userId) {
- mUserId = userId;
- return this;
- }
-
- public AutoAddTracker build() {
- return new AutoAddTracker(mContext, mUserId);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
new file mode 100644
index 000000000000..7ffa9d931ff0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.ArraySet
+import android.util.Log
+import androidx.annotation.GuardedBy
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.UserAwareController
+import com.android.systemui.util.settings.SecureSettings
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+private const val TAG = "AutoAddTracker"
+
+/**
+ * Class to track tiles that have been auto-added
+ *
+ * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
+ *
+ * It also handles restore gracefully.
+ */
+class AutoAddTracker @VisibleForTesting constructor(
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val qsHost: QSHost,
+ private val dumpManager: DumpManager,
+ private val mainHandler: Handler?,
+ private val backgroundExecutor: Executor,
+ private var userId: Int
+) : UserAwareController, Dumpable {
+
+ companion object {
+ private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
+ }
+
+ @GuardedBy("autoAdded")
+ private val autoAdded = ArraySet<String>()
+ private var restoredTiles: Set<String>? = null
+
+ override val currentUserId: Int
+ get() = userId
+
+ private val contentObserver = object : ContentObserver(mainHandler) {
+ override fun onChange(
+ selfChange: Boolean,
+ uris: Collection<Uri>,
+ flags: Int,
+ _userId: Int
+ ) {
+ if (_userId != userId) {
+ // Ignore changes outside of our user. We'll load the correct value on user change
+ return
+ }
+ loadTiles()
+ }
+ }
+
+ private val restoreReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action != Intent.ACTION_SETTING_RESTORED) return
+ processRestoreIntent(intent)
+ }
+ }
+
+ private fun processRestoreIntent(intent: Intent) {
+ when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
+ Settings.Secure.QS_TILES -> {
+ restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ ?.split(",")
+ ?.toSet()
+ ?: run {
+ Log.w(TAG, "Null restored tiles for user $userId")
+ emptySet()
+ }
+ }
+ Settings.Secure.QS_AUTO_ADDED_TILES -> {
+ restoredTiles?.let { tiles ->
+ val restoredAutoAdded = intent
+ .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
+ ?.split(",")
+ ?: emptyList()
+ val autoAddedBeforeRestore = intent
+ .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)
+ ?.split(",")
+ ?: emptyList()
+
+ val tilesToRemove = restoredAutoAdded.filter { it !in tiles }
+ if (tilesToRemove.isNotEmpty()) {
+ qsHost.removeTiles(tilesToRemove)
+ }
+ val tiles = synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
+ getTilesFromListLocked()
+ }
+ saveTiles(tiles)
+ } ?: run {
+ Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
+ "${Settings.Secure.QS_TILES} for user $userId")
+ }
+ }
+ else -> {} // Do nothing for other Settings
+ }
+ }
+
+ /**
+ * Init method must be called after construction to start listening
+ */
+ fun initialize() {
+ dumpManager.registerDumpable(TAG, this)
+ loadTiles()
+ secureSettings.registerContentObserverForUser(
+ secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
+ contentObserver,
+ UserHandle.USER_ALL
+ )
+ registerBroadcastReceiver()
+ }
+
+ /**
+ * Unregister listeners, receivers and observers
+ */
+ fun destroy() {
+ dumpManager.unregisterDumpable(TAG)
+ secureSettings.unregisterContentObserver(contentObserver)
+ unregisterBroadcastReceiver()
+ }
+
+ private fun registerBroadcastReceiver() {
+ broadcastDispatcher.registerReceiver(
+ restoreReceiver,
+ FILTER,
+ backgroundExecutor,
+ UserHandle.of(userId)
+ )
+ }
+
+ private fun unregisterBroadcastReceiver() {
+ broadcastDispatcher.unregisterReceiver(restoreReceiver)
+ }
+
+ override fun changeUser(newUser: UserHandle) {
+ if (newUser.identifier == userId) return
+ unregisterBroadcastReceiver()
+ userId = newUser.identifier
+ restoredTiles = null
+ loadTiles()
+ registerBroadcastReceiver()
+ }
+
+ /**
+ * Returns `true` if the tile has been auto-added before
+ */
+ fun isAdded(tile: String): Boolean {
+ return synchronized(autoAdded) {
+ tile in autoAdded
+ }
+ }
+
+ /**
+ * Sets a tile as auto-added.
+ *
+ * From here on, [isAdded] will return true for that tile.
+ */
+ fun setTileAdded(tile: String) {
+ val tiles = synchronized(autoAdded) {
+ if (autoAdded.add(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
+ }
+ tiles?.let { saveTiles(it) }
+ }
+
+ /**
+ * Removes a tile from the list of auto-added.
+ *
+ * This allows for this tile to be auto-added again in the future.
+ */
+ fun setTileRemoved(tile: String) {
+ val tiles = synchronized(autoAdded) {
+ if (autoAdded.remove(tile)) {
+ getTilesFromListLocked()
+ } else {
+ null
+ }
+ }
+ tiles?.let { saveTiles(it) }
+ }
+
+ private fun getTilesFromListLocked(): String {
+ return TextUtils.join(",", autoAdded)
+ }
+
+ private fun saveTiles(tiles: String) {
+ secureSettings.putStringForUser(
+ Settings.Secure.QS_AUTO_ADDED_TILES,
+ tiles,
+ /* tag */ null,
+ /* makeDefault */ false,
+ userId,
+ /* overrideableByRestore */ true
+ )
+ }
+
+ private fun loadTiles() {
+ synchronized(autoAdded) {
+ autoAdded.clear()
+ autoAdded.addAll(getAdded())
+ }
+ }
+
+ private fun getAdded(): Collection<String> {
+ val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
+ return current?.split(",") ?: emptySet()
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("Current user: $userId")
+ pw.println("Added tiles: $autoAdded")
+ }
+
+ @SysUISingleton
+ class Builder @Inject constructor(
+ private val secureSettings: SecureSettings,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val qsHost: QSHost,
+ private val dumpManager: DumpManager,
+ @Main private val handler: Handler,
+ @Background private val executor: Executor
+ ) {
+ private var userId: Int = 0
+
+ fun setUserId(_userId: Int): Builder {
+ userId = _userId
+ return this
+ }
+
+ fun build(): AutoAddTracker {
+ return AutoAddTracker(
+ secureSettings,
+ broadcastDispatcher,
+ qsHost,
+ dumpManager,
+ handler,
+ executor,
+ userId
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 59e5eb8d6ac8..6f12e467291a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,6 +23,7 @@ import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Point;
+import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsets;
@@ -289,6 +290,16 @@ public class QSContainerImpl extends FrameLayout implements Dumpable {
}
}
+ @Override
+ protected boolean isTransformedTouchPointInView(float x, float y,
+ View child, PointF outLocalPoint) {
+ // Prevent touches outside the clipped area from propagating to a child in that area.
+ if (mClippingEnabled && y + getTranslationY() > mFancyClippingTop) {
+ return false;
+ }
+ return super.isTransformedTouchPointInView(x, y, child, outLocalPoint);
+ }
+
private void updateClippingPath() {
mFancyClippingPath.reset();
if (!mClippingEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index 000fd1c4bd2e..9f585bdfaeb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -37,6 +37,7 @@ public interface QSHost {
void removeCallback(Callback callback);
TileServices getTileServices();
void removeTile(String tileSpec);
+ void removeTiles(Collection<String> specs);
void unmarkTileAsAutoAdded(String tileSpec);
int indexOf(String tileSpec);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index 4739a3f4c7d6..08cb4a9d44b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -25,6 +25,7 @@ import android.content.ComponentName;
import android.content.res.Configuration;
import android.metrics.LogMaker;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
@@ -80,7 +81,8 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private final QSHost.Callback mQSHostCallback = this::setTiles;
- private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
+ @VisibleForTesting
+ protected final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
new QSPanel.OnConfigurationChangedListener() {
@Override
public void onConfigurationChange(Configuration newConfig) {
@@ -156,6 +158,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
mHost.addCallback(mQSHostCallback);
setTiles();
+ mLastOrientation = getResources().getConfiguration().orientation;
switchTileLayout(true);
mDumpManager.registerDumpable(mView.getDumpableTag(), this);
@@ -356,8 +359,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
return false;
}
return mUsingMediaPlayer && mMediaHost.getVisible()
- && getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
+ && mLastOrientation == Configuration.ORIENTATION_LANDSCAPE;
}
private void logTiles() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 4a75810f86db..e60fb494e82b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -351,6 +351,17 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
}
+ /**
+ * Remove many tiles at once.
+ *
+ * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+ * multiple times).
+ */
+ @Override
+ public void removeTiles(Collection<String> specs) {
+ changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+ }
+
@Override
public void unmarkTileAsAutoAdded(String spec) {
if (mAutoTiles != null) mAutoTiles.unmarkTileAsAutoAdded(spec);
@@ -372,6 +383,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
+ if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
changeTileSpecs(tileSpecs -> {
if (tileSpecs.contains(spec)) return false;
@@ -386,6 +398,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
void saveTilesToSettings(List<String> tileSpecs) {
+ if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index 77906abce625..84b961e7c48a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconMa
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateView;
import java.util.List;
@@ -62,11 +63,14 @@ public class QuickStatusBarHeader extends FrameLayout {
protected QuickQSPanel mHeaderQsPanel;
private View mDatePrivacyView;
private View mDateView;
+ // DateView next to clock. Visible on QQS
+ private VariableDateView mClockDateView;
private View mSecurityHeaderView;
private View mClockIconsView;
private View mContainer;
private View mQSCarriers;
+ private ViewGroup mClockContainer;
private Clock mClockView;
private Space mDatePrivacySeparator;
private View mClockIconsSeparator;
@@ -86,7 +90,6 @@ public class QuickStatusBarHeader extends FrameLayout {
private int mWaterfallTopInset;
private int mCutOutPaddingLeft;
private int mCutOutPaddingRight;
- private float mViewAlpha = 1.0f;
private float mKeyguardExpansionFraction;
private int mTextColorPrimary = Color.TRANSPARENT;
private int mTopViewMeasureHeight;
@@ -123,18 +126,24 @@ public class QuickStatusBarHeader extends FrameLayout {
mIconContainer = findViewById(R.id.statusIcons);
mPrivacyChip = findViewById(R.id.privacy_chip);
mDateView = findViewById(R.id.date);
+ mClockDateView = findViewById(R.id.date_clock);
mSecurityHeaderView = findViewById(R.id.header_text_container);
mClockIconsSeparator = findViewById(R.id.separator);
mRightLayout = findViewById(R.id.rightLayout);
mDateContainer = findViewById(R.id.date_container);
mPrivacyContainer = findViewById(R.id.privacy_container);
+ mClockContainer = findViewById(R.id.clock_container);
mClockView = findViewById(R.id.clock);
mDatePrivacySeparator = findViewById(R.id.space);
// Tint for the battery icons are handled in setupHost()
mBatteryRemainingIcon = findViewById(R.id.batteryRemainingIcon);
updateResources();
+ Configuration config = mContext.getResources().getConfiguration();
+ setDatePrivacyContainersWidth(config.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setSecurityHeaderContainerVisibility(
+ config.orientation == Configuration.ORIENTATION_LANDSCAPE);
// Don't need to worry about tuner settings for this icon
mBatteryRemainingIcon.setIgnoreTunerUpdates(true);
@@ -177,7 +186,7 @@ public class QuickStatusBarHeader extends FrameLayout {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mDatePrivacyView.getMeasuredHeight() != mTopViewMeasureHeight) {
mTopViewMeasureHeight = mDatePrivacyView.getMeasuredHeight();
- updateAnimators();
+ post(this::updateAnimators);
}
}
@@ -186,6 +195,8 @@ public class QuickStatusBarHeader extends FrameLayout {
super.onConfigurationChanged(newConfig);
updateResources();
setDatePrivacyContainersWidth(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
+ setSecurityHeaderContainerVisibility(
+ newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
}
@Override
@@ -206,6 +217,10 @@ public class QuickStatusBarHeader extends FrameLayout {
mPrivacyContainer.setLayoutParams(lp);
}
+ private void setSecurityHeaderContainerVisibility(boolean landscape) {
+ mSecurityHeaderView.setVisibility(landscape ? VISIBLE : GONE);
+ }
+
private void updateBatteryMode() {
if (mConfigShowBatteryEstimate && !mHasCenterCutout) {
mBatteryRemainingIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE);
@@ -262,6 +277,25 @@ public class QuickStatusBarHeader extends FrameLayout {
updateBatteryMode();
updateHeadersPadding();
updateAnimators();
+
+ updateClockDatePadding();
+ }
+
+ private void updateClockDatePadding() {
+ int startPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding);
+ int endPadding = mContext.getResources()
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding);
+ mClockView.setPaddingRelative(
+ startPadding,
+ mClockView.getPaddingTop(),
+ endPadding,
+ mClockView.getPaddingBottom()
+ );
+
+ MarginLayoutParams lp = (MarginLayoutParams) mClockDateView.getLayoutParams();
+ lp.setMarginStart(endPadding);
+ mClockDateView.setLayoutParams(lp);
}
private void updateAnimators() {
@@ -280,7 +314,8 @@ public class QuickStatusBarHeader extends FrameLayout {
TouchAnimator.Builder builder = new TouchAnimator.Builder()
.addFloat(mSecurityHeaderView, "alpha", 0, 1)
// These views appear on expanding down
- .addFloat(mClockView, "alpha", 0, 1)
+ .addFloat(mDateView, "alpha", 0, 0, 1)
+ .addFloat(mClockDateView, "alpha", 1, 0, 0)
.addFloat(mQSCarriers, "alpha", 0, 1)
.setListener(new TouchAnimator.ListenerAdapter() {
@Override
@@ -289,10 +324,14 @@ public class QuickStatusBarHeader extends FrameLayout {
if (!mIsSingleCarrier) {
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
}
+ // Make it gone so there's enough room for carrier names
+ mClockDateView.setVisibility(View.GONE);
}
@Override
public void onAnimationStarted() {
+ mClockDateView.setVisibility(View.VISIBLE);
+ mClockDateView.setFreezeSwitching(true);
setSeparatorVisibility(false);
if (!mIsSingleCarrier) {
mIconContainer.addIgnoredSlots(mRssiIgnoredSlots);
@@ -302,6 +341,8 @@ public class QuickStatusBarHeader extends FrameLayout {
@Override
public void onAnimationAtStart() {
super.onAnimationAtStart();
+ mClockDateView.setFreezeSwitching(false);
+ mClockDateView.setVisibility(View.VISIBLE);
setSeparatorVisibility(mShowClockIconsSeparator);
// In QQS we never ignore RSSI.
mIconContainer.removeIgnoredSlots(mRssiIgnoredSlots);
@@ -434,10 +475,11 @@ public class QuickStatusBarHeader extends FrameLayout {
mClockIconsSeparator.setVisibility(visible ? View.VISIBLE : View.GONE);
mQSCarriers.setVisibility(visible ? View.GONE : View.VISIBLE);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mClockView.getLayoutParams();
+ LinearLayout.LayoutParams lp =
+ (LinearLayout.LayoutParams) mClockContainer.getLayoutParams();
lp.width = visible ? 0 : WRAP_CONTENT;
lp.weight = visible ? 1f : 0f;
- mClockView.setLayoutParams(lp);
+ mClockContainer.setLayoutParams(lp);
lp = (LinearLayout.LayoutParams) mRightLayout.getLayoutParams();
lp.width = visible ? 0 : WRAP_CONTENT;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index da75c9e45c54..18d6e646b007 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
+import com.android.systemui.statusbar.policy.VariableDateViewController;
import com.android.systemui.util.ViewController;
import java.util.List;
@@ -71,6 +72,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final QSExpansionPathInterpolator mQSExpansionPathInterpolator;
private final FeatureFlags mFeatureFlags;
+ private final VariableDateViewController mVariableDateViewControllerDateView;
+ private final VariableDateViewController mVariableDateViewControllerClockDateView;
+
private boolean mListening;
private boolean mMicCameraIndicatorsEnabled;
private boolean mLocationIndicatorsEnabled;
@@ -134,7 +138,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
SysuiColorExtractor colorExtractor,
PrivacyDialogController privacyDialogController,
QSExpansionPathInterpolator qsExpansionPathInterpolator,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ VariableDateViewController.Factory variableDateViewControllerFactory) {
super(view);
mPrivacyItemController = privacyItemController;
mActivityStarter = activityStarter;
@@ -154,6 +159,12 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
mClockView = mView.findViewById(R.id.clock);
mIconContainer = mView.findViewById(R.id.statusIcons);
+ mVariableDateViewControllerDateView = variableDateViewControllerFactory.create(
+ mView.requireViewById(R.id.date)
+ );
+ mVariableDateViewControllerClockDateView = variableDateViewControllerFactory.create(
+ mView.requireViewById(R.id.date_clock)
+ );
mIconManager = new StatusBarIconController.TintedIconManager(mIconContainer, featureFlags);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
@@ -205,6 +216,9 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mView.onAttach(mIconManager, mQSExpansionPathInterpolator, rssiIgnoredSlots);
mDemoModeController.addCallback(mDemoModeReceiver);
+
+ mVariableDateViewControllerDateView.init();
+ mVariableDateViewControllerClockDateView.init();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 2e771d6fb669..b1cd03c4a2f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -116,6 +116,9 @@ public class QSIconViewImpl extends QSIconView {
: icon.getInvisibleDrawable(mContext) : null;
int padding = icon != null ? icon.getPadding() : 0;
if (d != null) {
+ if (d.getConstantState() != null) {
+ d = d.getConstantState().newDrawable();
+ }
d.setAutoMirrored(false);
d.setLayoutDirection(getLayoutDirection());
}
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 73d13700d61b..14e0f707d3b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt
@@ -54,7 +54,7 @@ class AlarmTile @Inject constructor(
private var lastAlarmInfo: AlarmManager.AlarmClockInfo? = null
private val icon = ResourceIcon.get(R.drawable.ic_alarm)
@VisibleForTesting
- internal val defaultIntent = Intent(AlarmClock.ACTION_SET_ALARM)
+ internal val defaultIntent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
private val callback = NextAlarmController.NextAlarmChangeCallback { nextAlarm ->
lastAlarmInfo = nextAlarm
refreshState()
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 4b13015361cc..04f089d31664 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -150,11 +150,7 @@ public class CastTile extends QSTileImpl<BooleanState> {
}
List<CastDevice> activeDevices = getActiveDevices();
- // We want to pop up the media route selection dialog if we either have no active devices
- // (neither routes nor projection), or if we have an active route. In other cases, we assume
- // that a projection is active. This is messy, but this tile never correctly handled the
- // case where multiple devices were active :-/.
- if (activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo)) {
+ if (willPopDetail()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
showDetail(true);
});
@@ -163,6 +159,15 @@ public class CastTile extends QSTileImpl<BooleanState> {
}
}
+ // We want to pop up the media route selection dialog if we either have no active devices
+ // (neither routes nor projection), or if we have an active route. In other cases, we assume
+ // that a projection is active. This is messy, but this tile never correctly handled the
+ // case where multiple devices were active :-/.
+ private boolean willPopDetail() {
+ List<CastDevice> activeDevices = getActiveDevices();
+ return activeDevices.isEmpty() || (activeDevices.get(0).tag instanceof RouteInfo);
+ }
+
private List<CastDevice> getActiveDevices() {
ArrayList<CastDevice> activeDevices = new ArrayList<>();
for (CastDevice device : mController.getCastDevices()) {
@@ -234,10 +239,12 @@ public class CastTile extends QSTileImpl<BooleanState> {
state.contentDescription = state.contentDescription + ","
+ mContext.getString(R.string.accessibility_quick_settings_open_details);
state.expandedAccessibilityClassName = Button.class.getName();
+ state.forceExpandIcon = willPopDetail();
} else {
state.state = Tile.STATE_UNAVAILABLE;
String noWifi = mContext.getString(R.string.quick_settings_cast_no_wifi);
state.secondaryLabel = noWifi;
+ state.forceExpandIcon = false;
}
state.stateDescription = state.stateDescription + ", " + state.secondaryLabel;
mDetailAdapter.updateItems(devices);
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 34f2b6331678..87edc2cf8bac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -47,6 +47,7 @@ import javax.inject.Inject;
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTileImpl<BooleanState> {
+
private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot);
private final HotspotController mHotspotController;
@@ -98,7 +99,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> {
@Override
public Intent getLongClickIntent() {
- return new Intent(Settings.ACTION_TETHER_SETTINGS);
+ return new Intent(Settings.ACTION_WIFI_TETHER_SETTING);
}
@Override
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 7cb1421e3f0f..cc9e7485dcff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -51,7 +51,9 @@ import com.android.systemui.qs.AlphaControlledSignalTileView;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.NetworkController.MobileDataIndicators;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
@@ -66,15 +68,16 @@ import javax.inject.Inject;
/** Quick settings tile: Internet **/
public class InternetTile extends QSTileImpl<SignalState> {
private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS);
- private static final Intent INTERNET_PANEL =
- new Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
protected final NetworkController mController;
+ private final AccessPointController mAccessPointController;
private final DataUsageController mDataController;
// The last updated tile state, 0: mobile, 1: wifi, 2: ethernet.
private int mLastTileState = -1;
protected final InternetSignalCallback mSignalCallback = new InternetSignalCallback();
+ private final InternetDialogFactory mInternetDialogFactory;
+ final Handler mHandler;
@Inject
public InternetTile(
@@ -86,11 +89,16 @@ public class InternetTile extends QSTileImpl<SignalState> {
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- NetworkController networkController
+ NetworkController networkController,
+ AccessPointController accessPointController,
+ InternetDialogFactory internetDialogFactory
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
+ mInternetDialogFactory = internetDialogFactory;
+ mHandler = mainHandler;
mController = networkController;
+ mAccessPointController = accessPointController;
mDataController = mController.getMobileDataController();
mController.observe(getLifecycle(), mSignalCallback);
}
@@ -114,7 +122,9 @@ public class InternetTile extends QSTileImpl<SignalState> {
@Override
protected void handleClick(@Nullable View view) {
- mActivityStarter.postStartActivityDismissingKeyguard(INTERNET_PANEL, 0);
+ mHandler.post(() -> mInternetDialogFactory.create(true,
+ mAccessPointController.canConfigMobileData(),
+ mAccessPointController.canConfigWifi()));
}
@Override
@@ -429,7 +439,7 @@ public class InternetTile extends QSTileImpl<SignalState> {
state.icon = ResourceIcon.get(cb.mWifiSignalIconId);
}
} else if (cb.mNoDefaultNetwork) {
- if (cb.mNoNetworksAvailable) {
+ if (cb.mNoNetworksAvailable || !cb.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
@@ -489,7 +499,7 @@ public class InternetTile extends QSTileImpl<SignalState> {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.status_bar_airplane);
} else if (cb.mNoDefaultNetwork) {
- if (cb.mNoNetworksAvailable) {
+ if (cb.mNoNetworksAvailable || !mSignalCallback.mWifiInfo.mEnabled) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_no_internet_unavailable);
state.secondaryLabel = r.getString(R.string.quick_settings_networks_unavailable);
} else {
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 0bbb5bdd851a..4e936b8137af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -16,12 +16,19 @@
package com.android.systemui.qs.tiles;
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+
+import android.Manifest;
+import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
+import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.view.View;
import android.widget.Switch;
@@ -38,18 +45,25 @@ 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.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
/** Quick settings tile: Rotation **/
-public class RotationLockTile extends QSTileImpl<BooleanState> {
+public class RotationLockTile extends QSTileImpl<BooleanState> implements
+ BatteryController.BatteryStateChangeCallback {
private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
+ private final SensorPrivacyManager mPrivacyManager;
+ private final BatteryController mBatteryController;
+ private final SecureSetting mSetting;
@Inject
public RotationLockTile(
@@ -61,12 +75,38 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- RotationLockController rotationLockController
+ RotationLockController rotationLockController,
+ SensorPrivacyManager privacyManager,
+ BatteryController batteryController,
+ SecureSettings secureSettings
) {
super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
statusBarStateController, activityStarter, qsLogger);
mController = rotationLockController;
mController.observe(this, mCallback);
+ mPrivacyManager = privacyManager;
+ mBatteryController = batteryController;
+ mPrivacyManager
+ .addSensorPrivacyListener(CAMERA, (sensor, enabled) -> refreshState());
+ int currentUser = host.getUserContext().getUserId();
+ mSetting = new SecureSetting(
+ secureSettings,
+ mHandler,
+ Secure.CAMERA_AUTOROTATE,
+ currentUser
+ ) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ // mHandler is the background handler so calling this is OK
+ handleRefreshState(value);
+ }
+ };
+ mBatteryController.observe(getLifecycle(), this);
+ }
+
+ @Override
+ public void onPowerSaveChanged(boolean isPowerSave) {
+ refreshState();
}
@Override
@@ -95,14 +135,33 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean rotationLocked = mController.isRotationLocked();
+ final boolean powerSave = mBatteryController.isPowerSave();
+ final boolean cameraLocked = mPrivacyManager.isSensorPrivacyEnabled(
+ SensorPrivacyManager.Sensors.CAMERA);
+ final boolean cameraRotation =
+ !powerSave && !cameraLocked && hasSufficientPermission(mContext)
+ && mController.isCameraRotationEnabled();
state.value = !rotationLocked;
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
state.icon = mIcon;
state.contentDescription = getAccessibilityString(rotationLocked);
+ if (!rotationLocked && cameraRotation) {
+ state.secondaryLabel = mContext.getResources().getString(
+ R.string.rotation_lock_camera_rotation_on);
+ } else {
+ state.secondaryLabel = "";
+ }
+
state.expandedAccessibilityClassName = Switch.class.getName();
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
}
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ mSetting.setUserId(newUserId);
+ handleRefreshState(mSetting.getValue());
+ }
+
public static boolean isCurrentOrientationLockPortrait(RotationLockController controller,
Resources resources) {
int lockOrientation = controller.getRotationLockOrientation();
@@ -140,4 +199,11 @@ public class RotationLockTile extends QSTileImpl<BooleanState> {
refreshState(rotationLocked);
}
};
+
+ private boolean hasSufficientPermission(Context context) {
+ final PackageManager packageManager = context.getPackageManager();
+ final String rotationPackage = packageManager.getRotationResolverPackageName();
+ return rotationPackage != null && packageManager.checkPermission(
+ Manifest.permission.CAMERA, rotationPackage) == PackageManager.PERMISSION_GRANTED;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
new file mode 100644
index 000000000000..99eb5b6519bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Adapter for showing Wi-Fi networks.
+ */
+public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.InternetViewHolder> {
+
+ private static final String TAG = "InternetAdapter";
+ private static final String ACTION_WIFI_DIALOG = "com.android.settings.WIFI_DIALOG";
+ private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+ private static final String EXTRA_CONNECT_FOR_CALLER = "connect_for_caller";
+
+ private final InternetDialogController mInternetDialogController;
+ private List<WifiEntry> mWifiEntries;
+ private int mWifiEntriesCount;
+
+ protected View mHolderView;
+ protected Context mContext;
+
+ public InternetAdapter(InternetDialogController controller) {
+ mInternetDialogController = controller;
+ }
+
+ @Override
+ public InternetViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+ int viewType) {
+ mContext = viewGroup.getContext();
+ mHolderView = LayoutInflater.from(mContext).inflate(R.layout.internet_list_item,
+ viewGroup, false);
+ return new InternetViewHolder(mHolderView, mInternetDialogController);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull InternetViewHolder viewHolder, int position) {
+ if (mWifiEntries == null || position >= mWifiEntriesCount) {
+ return;
+ }
+ viewHolder.onBind(mWifiEntries.get(position));
+ }
+
+ /**
+ * Updates the Wi-Fi networks.
+ *
+ * @param wifiEntries the updated Wi-Fi entries.
+ * @param wifiEntriesCount the total number of Wi-Fi entries.
+ */
+ public void setWifiEntries(@Nullable List<WifiEntry> wifiEntries, int wifiEntriesCount) {
+ mWifiEntries = wifiEntries;
+ mWifiEntriesCount = wifiEntriesCount;
+ }
+
+ /**
+ * Gets the total number of Wi-Fi networks.
+ *
+ * @return The total number of Wi-Fi entries.
+ */
+ @Override
+ public int getItemCount() {
+ return mWifiEntriesCount;
+ }
+
+ /**
+ * ViewHolder for binding Wi-Fi view.
+ */
+ static class InternetViewHolder extends RecyclerView.ViewHolder {
+
+ final LinearLayout mContainerLayout;
+ final LinearLayout mWifiListLayout;
+ final LinearLayout mWifiNetworkLayout;
+ final ImageView mWifiIcon;
+ final TextView mWifiTitleText;
+ final TextView mWifiSummaryText;
+ final ImageView mWifiEndIcon;
+ final Context mContext;
+ final InternetDialogController mInternetDialogController;
+
+ @VisibleForTesting
+ protected WifiUtils.InternetIconInjector mWifiIconInjector;
+
+ InternetViewHolder(View view, InternetDialogController internetDialogController) {
+ super(view);
+ mContext = view.getContext();
+ mInternetDialogController = internetDialogController;
+ mContainerLayout = view.requireViewById(R.id.internet_container);
+ mWifiListLayout = view.requireViewById(R.id.wifi_list);
+ mWifiNetworkLayout = view.requireViewById(R.id.wifi_network_layout);
+ mWifiIcon = view.requireViewById(R.id.wifi_icon);
+ mWifiTitleText = view.requireViewById(R.id.wifi_title);
+ mWifiSummaryText = view.requireViewById(R.id.wifi_summary);
+ mWifiEndIcon = view.requireViewById(R.id.wifi_end_icon);
+ mWifiIconInjector = mInternetDialogController.getWifiIconInjector();
+ }
+
+ void onBind(@NonNull WifiEntry wifiEntry) {
+ mWifiIcon.setImageDrawable(getWifiDrawable(wifiEntry));
+ setWifiNetworkLayout(wifiEntry.getTitle(),
+ Html.fromHtml(wifiEntry.getSummary(false), Html.FROM_HTML_MODE_LEGACY));
+
+ final int connectedState = wifiEntry.getConnectedState();
+ final int security = wifiEntry.getSecurity();
+ updateEndIcon(connectedState, security);
+
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ mWifiListLayout.setOnClickListener(
+ v -> mInternetDialogController.launchWifiNetworkDetailsSetting(
+ wifiEntry.getKey()));
+ return;
+ }
+ mWifiListLayout.setOnClickListener(v -> {
+ if (wifiEntry.shouldEditBeforeConnect()) {
+ final Intent intent = new Intent(ACTION_WIFI_DIALOG);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, wifiEntry.getKey());
+ intent.putExtra(EXTRA_CONNECT_FOR_CALLER, false);
+ mContext.startActivity(intent);
+ }
+ mInternetDialogController.connect(wifiEntry);
+ });
+ }
+
+ void setWifiNetworkLayout(CharSequence title, CharSequence summary) {
+ mWifiTitleText.setText(title);
+ if (TextUtils.isEmpty(summary)) {
+ mWifiSummaryText.setVisibility(View.GONE);
+ return;
+ }
+ mWifiSummaryText.setVisibility(View.VISIBLE);
+ mWifiSummaryText.setText(summary);
+ }
+
+ Drawable getWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
+ final Drawable drawable = mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(),
+ wifiEntry.getLevel());
+ if (drawable == null) {
+ return null;
+ }
+ drawable.setTint(
+ Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorTertiary));
+ final AtomicReference<Drawable> shared = new AtomicReference<>();
+ shared.set(drawable);
+ return shared.get();
+ }
+
+ void updateEndIcon(int connectedState, int security) {
+ Drawable drawable = null;
+ if (connectedState != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
+ drawable = mContext.getDrawable(R.drawable.ic_settings_24dp);
+ } else if (security != WifiEntry.SECURITY_NONE && security != WifiEntry.SECURITY_OWE) {
+ drawable = mContext.getDrawable(R.drawable.ic_friction_lock_closed);
+ }
+ if (drawable == null) {
+ mWifiEndIcon.setVisibility(View.GONE);
+ return;
+ }
+ mWifiEndIcon.setVisibility(View.VISIBLE);
+ mWifiEndIcon.setImageDrawable(drawable);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
new file mode 100644
index 000000000000..dc54e1b52f2e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java
@@ -0,0 +1,597 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles.dialog;
+
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+
+import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.Html;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Prefs;
+import com.android.systemui.R;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.List;
+
+/**
+ * Dialog for showing mobile network, connected Wi-Fi network and Wi-Fi networks.
+ */
+@SysUISingleton
+public class InternetDialog extends SystemUIDialog implements
+ InternetDialogController.InternetDialogCallback, Window.Callback {
+ private static final String TAG = "InternetDialog";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final long PROGRESS_DELAY_MS = 2000L;
+
+ private final Handler mHandler;
+ private final LinearLayoutManager mLayoutManager;
+
+ @VisibleForTesting
+ protected InternetAdapter mAdapter;
+ @VisibleForTesting
+ protected WifiManager mWifiManager;
+ @VisibleForTesting
+ protected View mDialogView;
+ @VisibleForTesting
+ protected boolean mCanConfigWifi;
+
+ private InternetDialogFactory mInternetDialogFactory;
+ private SubscriptionManager mSubscriptionManager;
+ private TelephonyManager mTelephonyManager;
+ private AlertDialog mAlertDialog;
+ private UiEventLogger mUiEventLogger;
+ private Context mContext;
+ private InternetDialogController mInternetDialogController;
+ private TextView mInternetDialogTitle;
+ private TextView mInternetDialogSubTitle;
+ private View mDivider;
+ private ProgressBar mProgressBar;
+ private LinearLayout mInternetDialogLayout;
+ private LinearLayout mConnectedWifListLayout;
+ private LinearLayout mMobileNetworkLayout;
+ private LinearLayout mTurnWifiOnLayout;
+ private LinearLayout mEthernetLayout;
+ private TextView mWifiToggleTitleText;
+ private LinearLayout mSeeAllLayout;
+ private RecyclerView mWifiRecyclerView;
+ private ImageView mConnectedWifiIcon;
+ private ImageView mWifiSettingsIcon;
+ private TextView mConnectedWifiTitleText;
+ private TextView mConnectedWifiSummaryText;
+ private ImageView mSignalIcon;
+ private TextView mMobileTitleText;
+ private TextView mMobileSummaryText;
+ private Switch mMobileDataToggle;
+ private Switch mWiFiToggle;
+ private FrameLayout mDoneLayout;
+ private Drawable mBackgroundOn;
+ private int mListMaxHeight;
+ private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private boolean mCanConfigMobileData;
+
+ // Wi-Fi entries
+ protected WifiEntry mConnectedWifiEntry;
+ protected int mWifiEntriesCount;
+
+ // Wi-Fi scanning progress bar
+ protected boolean mIsProgressBarVisible;
+ protected boolean mIsSearchingHidden;
+ protected final Runnable mHideProgressBarRunnable = () -> {
+ setProgressBarVisible(false);
+ };
+ protected Runnable mHideSearchingRunnable = () -> {
+ mIsSearchingHidden = true;
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ };
+
+ private final ViewTreeObserver.OnGlobalLayoutListener mInternetListLayoutListener = () -> {
+ // Set max height for list
+ if (mInternetDialogLayout.getHeight() > mListMaxHeight) {
+ ViewGroup.LayoutParams params = mInternetDialogLayout.getLayoutParams();
+ params.height = mListMaxHeight;
+ mInternetDialogLayout.setLayoutParams(params);
+ }
+ };
+
+ public InternetDialog(Context context, InternetDialogFactory internetDialogFactory,
+ InternetDialogController internetDialogController, boolean canConfigMobileData,
+ boolean canConfigWifi, boolean aboveStatusBar, UiEventLogger uiEventLogger,
+ @Main Handler handler) {
+ super(context, R.style.Theme_SystemUI_Dialog_Internet);
+ if (DEBUG) {
+ Log.d(TAG, "Init InternetDialog");
+ }
+ mContext = context;
+ mHandler = handler;
+ mInternetDialogFactory = internetDialogFactory;
+ mInternetDialogController = internetDialogController;
+ mSubscriptionManager = mInternetDialogController.getSubscriptionManager();
+ mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId();
+ mTelephonyManager = mInternetDialogController.getTelephonyManager();
+ mWifiManager = mInternetDialogController.getWifiManager();
+ mCanConfigMobileData = canConfigMobileData;
+ mCanConfigWifi = canConfigWifi;
+
+ mLayoutManager = new LinearLayoutManager(mContext) {
+ @Override
+ public boolean canScrollVertically() {
+ return false;
+ }
+ };
+ mListMaxHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.internet_dialog_list_max_height);
+ mUiEventLogger = uiEventLogger;
+ mAdapter = new InternetAdapter(mInternetDialogController);
+ if (!aboveStatusBar) {
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
+ mUiEventLogger.log(InternetDialogEvent.INTERNET_DIALOG_SHOW);
+ mDialogView = LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog,
+ null);
+ final Window window = getWindow();
+ final WindowManager.LayoutParams layoutParams = window.getAttributes();
+ layoutParams.gravity = Gravity.BOTTOM;
+ // Move down the dialog to overlay the navigation bar.
+ layoutParams.setFitInsetsTypes(
+ layoutParams.getFitInsetsTypes() & ~WindowInsets.Type.navigationBars());
+ layoutParams.setFitInsetsSides(WindowInsets.Side.all());
+ layoutParams.setFitInsetsIgnoringVisibility(true);
+ window.setAttributes(layoutParams);
+ window.setContentView(mDialogView);
+ //Only fix the width for large screen or tablet.
+ window.setLayout(mContext.getResources().getDimensionPixelSize(
+ R.dimen.internet_dialog_list_max_width), ViewGroup.LayoutParams.WRAP_CONTENT);
+ window.setWindowAnimations(R.style.Animation_InternetDialog);
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ window.addFlags(FLAG_LAYOUT_NO_LIMITS);
+
+ mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog);
+ mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
+ mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+ mDivider = mDialogView.requireViewById(R.id.divider);
+ mProgressBar = mDialogView.requireViewById(R.id.wifi_searching_progress);
+ mEthernetLayout = mDialogView.requireViewById(R.id.ethernet_layout);
+ mMobileNetworkLayout = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mTurnWifiOnLayout = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mWifiToggleTitleText = mDialogView.requireViewById(R.id.wifi_toggle_title);
+ mConnectedWifListLayout = mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mConnectedWifiIcon = mDialogView.requireViewById(R.id.wifi_connected_icon);
+ mConnectedWifiTitleText = mDialogView.requireViewById(R.id.wifi_connected_title);
+ mConnectedWifiSummaryText = mDialogView.requireViewById(R.id.wifi_connected_summary);
+ mWifiSettingsIcon = mDialogView.requireViewById(R.id.wifi_settings_icon);
+ mWifiRecyclerView = mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAllLayout = mDialogView.requireViewById(R.id.see_all_layout);
+ mDoneLayout = mDialogView.requireViewById(R.id.done_layout);
+ mSignalIcon = mDialogView.requireViewById(R.id.signal_icon);
+ mMobileTitleText = mDialogView.requireViewById(R.id.mobile_title);
+ mMobileSummaryText = mDialogView.requireViewById(R.id.mobile_summary);
+ mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_toggle);
+ mWiFiToggle = mDialogView.requireViewById(R.id.wifi_toggle);
+ mBackgroundOn = mContext.getDrawable(R.drawable.settingslib_switch_bar_bg_on);
+ mInternetDialogLayout.getViewTreeObserver().addOnGlobalLayoutListener(
+ mInternetListLayoutListener);
+ mInternetDialogTitle.setText(getDialogTitleText());
+ mInternetDialogTitle.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
+
+ setOnClickListener();
+ mTurnWifiOnLayout.setBackground(null);
+ mWifiRecyclerView.setLayoutManager(mLayoutManager);
+ mWifiRecyclerView.setAdapter(mAdapter);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mInternetDialogController.onStart(this, mCanConfigWifi);
+ if (!mCanConfigWifi) {
+ hideWifiViews();
+ }
+ }
+
+ @VisibleForTesting
+ void hideWifiViews() {
+ setProgressBarVisible(false);
+ mTurnWifiOnLayout.setVisibility(View.GONE);
+ mConnectedWifListLayout.setVisibility(View.GONE);
+ mWifiRecyclerView.setVisibility(View.GONE);
+ mSeeAllLayout.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mHandler.removeCallbacks(mHideProgressBarRunnable);
+ mHandler.removeCallbacks(mHideSearchingRunnable);
+ mMobileNetworkLayout.setOnClickListener(null);
+ mMobileDataToggle.setOnCheckedChangeListener(null);
+ mConnectedWifListLayout.setOnClickListener(null);
+ mSeeAllLayout.setOnClickListener(null);
+ mWiFiToggle.setOnCheckedChangeListener(null);
+ mDoneLayout.setOnClickListener(null);
+ mInternetDialogController.onStop();
+ mInternetDialogFactory.destroyDialog();
+ }
+
+ @Override
+ public void dismissDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "dismissDialog");
+ }
+ mInternetDialogFactory.destroyDialog();
+ dismiss();
+ }
+
+ void updateDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "updateDialog");
+ }
+ if (mInternetDialogController.isAirplaneModeEnabled()) {
+ mInternetDialogSubTitle.setVisibility(View.GONE);
+ } else {
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ }
+ updateEthernet();
+ setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular()
+ || mInternetDialogController.isCarrierNetworkActive());
+
+ if (!mCanConfigWifi) {
+ return;
+ }
+
+ showProgressBar();
+ final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
+ final boolean isWifiEnabled = mWifiManager.isWifiEnabled();
+ updateWifiToggle(isWifiEnabled, isDeviceLocked);
+ updateConnectedWifi(isWifiEnabled, isDeviceLocked);
+
+ final int visibility = (isDeviceLocked || !isWifiEnabled || mWifiEntriesCount <= 0)
+ ? View.GONE : View.VISIBLE;
+ mWifiRecyclerView.setVisibility(visibility);
+ mSeeAllLayout.setVisibility(visibility);
+ }
+
+ private void setOnClickListener() {
+ mMobileNetworkLayout.setOnClickListener(v -> {
+ if (mInternetDialogController.isMobileDataEnabled()
+ && !mInternetDialogController.isDeviceLocked()) {
+ if (!mInternetDialogController.activeNetworkIsCellular()) {
+ mInternetDialogController.connectCarrierNetwork();
+ }
+ }
+ });
+ mMobileDataToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ if (!isChecked && shouldShowMobileDialog()) {
+ showTurnOffMobileDialog();
+ } else if (!shouldShowMobileDialog()) {
+ mInternetDialogController.setMobileDataEnabled(mContext, mDefaultDataSubId,
+ isChecked, false);
+ }
+ });
+ mConnectedWifListLayout.setOnClickListener(v -> onClickConnectedWifi());
+ mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton());
+ mWiFiToggle.setOnCheckedChangeListener(
+ (buttonView, isChecked) -> {
+ buttonView.setChecked(isChecked);
+ mWifiManager.setWifiEnabled(isChecked);
+ });
+ mDoneLayout.setOnClickListener(v -> dismiss());
+ }
+
+ @MainThread
+ private void updateEthernet() {
+ mEthernetLayout.setVisibility(
+ mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
+ }
+
+ private void setMobileDataLayout(boolean isCarrierNetworkConnected) {
+ if (mInternetDialogController.isAirplaneModeEnabled()
+ || !mInternetDialogController.hasCarrier()) {
+ mMobileNetworkLayout.setVisibility(View.GONE);
+ } else {
+ mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
+ mMobileNetworkLayout.setVisibility(View.VISIBLE);
+ mMobileTitleText.setText(getMobileNetworkTitle());
+ if (!TextUtils.isEmpty(getMobileNetworkSummary())) {
+ mMobileSummaryText.setText(
+ Html.fromHtml(getMobileNetworkSummary(), Html.FROM_HTML_MODE_LEGACY));
+ mMobileSummaryText.setVisibility(View.VISIBLE);
+ } else {
+ mMobileSummaryText.setVisibility(View.GONE);
+ }
+ mSignalIcon.setImageDrawable(getSignalStrengthDrawable());
+ mMobileTitleText.setTextAppearance(isCarrierNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ mMobileSummaryText.setTextAppearance(isCarrierNetworkConnected
+ ? R.style.TextAppearance_InternetDialog_Secondary_Active
+ : R.style.TextAppearance_InternetDialog_Secondary);
+ mMobileNetworkLayout.setBackground(isCarrierNetworkConnected ? mBackgroundOn : null);
+
+ mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ @MainThread
+ private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
+ mWiFiToggle.setChecked(isWifiEnabled);
+ if (isDeviceLocked) {
+ mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null)
+ ? R.style.TextAppearance_InternetDialog_Active
+ : R.style.TextAppearance_InternetDialog);
+ }
+ mTurnWifiOnLayout.setBackground(
+ (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+ }
+
+ @MainThread
+ private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
+ if (!isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
+ mConnectedWifListLayout.setVisibility(View.GONE);
+ return;
+ }
+ mConnectedWifListLayout.setVisibility(View.VISIBLE);
+ mConnectedWifiTitleText.setText(mConnectedWifiEntry.getTitle());
+ mConnectedWifiSummaryText.setText(mConnectedWifiEntry.getSummary(false));
+ mConnectedWifiIcon.setImageDrawable(
+ mInternetDialogController.getInternetWifiDrawable(mConnectedWifiEntry));
+ mWifiSettingsIcon.setColorFilter(
+ mContext.getColor(R.color.connected_network_primary_color));
+ }
+
+ void onClickConnectedWifi() {
+ if (mConnectedWifiEntry == null) {
+ return;
+ }
+ mInternetDialogController.launchWifiNetworkDetailsSetting(mConnectedWifiEntry.getKey());
+ }
+
+ void onClickSeeMoreButton() {
+ mInternetDialogController.launchNetworkSetting();
+ }
+
+ CharSequence getDialogTitleText() {
+ return mInternetDialogController.getDialogTitleText();
+ }
+
+ CharSequence getSubtitleText() {
+ return mInternetDialogController.getSubtitleText(
+ mIsProgressBarVisible && !mIsSearchingHidden);
+ }
+
+ private Drawable getSignalStrengthDrawable() {
+ return mInternetDialogController.getSignalStrengthDrawable();
+ }
+
+ CharSequence getMobileNetworkTitle() {
+ return mInternetDialogController.getMobileNetworkTitle();
+ }
+
+ String getMobileNetworkSummary() {
+ return mInternetDialogController.getMobileNetworkSummary();
+ }
+
+ protected void showProgressBar() {
+ if (mWifiManager == null || !mWifiManager.isWifiEnabled()
+ || mInternetDialogController.isDeviceLocked()) {
+ setProgressBarVisible(false);
+ return;
+ }
+ setProgressBarVisible(true);
+ if (mConnectedWifiEntry != null || mWifiEntriesCount > 0) {
+ mHandler.postDelayed(mHideProgressBarRunnable, PROGRESS_DELAY_MS);
+ } else if (!mIsSearchingHidden) {
+ mHandler.postDelayed(mHideSearchingRunnable, PROGRESS_DELAY_MS);
+ }
+ }
+
+ private void setProgressBarVisible(boolean visible) {
+ if (mWifiManager.isWifiEnabled() && mAdapter.mHolderView != null
+ && mAdapter.mHolderView.isAttachedToWindow()) {
+ mIsProgressBarVisible = true;
+ }
+ mIsProgressBarVisible = visible;
+ mProgressBar.setVisibility(mIsProgressBarVisible ? View.VISIBLE : View.GONE);
+ mDivider.setVisibility(mIsProgressBarVisible ? View.GONE : View.VISIBLE);
+ mInternetDialogSubTitle.setText(getSubtitleText());
+ }
+
+ private boolean shouldShowMobileDialog() {
+ boolean flag = Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA,
+ false);
+ if (mInternetDialogController.isMobileDataEnabled() && !flag) {
+ return true;
+ }
+ return false;
+ }
+
+ private void showTurnOffMobileDialog() {
+ CharSequence carrierName = getMobileNetworkTitle();
+ boolean isInService = mInternetDialogController.isVoiceStateInService();
+ if (TextUtils.isEmpty(carrierName) || !isInService) {
+ carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
+ }
+ mAlertDialog = new Builder(mContext)
+ .setTitle(R.string.mobile_data_disable_title)
+ .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
+ .setNegativeButton(android.R.string.cancel, (d, w) -> {
+ mMobileDataToggle.setChecked(true);
+ })
+ .setPositiveButton(
+ com.android.internal.R.string.alert_windows_notification_turn_off_action,
+ (d, w) -> {
+ mInternetDialogController.setMobileDataEnabled(mContext,
+ mDefaultDataSubId, false, false);
+ mMobileDataToggle.setChecked(false);
+ Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
+ })
+ .create();
+ mAlertDialog.setOnCancelListener(dialog -> mMobileDataToggle.setChecked(true));
+ mAlertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+ SystemUIDialog.setShowForAllUsers(mAlertDialog, true);
+ SystemUIDialog.registerDismissListener(mAlertDialog);
+ SystemUIDialog.setWindowOnTop(mAlertDialog);
+ mAlertDialog.show();
+ }
+
+ @Override
+ public void onRefreshCarrierInfo() {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSimStateChanged() {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onLost(Network network) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSubscriptionsChanged(int defaultDataSubId) {
+ mDefaultDataSubId = defaultDataSubId;
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onServiceStateChanged(ServiceState serviceState) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
+ mHandler.post(() -> updateDialog());
+ }
+
+ @Override
+ @WorkerThread
+ public void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+ @Nullable WifiEntry connectedEntry) {
+ mConnectedWifiEntry = connectedEntry;
+ mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+ mAdapter.setWifiEntries(wifiEntries, mWifiEntriesCount);
+ mHandler.post(() -> {
+ mAdapter.notifyDataSetChanged();
+ updateDialog();
+ });
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (mAlertDialog != null && !mAlertDialog.isShowing()) {
+ if (!hasFocus && isShowing()) {
+ dismiss();
+ }
+ }
+ }
+
+ public enum InternetDialogEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The Internet dialog became visible on the screen.")
+ INTERNET_DIALOG_SHOW(843);
+
+ private final int mId;
+
+ InternetDialogEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
new file mode 100644
index 000000000000..aaba5efc92f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -0,0 +1,1072 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.android.settingslib.mobile.MobileMappings.getIconKey;
+import static com.android.settingslib.mobile.MobileMappings.mapIconSets;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.SignalStrength;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyDisplayInfo;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.DeviceInfoUtils;
+import com.android.settingslib.SignalIcon;
+import com.android.settingslib.Utils;
+import com.android.settingslib.graph.SignalDrawable;
+import com.android.settingslib.mobile.MobileMappings;
+import com.android.settingslib.mobile.TelephonyIcons;
+import com.android.settingslib.net.SignalStrengthUtil;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.toast.SystemUIToast;
+import com.android.systemui.toast.ToastFactory;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.inject.Inject;
+
+public class InternetDialogController implements WifiEntry.DisconnectCallback,
+ NetworkController.AccessPointController.AccessPointCallback {
+
+ private static final String TAG = "InternetDialogController";
+ private static final String ACTION_NETWORK_PROVIDER_SETTINGS =
+ "android.settings.NETWORK_PROVIDER_SETTINGS";
+ private static final String EXTRA_CHOSEN_WIFI_ENTRY_KEY = "key_chosen_wifientry_key";
+ public static final Drawable EMPTY_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
+ public static final int NO_CELL_DATA_TYPE_ICON = 0;
+ private static final int SUBTITLE_TEXT_WIFI_IS_OFF = R.string.wifi_is_off;
+ private static final int SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT =
+ R.string.tap_a_network_to_connect;
+ private static final int SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS =
+ R.string.unlock_to_view_networks;
+ private static final int SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS =
+ R.string.wifi_empty_list_wifi_on;
+ private static final int SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE =
+ R.string.non_carrier_network_unavailable;
+ private static final int SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE =
+ R.string.all_network_unavailable;
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ static final int MAX_WIFI_ENTRY_COUNT = 4;
+
+ private WifiManager mWifiManager;
+ private Context mContext;
+ private SubscriptionManager mSubscriptionManager;
+ private TelephonyManager mTelephonyManager;
+ private ConnectivityManager mConnectivityManager;
+ private CarrierConfigTracker mCarrierConfigTracker;
+ private TelephonyDisplayInfo mTelephonyDisplayInfo =
+ new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+ private Handler mHandler;
+ private Handler mWorkerHandler;
+ private MobileMappings.Config mConfig = null;
+ private Executor mExecutor;
+ private AccessPointController mAccessPointController;
+ private IntentFilter mConnectionStateFilter;
+ private InternetDialogCallback mCallback;
+ private WifiEntry mConnectedEntry;
+ private int mWifiEntriesCount;
+ private UiEventLogger mUiEventLogger;
+ private BroadcastDispatcher mBroadcastDispatcher;
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private GlobalSettings mGlobalSettings;
+ private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
+ private WindowManager mWindowManager;
+ private ToastFactory mToastFactory;
+
+ @VisibleForTesting
+ static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
+ @VisibleForTesting
+ static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f;
+ @VisibleForTesting
+ static final long SHORT_DURATION_TIMEOUT = 4000;
+ @VisibleForTesting
+ protected ActivityStarter mActivityStarter;
+ @VisibleForTesting
+ protected SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
+ @VisibleForTesting
+ protected InternetTelephonyCallback mInternetTelephonyCallback;
+ @VisibleForTesting
+ protected WifiUtils.InternetIconInjector mWifiIconInjector;
+ @VisibleForTesting
+ protected boolean mCanConfigWifi;
+ @VisibleForTesting
+ protected KeyguardStateController mKeyguardStateController;
+ @VisibleForTesting
+ protected boolean mHasEthernet = false;
+
+ private final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onRefreshCarrierInfo() {
+ mCallback.onRefreshCarrierInfo();
+ }
+
+ @Override
+ public void onSimStateChanged(int subId, int slotId, int simState) {
+ mCallback.onSimStateChanged();
+ }
+ };
+
+ protected List<SubscriptionInfo> getSubscriptionInfo() {
+ return mKeyguardUpdateMonitor.getFilteredSubscriptionInfo(false);
+ }
+
+ @Inject
+ public InternetDialogController(@NonNull Context context, UiEventLogger uiEventLogger,
+ ActivityStarter starter, AccessPointController accessPointController,
+ SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ @Main Handler handler, @Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
+ WindowManager windowManager, ToastFactory toastFactory,
+ @Background Handler workerHandler,
+ CarrierConfigTracker carrierConfigTracker) {
+ if (DEBUG) {
+ Log.d(TAG, "Init InternetDialogController");
+ }
+ mHandler = handler;
+ mWorkerHandler = workerHandler;
+ mExecutor = mainExecutor;
+ mContext = context;
+ mGlobalSettings = globalSettings;
+ mWifiManager = wifiManager;
+ mTelephonyManager = telephonyManager;
+ mConnectivityManager = connectivityManager;
+ mSubscriptionManager = subscriptionManager;
+ mCarrierConfigTracker = carrierConfigTracker;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mKeyguardStateController = keyguardStateController;
+ mConnectionStateFilter = new IntentFilter();
+ mConnectionStateFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ mConnectionStateFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
+ mUiEventLogger = uiEventLogger;
+ mActivityStarter = starter;
+ mAccessPointController = accessPointController;
+ mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
+ mConnectivityManagerNetworkCallback = new DataConnectivityListener();
+ mWindowManager = windowManager;
+ mToastFactory = toastFactory;
+ }
+
+ void onStart(@NonNull InternetDialogCallback callback, boolean canConfigWifi) {
+ if (DEBUG) {
+ Log.d(TAG, "onStart");
+ }
+ mCallback = callback;
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ mAccessPointController.addAccessPointCallback(this);
+ mBroadcastDispatcher.registerReceiver(mConnectionStateReceiver, mConnectionStateFilter,
+ mExecutor);
+ // Listen the subscription changes
+ mOnSubscriptionsChangedListener = new InternetOnSubscriptionChangedListener();
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+ mOnSubscriptionsChangedListener);
+ mDefaultDataSubId = getDefaultDataSubscriptionId();
+ if (DEBUG) {
+ Log.d(TAG, "Init, SubId: " + mDefaultDataSubId);
+ }
+ mConfig = MobileMappings.Config.readConfig(mContext);
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mInternetTelephonyCallback = new InternetTelephonyCallback();
+ mTelephonyManager.registerTelephonyCallback(mExecutor, mInternetTelephonyCallback);
+ // Listen the connectivity changes
+ mConnectivityManager.registerDefaultNetworkCallback(mConnectivityManagerNetworkCallback);
+ mCanConfigWifi = canConfigWifi;
+ scanWifiAccessPoints();
+ }
+
+ void onStop() {
+ if (DEBUG) {
+ Log.d(TAG, "onStop");
+ }
+ mBroadcastDispatcher.unregisterReceiver(mConnectionStateReceiver);
+ mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ mSubscriptionManager.removeOnSubscriptionsChangedListener(
+ mOnSubscriptionsChangedListener);
+ mAccessPointController.removeAccessPointCallback(this);
+ mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+ mConnectivityManager.unregisterNetworkCallback(mConnectivityManagerNetworkCallback);
+ }
+
+ @VisibleForTesting
+ boolean isAirplaneModeEnabled() {
+ return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
+ }
+
+ @VisibleForTesting
+ protected int getDefaultDataSubscriptionId() {
+ return mSubscriptionManager.getDefaultDataSubscriptionId();
+ }
+
+ @VisibleForTesting
+ protected Intent getSettingsIntent() {
+ return new Intent(ACTION_NETWORK_PROVIDER_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+
+ protected Intent getWifiDetailsSettingsIntent(String key) {
+ if (TextUtils.isEmpty(key)) {
+ if (DEBUG) {
+ Log.d(TAG, "connected entry's key is empty");
+ }
+ return null;
+ }
+ return WifiUtils.getWifiDetailsSettingsIntent(key);
+ }
+
+ CharSequence getDialogTitleText() {
+ if (isAirplaneModeEnabled()) {
+ return mContext.getText(R.string.airplane_mode);
+ }
+ return mContext.getText(R.string.quick_settings_internet_label);
+ }
+
+ CharSequence getSubtitleText(boolean isProgressBarVisible) {
+ if (isAirplaneModeEnabled()) {
+ return null;
+ }
+
+ if (mCanConfigWifi && !mWifiManager.isWifiEnabled()) {
+ // When the airplane mode is off and Wi-Fi is disabled.
+ // Sub-Title: Wi-Fi is off
+ if (DEBUG) {
+ Log.d(TAG, "Airplane mode off + Wi-Fi off.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_WIFI_IS_OFF);
+ }
+
+ if (isDeviceLocked()) {
+ // When the device is locked.
+ // Sub-Title: Unlock to view networks
+ if (DEBUG) {
+ Log.d(TAG, "The device is locked.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_UNLOCK_TO_VIEW_NETWORKS);
+ }
+
+ if (mConnectedEntry != null || mWifiEntriesCount > 0) {
+ return mCanConfigWifi ? mContext.getText(SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT) : null;
+ }
+
+ if (mCanConfigWifi && isProgressBarVisible) {
+ // When the Wi-Fi scan result callback is received
+ // Sub-Title: Searching for networks...
+ return mContext.getText(SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS);
+ }
+
+ // Sub-Title:
+ // show non_carrier_network_unavailable
+ // - while Wi-Fi on + no Wi-Fi item
+ // - while Wi-Fi on + no Wi-Fi item + mobile data off
+ // show all_network_unavailable:
+ // - while Wi-Fi on + no Wi-Fi item + no carrier item
+ // - while Wi-Fi on + no Wi-Fi item + service is out of service
+ // - while Wi-Fi on + no Wi-Fi item + mobile data on + no carrier data.
+ if (DEBUG) {
+ Log.d(TAG, "No Wi-Fi item.");
+ }
+ if (!hasCarrier() || (!isVoiceStateInService() && !isDataStateInService())) {
+ if (DEBUG) {
+ Log.d(TAG, "No carrier or service is out of service.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (mCanConfigWifi && !isMobileDataEnabled()) {
+ if (DEBUG) {
+ Log.d(TAG, "Mobile data off");
+ }
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (!activeNetworkIsCellular()) {
+ if (DEBUG) {
+ Log.d(TAG, "No carrier data.");
+ }
+ return mContext.getText(SUBTITLE_TEXT_ALL_CARRIER_NETWORK_UNAVAILABLE);
+ }
+
+ if (mCanConfigWifi) {
+ return mContext.getText(SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE);
+ }
+ return null;
+ }
+
+ Drawable getInternetWifiDrawable(@NonNull WifiEntry wifiEntry) {
+ if (wifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) {
+ return null;
+ }
+ final Drawable drawable =
+ mWifiIconInjector.getIcon(wifiEntry.shouldShowXLevelIcon(), wifiEntry.getLevel());
+ if (drawable == null) {
+ return null;
+ }
+ drawable.setTint(mContext.getColor(R.color.connected_network_primary_color));
+ return drawable;
+ }
+
+ Drawable getSignalStrengthDrawable() {
+ Drawable drawable = mContext.getDrawable(
+ R.drawable.ic_signal_strength_zero_bar_no_internet);
+ try {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null");
+ }
+ return drawable;
+ }
+
+ if (isDataStateInService() || isVoiceStateInService()) {
+ AtomicReference<Drawable> shared = new AtomicReference<>();
+ shared.set(getSignalStrengthDrawableWithLevel());
+ drawable = shared.get();
+ }
+
+ int tintColor = Utils.getColorAttrDefaultColor(mContext,
+ android.R.attr.textColorTertiary);
+ if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ tintColor = mContext.getColor(R.color.connected_network_primary_color);
+ }
+ drawable.setTint(tintColor);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return drawable;
+ }
+
+ /**
+ * To get the signal bar icon with level.
+ *
+ * @return The Drawable which is a signal bar icon with level.
+ */
+ Drawable getSignalStrengthDrawableWithLevel() {
+ final SignalStrength strength = mTelephonyManager.getSignalStrength();
+ int level = (strength == null) ? 0 : strength.getLevel();
+ int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+ if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) {
+ level += 1;
+ numLevels += 1;
+ }
+ return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON,
+ !isMobileDataEnabled());
+ }
+
+ Drawable getSignalStrengthIcon(Context context, int level, int numLevels,
+ int iconType, boolean cutOut) {
+ Log.d(TAG, "getSignalStrengthIcon");
+ final SignalDrawable signalDrawable = new SignalDrawable(context);
+ signalDrawable.setLevel(
+ SignalDrawable.getState(level, numLevels, cutOut));
+
+ // Make the network type drawable
+ final Drawable networkDrawable =
+ iconType == NO_CELL_DATA_TYPE_ICON
+ ? EMPTY_DRAWABLE
+ : context.getResources().getDrawable(iconType, context.getTheme());
+
+ // Overlay the two drawables
+ final Drawable[] layers = {networkDrawable, signalDrawable};
+ final int iconSize =
+ context.getResources().getDimensionPixelSize(R.dimen.signal_strength_icon_size);
+
+ final LayerDrawable icons = new LayerDrawable(layers);
+ // Set the network type icon at the top left
+ icons.setLayerGravity(0 /* index of networkDrawable */, Gravity.TOP | Gravity.LEFT);
+ // Set the signal strength icon at the bottom right
+ icons.setLayerGravity(1 /* index of SignalDrawable */, Gravity.BOTTOM | Gravity.RIGHT);
+ icons.setLayerSize(1 /* index of SignalDrawable */, iconSize, iconSize);
+ icons.setTintList(Utils.getColorAttr(context, android.R.attr.textColorTertiary));
+ return icons;
+ }
+
+ private boolean shouldInflateSignalStrength(int subId) {
+ return SignalStrengthUtil.shouldInflateSignalStrength(mContext, subId);
+ }
+
+ private CharSequence getUniqueSubscriptionDisplayName(int subscriptionId, Context context) {
+ final Map<Integer, CharSequence> displayNames = getUniqueSubscriptionDisplayNames(context);
+ return displayNames.getOrDefault(subscriptionId, "");
+ }
+
+ private Map<Integer, CharSequence> getUniqueSubscriptionDisplayNames(Context context) {
+ class DisplayInfo {
+ public SubscriptionInfo subscriptionInfo;
+ public CharSequence originalName;
+ public CharSequence uniqueName;
+ }
+
+ // Map of SubscriptionId to DisplayName
+ final Supplier<Stream<DisplayInfo>> originalInfos =
+ () -> getSubscriptionInfo()
+ .stream()
+ .filter(i -> {
+ // Filter out null values.
+ return (i != null && i.getDisplayName() != null);
+ })
+ .map(i -> {
+ DisplayInfo info = new DisplayInfo();
+ info.subscriptionInfo = i;
+ info.originalName = i.getDisplayName().toString().trim();
+ return info;
+ });
+
+ // A Unique set of display names
+ Set<CharSequence> uniqueNames = new HashSet<>();
+ // Return the set of duplicate names
+ final Set<CharSequence> duplicateOriginalNames = originalInfos.get()
+ .filter(info -> !uniqueNames.add(info.originalName))
+ .map(info -> info.originalName)
+ .collect(Collectors.toSet());
+
+ // If a display name is duplicate, append the final 4 digits of the phone number.
+ // Creates a mapping of Subscription id to original display name + phone number display name
+ final Supplier<Stream<DisplayInfo>> uniqueInfos = () -> originalInfos.get().map(info -> {
+ if (duplicateOriginalNames.contains(info.originalName)) {
+ // This may return null, if the user cannot view the phone number itself.
+ final String phoneNumber = DeviceInfoUtils.getBidiFormattedPhoneNumber(context,
+ info.subscriptionInfo);
+ String lastFourDigits = "";
+ if (phoneNumber != null) {
+ lastFourDigits = (phoneNumber.length() > 4)
+ ? phoneNumber.substring(phoneNumber.length() - 4) : phoneNumber;
+ }
+
+ if (TextUtils.isEmpty(lastFourDigits)) {
+ info.uniqueName = info.originalName;
+ } else {
+ info.uniqueName = info.originalName + " " + lastFourDigits;
+ }
+
+ } else {
+ info.uniqueName = info.originalName;
+ }
+ return info;
+ });
+
+ // Check uniqueness a second time.
+ // We might not have had permission to view the phone numbers.
+ // There might also be multiple phone numbers whose last 4 digits the same.
+ uniqueNames.clear();
+ final Set<CharSequence> duplicatePhoneNames = uniqueInfos.get()
+ .filter(info -> !uniqueNames.add(info.uniqueName))
+ .map(info -> info.uniqueName)
+ .collect(Collectors.toSet());
+
+ return uniqueInfos.get().map(info -> {
+ if (duplicatePhoneNames.contains(info.uniqueName)) {
+ info.uniqueName = info.originalName + " "
+ + info.subscriptionInfo.getSubscriptionId();
+ }
+ return info;
+ }).collect(Collectors.toMap(
+ info -> info.subscriptionInfo.getSubscriptionId(),
+ info -> info.uniqueName));
+ }
+
+ CharSequence getMobileNetworkTitle() {
+ return getUniqueSubscriptionDisplayName(mDefaultDataSubId, mContext);
+ }
+
+ String getMobileNetworkSummary() {
+ String description = getNetworkTypeDescription(mContext, mConfig,
+ mTelephonyDisplayInfo, mDefaultDataSubId);
+ return getMobileSummary(mContext, description);
+ }
+
+ /**
+ * Get currently description of mobile network type.
+ */
+ private String getNetworkTypeDescription(Context context, MobileMappings.Config config,
+ TelephonyDisplayInfo telephonyDisplayInfo, int subId) {
+ String iconKey = getIconKey(telephonyDisplayInfo);
+
+ if (mapIconSets(config) == null || mapIconSets(config).get(iconKey) == null) {
+ if (DEBUG) {
+ Log.d(TAG, "The description of network type is empty.");
+ }
+ return "";
+ }
+
+ int resId = mapIconSets(config).get(iconKey).dataContentDescription;
+ if (isCarrierNetworkActive()) {
+ SignalIcon.MobileIconGroup carrierMergedWifiIconGroup =
+ TelephonyIcons.CARRIER_MERGED_WIFI;
+ resId = carrierMergedWifiIconGroup.dataContentDescription;
+ }
+
+ return resId != 0
+ ? SubscriptionManager.getResourcesForSubId(context, subId).getString(resId) : "";
+ }
+
+ private String getMobileSummary(Context context, String networkTypeDescription) {
+ if (!isMobileDataEnabled()) {
+ return context.getString(R.string.mobile_data_off_summary);
+ }
+ if (!isDataStateInService()) {
+ return context.getString(R.string.mobile_data_no_connection);
+ }
+ String summary = networkTypeDescription;
+ if (activeNetworkIsCellular() || isCarrierNetworkActive()) {
+ summary = context.getString(R.string.preference_summary_default_combination,
+ context.getString(R.string.mobile_data_connection_active),
+ networkTypeDescription);
+ }
+ return summary;
+ }
+
+ void launchNetworkSetting() {
+ mCallback.dismissDialog();
+ mActivityStarter.postStartActivityDismissingKeyguard(getSettingsIntent(), 0);
+ }
+
+ void launchWifiNetworkDetailsSetting(String key) {
+ Intent intent = getWifiDetailsSettingsIntent(key);
+ if (intent != null) {
+ mCallback.dismissDialog();
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
+ }
+ }
+
+ void connectCarrierNetwork() {
+ final MergedCarrierEntry mergedCarrierEntry =
+ mAccessPointController.getMergedCarrierEntry();
+ if (mergedCarrierEntry != null && mergedCarrierEntry.canConnect()) {
+ mergedCarrierEntry.connect(null /* ConnectCallback */, false);
+ makeOverlayToast(R.string.wifi_wont_autoconnect_for_now);
+ }
+ }
+
+ boolean isCarrierNetworkActive() {
+ final MergedCarrierEntry mergedCarrierEntry =
+ mAccessPointController.getMergedCarrierEntry();
+ return mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork();
+ }
+
+ @WorkerThread
+ void setMergedCarrierWifiEnabledIfNeed(int subId, boolean enabled) {
+ // If the Carrier Provisions Wi-Fi Merged Networks enabled, do not set the merged carrier
+ // Wi-Fi state together.
+ if (mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(subId)) {
+ return;
+ }
+
+ final MergedCarrierEntry entry = mAccessPointController.getMergedCarrierEntry();
+ if (entry == null) {
+ if (DEBUG) {
+ Log.d(TAG, "MergedCarrierEntry is null, can not set the status.");
+ }
+ return;
+ }
+ entry.setEnabled(enabled);
+ }
+
+ WifiManager getWifiManager() {
+ return mWifiManager;
+ }
+
+ TelephonyManager getTelephonyManager() {
+ return mTelephonyManager;
+ }
+
+ SubscriptionManager getSubscriptionManager() {
+ return mSubscriptionManager;
+ }
+
+ /**
+ * @return whether there is the carrier item in the slice.
+ */
+ boolean hasCarrier() {
+ if (mSubscriptionManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "SubscriptionManager is null, can not check carrier.");
+ }
+ return false;
+ }
+
+ if (isAirplaneModeEnabled() || mTelephonyManager == null
+ || mSubscriptionManager.getActiveSubscriptionIdList().length <= 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return {@code true} if mobile data is enabled
+ */
+ boolean isMobileDataEnabled() {
+ if (mTelephonyManager == null || !mTelephonyManager.isDataEnabled()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Set whether to enable data for {@code subId}, also whether to disable data for other
+ * subscription
+ */
+ void setMobileDataEnabled(Context context, int subId, boolean enabled,
+ boolean disableOtherSubscriptions) {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not set mobile data.");
+ }
+ return;
+ }
+
+ if (mSubscriptionManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "SubscriptionManager is null, can not set mobile data.");
+ }
+ return;
+ }
+
+ mTelephonyManager.setDataEnabled(enabled);
+ if (disableOtherSubscriptions) {
+ final List<SubscriptionInfo> subInfoList =
+ mSubscriptionManager.getActiveSubscriptionInfoList();
+ if (subInfoList != null) {
+ for (SubscriptionInfo subInfo : subInfoList) {
+ // We never disable mobile data for opportunistic subscriptions.
+ if (subInfo.getSubscriptionId() != subId && !subInfo.isOpportunistic()) {
+ context.getSystemService(TelephonyManager.class).createForSubscriptionId(
+ subInfo.getSubscriptionId()).setDataEnabled(false);
+ }
+ }
+ }
+ }
+ mWorkerHandler.post(() -> setMergedCarrierWifiEnabledIfNeed(subId, enabled));
+ }
+
+ boolean isDataStateInService() {
+ final ServiceState serviceState = mTelephonyManager.getServiceState();
+ NetworkRegistrationInfo regInfo =
+ (serviceState == null) ? null : serviceState.getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS,
+ AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+ return (regInfo == null) ? false : regInfo.isRegistered();
+ }
+
+ boolean isVoiceStateInService() {
+ if (mTelephonyManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "TelephonyManager is null, can not detect voice state.");
+ }
+ return false;
+ }
+
+ final ServiceState serviceState = mTelephonyManager.getServiceState();
+ return serviceState != null
+ && serviceState.getState() == serviceState.STATE_IN_SERVICE;
+ }
+
+ public boolean isDeviceLocked() {
+ return !mKeyguardStateController.isUnlocked();
+ }
+
+ boolean activeNetworkIsCellular() {
+ if (mConnectivityManager == null) {
+ if (DEBUG) {
+ Log.d(TAG, "ConnectivityManager is null, can not check active network.");
+ }
+ return false;
+ }
+
+ final Network activeNetwork = mConnectivityManager.getActiveNetwork();
+ if (activeNetwork == null) {
+ return false;
+ }
+ final NetworkCapabilities networkCapabilities =
+ mConnectivityManager.getNetworkCapabilities(activeNetwork);
+ if (networkCapabilities == null) {
+ return false;
+ }
+ return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+ }
+
+ boolean connect(WifiEntry ap) {
+ if (ap == null) {
+ if (DEBUG) {
+ Log.d(TAG, "No Wi-Fi ap to connect.");
+ }
+ return false;
+ }
+
+ if (ap.getWifiConfiguration() != null) {
+ if (DEBUG) {
+ Log.d(TAG, "connect networkId=" + ap.getWifiConfiguration().networkId);
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "connect to unsaved network " + ap.getTitle());
+ }
+ }
+ ap.connect(new WifiEntryConnectCallback(mActivityStarter, ap, this));
+ return false;
+ }
+
+ static class WifiEntryConnectCallback implements WifiEntry.ConnectCallback {
+ final ActivityStarter mActivityStarter;
+ final WifiEntry mWifiEntry;
+ final InternetDialogController mInternetDialogController;
+
+ WifiEntryConnectCallback(ActivityStarter activityStarter, WifiEntry connectWifiEntry,
+ InternetDialogController internetDialogController) {
+ mActivityStarter = activityStarter;
+ mWifiEntry = connectWifiEntry;
+ mInternetDialogController = internetDialogController;
+ }
+
+ @Override
+ public void onConnectResult(@ConnectStatus int status) {
+ if (DEBUG) {
+ Log.d(TAG, "onConnectResult " + status);
+ }
+
+ if (status == WifiEntry.ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) {
+ final Intent intent = new Intent("com.android.settings.WIFI_DIALOG")
+ .putExtra(EXTRA_CHOSEN_WIFI_ENTRY_KEY, mWifiEntry.getKey());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
+ } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) {
+ mInternetDialogController.makeOverlayToast(R.string.wifi_failed_connect_message);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "connect failure reason=" + status);
+ }
+ }
+ }
+ }
+
+ private void scanWifiAccessPoints() {
+ if (mCanConfigWifi) {
+ mAccessPointController.scanForAccessPoints();
+ }
+ }
+
+ @Override
+ @WorkerThread
+ public void onAccessPointsChanged(List<WifiEntry> accessPoints) {
+ if (!mCanConfigWifi) {
+ return;
+ }
+
+ if (accessPoints == null || accessPoints.size() == 0) {
+ mConnectedEntry = null;
+ mWifiEntriesCount = 0;
+ if (mCallback != null) {
+ mCallback.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ }
+ return;
+ }
+
+ boolean hasConnectedWifi = false;
+ final int accessPointSize = accessPoints.size();
+ for (int i = 0; i < accessPointSize; i++) {
+ WifiEntry wifiEntry = accessPoints.get(i);
+ if (wifiEntry.isDefaultNetwork() && wifiEntry.hasInternetAccess()) {
+ mConnectedEntry = wifiEntry;
+ hasConnectedWifi = true;
+ break;
+ }
+ }
+ if (!hasConnectedWifi) {
+ mConnectedEntry = null;
+ }
+
+ int count = MAX_WIFI_ENTRY_COUNT;
+ if (mHasEthernet) {
+ count -= 1;
+ }
+ if (hasCarrier()) {
+ count -= 1;
+ }
+ if (hasConnectedWifi) {
+ count -= 1;
+ }
+ final List<WifiEntry> wifiEntries = accessPoints.stream()
+ .filter(wifiEntry -> (!wifiEntry.isDefaultNetwork()
+ || !wifiEntry.hasInternetAccess()))
+ .limit(count)
+ .collect(Collectors.toList());
+ mWifiEntriesCount = wifiEntries == null ? 0 : wifiEntries.size();
+
+ if (mCallback != null) {
+ mCallback.onAccessPointsChanged(wifiEntries, mConnectedEntry);
+ }
+ }
+
+ @Override
+ public void onSettingsActivityTriggered(Intent settingsIntent) {
+ }
+
+ @Override
+ public void onDisconnectResult(int status) {
+ }
+
+ private class InternetTelephonyCallback extends TelephonyCallback implements
+ TelephonyCallback.DataConnectionStateListener,
+ TelephonyCallback.DisplayInfoListener,
+ TelephonyCallback.ServiceStateListener,
+ TelephonyCallback.SignalStrengthsListener {
+
+ @Override
+ public void onServiceStateChanged(@NonNull ServiceState serviceState) {
+ mCallback.onServiceStateChanged(serviceState);
+ }
+
+ @Override
+ public void onDataConnectionStateChanged(int state, int networkType) {
+ mCallback.onDataConnectionStateChanged(state, networkType);
+ }
+
+ @Override
+ public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
+ mCallback.onSignalStrengthsChanged(signalStrength);
+ }
+
+ @Override
+ public void onDisplayInfoChanged(@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
+ mTelephonyDisplayInfo = telephonyDisplayInfo;
+ mCallback.onDisplayInfoChanged(telephonyDisplayInfo);
+ }
+ }
+
+ private class InternetOnSubscriptionChangedListener
+ extends SubscriptionManager.OnSubscriptionsChangedListener {
+ InternetOnSubscriptionChangedListener() {
+ super();
+ }
+
+ @Override
+ public void onSubscriptionsChanged() {
+ updateListener();
+ }
+ }
+
+ private class DataConnectivityListener extends ConnectivityManager.NetworkCallback {
+ @Override
+ @WorkerThread
+ public void onCapabilitiesChanged(@NonNull Network network,
+ @NonNull NetworkCapabilities capabilities) {
+ mHasEthernet = capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
+ if (mCanConfigWifi && (mHasEthernet || capabilities.hasTransport(
+ NetworkCapabilities.TRANSPORT_WIFI))) {
+ scanWifiAccessPoints();
+ }
+ // update UI
+ mCallback.onCapabilitiesChanged(network, capabilities);
+ }
+
+ @Override
+ @WorkerThread
+ public void onLost(@NonNull Network network) {
+ mHasEthernet = false;
+ mCallback.onLost(network);
+ }
+ }
+
+ /**
+ * Return {@code true} If the Ethernet exists
+ */
+ @MainThread
+ public boolean hasEthernet() {
+ return mHasEthernet;
+ }
+
+ private final BroadcastReceiver mConnectionStateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED.equals(action)) {
+ if (DEBUG) {
+ Log.d(TAG, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED");
+ }
+ mConfig = MobileMappings.Config.readConfig(context);
+ updateListener();
+ } else if (WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION.equals(action)) {
+ updateListener();
+ }
+ }
+ };
+
+ private void updateListener() {
+ int defaultDataSubId = getDefaultDataSubscriptionId();
+ if (mDefaultDataSubId == getDefaultDataSubscriptionId()) {
+ if (DEBUG) {
+ Log.d(TAG, "DDS: no change");
+ }
+ return;
+ }
+
+ mDefaultDataSubId = defaultDataSubId;
+ if (DEBUG) {
+ Log.d(TAG, "DDS: defaultDataSubId:" + mDefaultDataSubId);
+ }
+ if (SubscriptionManager.isUsableSubscriptionId(mDefaultDataSubId)) {
+ mTelephonyManager.unregisterTelephonyCallback(mInternetTelephonyCallback);
+ mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
+ mTelephonyManager.registerTelephonyCallback(mHandler::post,
+ mInternetTelephonyCallback);
+ mCallback.onSubscriptionsChanged(mDefaultDataSubId);
+ }
+ }
+
+ public WifiUtils.InternetIconInjector getWifiIconInjector() {
+ return mWifiIconInjector;
+ }
+
+ interface InternetDialogCallback {
+
+ void onRefreshCarrierInfo();
+
+ void onSimStateChanged();
+
+ void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities);
+
+ void onLost(@NonNull Network network);
+
+ void onSubscriptionsChanged(int defaultDataSubId);
+
+ void onServiceStateChanged(ServiceState serviceState);
+
+ void onDataConnectionStateChanged(int state, int networkType);
+
+ void onSignalStrengthsChanged(SignalStrength signalStrength);
+
+ void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo);
+
+ void dismissDialog();
+
+ void onAccessPointsChanged(@Nullable List<WifiEntry> wifiEntries,
+ @Nullable WifiEntry connectedEntry);
+ }
+
+ void makeOverlayToast(int stringId) {
+ final Resources res = mContext.getResources();
+
+ final SystemUIToast systemUIToast = mToastFactory.createToast(mContext,
+ res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(),
+ res.getConfiguration().orientation);
+ if (systemUIToast == null) {
+ return;
+ }
+
+ View toastView = systemUIToast.getView();
+
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+ params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ params.format = PixelFormat.TRANSLUCENT;
+ params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+ params.y = systemUIToast.getYOffset();
+
+ int absGravity = Gravity.getAbsoluteGravity(systemUIToast.getGravity(),
+ res.getConfiguration().getLayoutDirection());
+ params.gravity = absGravity;
+ if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
+ params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT;
+ }
+ if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
+ params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT;
+ }
+
+ mWindowManager.addView(toastView, params);
+
+ Animator inAnimator = systemUIToast.getInAnimation();
+ if (inAnimator != null) {
+ inAnimator.start();
+ }
+
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ Animator outAnimator = systemUIToast.getOutAnimation();
+ if (outAnimator != null) {
+ outAnimator.start();
+ outAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ mWindowManager.removeViewImmediate(toastView);
+ }
+ });
+ }
+ }
+ }, SHORT_DURATION_TIMEOUT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
new file mode 100644
index 000000000000..11c6980678b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.qs.tiles.dialog
+
+import android.content.Context
+import android.os.Handler
+import android.util.Log
+import com.android.internal.logging.UiEventLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import javax.inject.Inject
+
+private const val TAG = "InternetDialogFactory"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+/**
+ * Factory to create [InternetDialog] objects.
+ */
+@SysUISingleton
+class InternetDialogFactory @Inject constructor(
+ @Main private val handler: Handler,
+ private val internetDialogController: InternetDialogController,
+ private val context: Context,
+ private val uiEventLogger: UiEventLogger
+) {
+ companion object {
+ var internetDialog: InternetDialog? = null
+ }
+
+ /** Creates a [InternetDialog]. */
+ fun create(aboveStatusBar: Boolean, canConfigMobileData: Boolean, canConfigWifi: Boolean) {
+ if (internetDialog != null) {
+ if (DEBUG) {
+ Log.d(TAG, "InternetDialog is showing, do not create it twice.")
+ }
+ return
+ } else {
+ internetDialog = InternetDialog(context, this, internetDialogController,
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler)
+ internetDialog?.show()
+ }
+ }
+
+ fun destroyDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "destroyDialog")
+ }
+ internetDialog = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
new file mode 100644
index 000000000000..6aaba997faad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogUtil.java
@@ -0,0 +1,14 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import android.content.Context;
+import android.util.FeatureFlagUtils;
+
+public class InternetDialogUtil {
+
+ public static boolean isProviderModelEnabled(Context context) {
+ if (context == null) {
+ return false;
+ }
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 26781f4ccf09..2133cf63d1c3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -29,8 +29,8 @@ import android.content.Context;
import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
@@ -187,77 +187,63 @@ public class ScreenMediaRecorder {
* @param refreshRate Desired refresh rate
* @return array with supported width, height, and refresh rate
*/
- private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate) {
- double maxScale = 0;
-
- MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
- MediaCodecInfo.VideoCapabilities maxInfo = null;
- for (MediaCodecInfo codec : codecList.getCodecInfos()) {
- String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
- String[] types = codec.getSupportedTypes();
- for (String t : types) {
- if (!t.equalsIgnoreCase(videoType)) {
- continue;
- }
- MediaCodecInfo.CodecCapabilities capabilities =
- codec.getCapabilitiesForType(videoType);
- if (capabilities != null && capabilities.getVideoCapabilities() != null) {
- MediaCodecInfo.VideoCapabilities vc = capabilities.getVideoCapabilities();
-
- int width = vc.getSupportedWidths().getUpper();
- int height = vc.getSupportedHeights().getUpper();
-
- int screenWidthAligned = screenWidth;
- if (screenWidthAligned % vc.getWidthAlignment() != 0) {
- screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
- }
- int screenHeightAligned = screenHeight;
- if (screenHeightAligned % vc.getHeightAlignment() != 0) {
- screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
- }
-
- if (width >= screenWidthAligned && height >= screenHeightAligned
- && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
- // Desired size is supported, now get the rate
- int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
- screenHeightAligned).getUpper().intValue();
-
- if (maxRate < refreshRate) {
- refreshRate = maxRate;
- }
- Log.d(TAG, "Screen size supported at rate " + refreshRate);
- return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
- }
-
- // Otherwise, continue searching
- double scale = Math.min(((double) width / screenWidth),
- ((double) height / screenHeight));
- if (scale > maxScale) {
- maxScale = Math.min(1, scale);
- maxInfo = vc;
- }
- }
+ private int[] getSupportedSize(final int screenWidth, final int screenHeight, int refreshRate)
+ throws IOException {
+ String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+ // Get max size from the decoder, to ensure recordings will be playable on device
+ MediaCodec decoder = MediaCodec.createDecoderByType(videoType);
+ MediaCodecInfo.VideoCapabilities vc = decoder.getCodecInfo()
+ .getCapabilitiesForType(videoType).getVideoCapabilities();
+ decoder.release();
+
+ // Check if we can support screen size as-is
+ int width = vc.getSupportedWidths().getUpper();
+ int height = vc.getSupportedHeights().getUpper();
+
+ int screenWidthAligned = screenWidth;
+ if (screenWidthAligned % vc.getWidthAlignment() != 0) {
+ screenWidthAligned -= (screenWidthAligned % vc.getWidthAlignment());
+ }
+ int screenHeightAligned = screenHeight;
+ if (screenHeightAligned % vc.getHeightAlignment() != 0) {
+ screenHeightAligned -= (screenHeightAligned % vc.getHeightAlignment());
+ }
+
+ if (width >= screenWidthAligned && height >= screenHeightAligned
+ && vc.isSizeSupported(screenWidthAligned, screenHeightAligned)) {
+ // Desired size is supported, now get the rate
+ int maxRate = vc.getSupportedFrameRatesFor(screenWidthAligned,
+ screenHeightAligned).getUpper().intValue();
+
+ if (maxRate < refreshRate) {
+ refreshRate = maxRate;
}
+ Log.d(TAG, "Screen size supported at rate " + refreshRate);
+ return new int[]{screenWidthAligned, screenHeightAligned, refreshRate};
}
- // Resize for max supported size
- int scaledWidth = (int) (screenWidth * maxScale);
- int scaledHeight = (int) (screenHeight * maxScale);
- if (scaledWidth % maxInfo.getWidthAlignment() != 0) {
- scaledWidth -= (scaledWidth % maxInfo.getWidthAlignment());
+ // Otherwise, resize for max supported size
+ double scale = Math.min(((double) width / screenWidth),
+ ((double) height / screenHeight));
+
+ int scaledWidth = (int) (screenWidth * scale);
+ int scaledHeight = (int) (screenHeight * scale);
+ if (scaledWidth % vc.getWidthAlignment() != 0) {
+ scaledWidth -= (scaledWidth % vc.getWidthAlignment());
}
- if (scaledHeight % maxInfo.getHeightAlignment() != 0) {
- scaledHeight -= (scaledHeight % maxInfo.getHeightAlignment());
+ if (scaledHeight % vc.getHeightAlignment() != 0) {
+ scaledHeight -= (scaledHeight % vc.getHeightAlignment());
}
// Find max supported rate for size
- int maxRate = maxInfo.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
+ int maxRate = vc.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
.getUpper().intValue();
if (maxRate < refreshRate) {
refreshRate = maxRate;
}
- Log.d(TAG, "Resized by " + maxScale + ": " + scaledWidth + ", " + scaledHeight
+ Log.d(TAG, "Resized by " + scale + ": " + scaledWidth + ", " + scaledHeight
+ ", " + refreshRate);
return new int[]{scaledWidth, scaledHeight, refreshRate};
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 0eaef72ae29b..31d51f1d1a60 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -154,6 +154,7 @@ public class LongScreenshotActivity extends Activity {
@Override
public void onStart() {
super.onStart();
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED);
if (mPreview.getDrawable() != null) {
// We already have an image, so no need to try to load again.
@@ -245,6 +246,8 @@ public class LongScreenshotActivity extends Activity {
}
private void onCachedImageLoaded(ImageLoader.Result imageResult) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED);
+
BitmapDrawable drawable = new BitmapDrawable(getResources(), imageResult.bitmap);
mPreview.setImageDrawable(drawable);
mPreview.setAlpha(1f);
@@ -282,6 +285,8 @@ public class LongScreenshotActivity extends Activity {
finish();
}
if (isFinishing()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED);
+
if (mScrollCaptureResponse != null) {
mScrollCaptureResponse.close();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 16872b08b9c8..8def475c192c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -33,6 +33,7 @@ import static java.util.Objects.requireNonNull;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -261,6 +262,7 @@ public class ScreenshotController {
private Bitmap mScreenBitmap;
private SaveImageInBackgroundTask mSaveInBgTask;
private boolean mScreenshotTakenInPortrait;
+ private boolean mBlockAttach;
private Animator mScreenshotAnimation;
private RequestCallback mCurrentRequestCallback;
@@ -559,8 +561,8 @@ public class ScreenshotController {
mScreenshotView.reset();
}
- mScreenshotView.updateOrientation(mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets().getDisplayCutout());
+ mScreenshotView.updateOrientation(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
mScreenBitmap = screenshot;
@@ -594,9 +596,8 @@ public class ScreenshotController {
// Delay scroll capture eval a bit to allow the underlying activity
// to set up in the new orientation.
mScreenshotHandler.postDelayed(this::requestScrollCapture, 150);
- mScreenshotView.updateDisplayCutoutMargins(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets()
- .getDisplayCutout());
+ mScreenshotView.updateInsets(
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets());
// screenshot animation calculations won't be valid anymore, so just end
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.end();
@@ -660,7 +661,7 @@ public class ScreenshotController {
+ mLastScrollCaptureResponse.getWindowTitle() + "]");
final ScrollCaptureResponse response = mLastScrollCaptureResponse;
- mScreenshotView.showScrollChip(/* onClick */ () -> {
+ mScreenshotView.showScrollChip(response.getPackageName(), /* onClick */ () -> {
DisplayMetrics displayMetrics = new DisplayMetrics();
getDefaultDisplay().getRealMetrics(displayMetrics);
Bitmap newScreenshot = captureScreenshot(
@@ -732,6 +733,7 @@ public class ScreenshotController {
new ViewTreeObserver.OnWindowAttachListener() {
@Override
public void onWindowAttached() {
+ mBlockAttach = false;
decorView.getViewTreeObserver().removeOnWindowAttachListener(this);
action.run();
}
@@ -748,14 +750,16 @@ public class ScreenshotController {
mWindow.setContentView(contentView);
}
+ @MainThread
private void attachWindow() {
View decorView = mWindow.getDecorView();
- if (decorView.isAttachedToWindow()) {
+ if (decorView.isAttachedToWindow() || mBlockAttach) {
return;
}
if (DEBUG_WINDOW) {
Log.d(TAG, "attachWindow");
}
+ mBlockAttach = true;
mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
index 5cf018813133..169b28c6b373 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -71,7 +71,19 @@ public enum ScreenshotEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "User has shared a long screenshot")
SCREENSHOT_LONG_SCREENSHOT_SHARE(689),
@UiEvent(doc = "User has sent a long screenshot to the editor")
- SCREENSHOT_LONG_SCREENSHOT_EDIT(690);
+ SCREENSHOT_LONG_SCREENSHOT_EDIT(690),
+ @UiEvent(doc = "A long screenshot capture has started")
+ SCREENSHOT_LONG_SCREENSHOT_STARTED(880),
+ @UiEvent(doc = "The long screenshot capture failed")
+ SCREENSHOT_LONG_SCREENSHOT_FAILURE(881),
+ @UiEvent(doc = "The long screenshot capture completed successfully")
+ SCREENSHOT_LONG_SCREENSHOT_COMPLETED(882),
+ @UiEvent(doc = "Long screenshot editor activity started")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_STARTED(889),
+ @UiEvent(doc = "Long screenshot editor activity loaded a previously saved screenshot")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_CACHED_IMAGE_LOADED(890),
+ @UiEvent(doc = "Long screenshot editor activity finished")
+ SCREENSHOT_LONG_SCREENSHOT_ACTIVITY_FINISHED(891);
private final int mId;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index e9e62f26a10e..dfb39e300450 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -118,8 +118,8 @@ public class ScreenshotView extends FrameLayout implements
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
+ private static final long SCREENSHOT_DISMISS_X_DURATION_MS = 350;
+ private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 350;
private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final float ROUNDED_CORNER_RADIUS = .25f;
@@ -242,19 +242,21 @@ public class ScreenshotView extends FrameLayout implements
/**
* Called to display the scroll action chip when support is detected.
*
+ * @param packageName the owning package of the window to be captured
* @param onClick the action to take when the chip is clicked.
*/
- public void showScrollChip(Runnable onClick) {
+ public void showScrollChip(String packageName, Runnable onClick) {
if (DEBUG_SCROLL) {
Log.d(TAG, "Showing Scroll option");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_IMPRESSION, 0, packageName);
mScrollChip.setVisibility(VISIBLE);
mScrollChip.setOnClickListener((v) -> {
if (DEBUG_INPUT) {
Log.d(TAG, "scroll chip tapped");
}
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED);
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_REQUESTED, 0,
+ packageName);
onClick.run();
});
}
@@ -414,21 +416,30 @@ public class ScreenshotView extends FrameLayout implements
mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
}
- void updateDisplayCutoutMargins(DisplayCutout cutout) {
+ void updateInsets(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
FrameLayout.LayoutParams p =
(FrameLayout.LayoutParams) mScreenshotStatic.getLayoutParams();
+ DisplayCutout cutout = insets.getDisplayCutout();
+ Insets navBarInsets = insets.getInsets(WindowInsets.Type.navigationBars());
if (cutout == null) {
- p.setMargins(0, 0, 0, 0);
+ p.setMargins(0, 0, 0, navBarInsets.bottom);
} else {
Insets waterfall = cutout.getWaterfallInsets();
if (mOrientationPortrait) {
- p.setMargins(waterfall.left, Math.max(cutout.getSafeInsetTop(), waterfall.top),
- waterfall.right, Math.max(cutout.getSafeInsetBottom(), waterfall.bottom));
+ p.setMargins(
+ waterfall.left,
+ Math.max(cutout.getSafeInsetTop(), waterfall.top),
+ waterfall.right,
+ Math.max(cutout.getSafeInsetBottom(),
+ Math.max(navBarInsets.bottom, waterfall.bottom)));
} else {
- p.setMargins(Math.max(cutout.getSafeInsetLeft(), waterfall.left), waterfall.top,
- Math.max(cutout.getSafeInsetRight(), waterfall.right), waterfall.bottom);
+ p.setMargins(
+ Math.max(cutout.getSafeInsetLeft(), waterfall.left),
+ waterfall.top,
+ Math.max(cutout.getSafeInsetRight(), waterfall.right),
+ Math.max(navBarInsets.bottom, waterfall.bottom));
}
}
mStaticLeftMargin = p.leftMargin;
@@ -436,10 +447,10 @@ public class ScreenshotView extends FrameLayout implements
mScreenshotStatic.requestLayout();
}
- void updateOrientation(DisplayCutout cutout) {
+ void updateOrientation(WindowInsets insets) {
int orientation = mContext.getResources().getConfiguration().orientation;
mOrientationPortrait = (orientation == ORIENTATION_PORTRAIT);
- updateDisplayCutoutMargins(cutout);
+ updateInsets(insets);
int screenshotFixedSize =
mContext.getResources().getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
ViewGroup.LayoutParams params = mScreenshotPreview.getLayoutParams();
@@ -978,7 +989,6 @@ public class ScreenshotView extends FrameLayout implements
mScrollingScrim.setVisibility(View.GONE);
mScrollablePreview.setVisibility(View.GONE);
mScreenshotStatic.setTranslationX(0);
- mScreenshotPreview.setTranslationY(0);
mScreenshotPreview.setContentDescription(
mContext.getResources().getString(R.string.screenshot_preview_description));
mScreenshotPreview.setOnClickListener(null);
@@ -994,9 +1004,6 @@ public class ScreenshotView extends FrameLayout implements
mSmartChips.clear();
mQuickShareChip = null;
setAlpha(1);
- mDismissButton.setTranslationY(0);
- mActionsContainer.setTranslationY(0);
- mActionsContainerBackground.setTranslationY(0);
mScreenshotSelectorView.stop();
}
@@ -1024,22 +1031,19 @@ public class ScreenshotView extends FrameLayout implements
setAlpha(1 - animation.getAnimatedFraction());
});
- ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
- yAnim.setInterpolator(mAccelerateInterpolator);
- yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
- float screenshotStartY = mScreenshotPreview.getTranslationY();
- float dismissStartY = mDismissButton.getTranslationY();
- yAnim.addUpdateListener(animation -> {
- float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
- mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
- mScreenshotPreviewBorder.setTranslationY(screenshotStartY + yDelta);
- mDismissButton.setTranslationY(dismissStartY + yDelta);
- mActionsContainer.setTranslationY(yDelta);
- mActionsContainerBackground.setTranslationY(yDelta);
+ ValueAnimator xAnim = ValueAnimator.ofFloat(0, 1);
+ xAnim.setInterpolator(mAccelerateInterpolator);
+ xAnim.setDuration(SCREENSHOT_DISMISS_X_DURATION_MS);
+ float deltaX = mDirectionLTR
+ ? -1 * (mScreenshotPreviewBorder.getX() + mScreenshotPreviewBorder.getWidth())
+ : (mDisplayMetrics.widthPixels - mScreenshotPreviewBorder.getX());
+ xAnim.addUpdateListener(animation -> {
+ float currXDelta = MathUtils.lerp(0, deltaX, animation.getAnimatedFraction());
+ mScreenshotStatic.setTranslationX(currXDelta);
});
AnimatorSet animSet = new AnimatorSet();
- animSet.play(yAnim).with(alphaAnim);
+ animSet.play(xAnim).with(alphaAnim);
return animSet;
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 6dc68746e3ec..ef7355a09fbf 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -28,6 +28,7 @@ import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.screenshot.ScrollCaptureClient.CaptureResult;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -61,6 +62,7 @@ public class ScrollCaptureController {
private final Context mContext;
private final Executor mBgExecutor;
private final ImageTileSet mImageTileSet;
+ private final UiEventLogger mEventLogger;
private final ScrollCaptureClient mClient;
private Completer<LongScreenshot> mCaptureCompleter;
@@ -69,6 +71,7 @@ public class ScrollCaptureController {
private Session mSession;
private ListenableFuture<CaptureResult> mTileFuture;
private ListenableFuture<Void> mEndFuture;
+ private String mWindowOwner;
static class LongScreenshot {
private final ImageTileSet mImageTileSet;
@@ -135,11 +138,12 @@ public class ScrollCaptureController {
@Inject
ScrollCaptureController(Context context, @Background Executor bgExecutor,
- ScrollCaptureClient client, ImageTileSet imageTileSet) {
+ ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) {
mContext = context;
mBgExecutor = bgExecutor;
mClient = client;
mImageTileSet = imageTileSet;
+ mEventLogger = logger;
}
@VisibleForTesting
@@ -157,6 +161,7 @@ public class ScrollCaptureController {
ListenableFuture<LongScreenshot> run(ScrollCaptureResponse response) {
return CallbackToFutureAdapter.getFuture(completer -> {
mCaptureCompleter = completer;
+ mWindowOwner = response.getPackageName();
mBgExecutor.execute(() -> {
float maxPages = Settings.Secure.getFloat(mContext.getContentResolver(),
SETTING_KEY_MAX_PAGES, MAX_PAGES_DEFAULT);
@@ -173,11 +178,13 @@ public class ScrollCaptureController {
if (LogConfig.DEBUG_SCROLL) {
Log.d(TAG, "got session " + mSession);
}
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_STARTED, 0, mWindowOwner);
requestNextTile(0);
} catch (InterruptedException | ExecutionException e) {
// Failure to start, propagate to caller
Log.e(TAG, "session start failed!");
mCaptureCompleter.setException(e);
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
}
}
@@ -297,6 +304,11 @@ public class ScrollCaptureController {
if (LogConfig.DEBUG_SCROLL) {
Log.d(TAG, "finishCapture()");
}
+ if (mImageTileSet.getHeight() > 0) {
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_COMPLETED, 0, mWindowOwner);
+ } else {
+ mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
+ }
mEndFuture = mSession.end();
mEndFuture.addListener(() -> {
if (LogConfig.DEBUG_SCROLL) {
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
new file mode 100644
index 000000000000..c50365f1bf38
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt
@@ -0,0 +1,74 @@
+package com.android.systemui.sensorprivacy
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.res.Resources
+import android.text.Html
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.ImageView
+import com.android.internal.widget.DialogTitle
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class SensorUseDialog(
+ context: Context,
+ val sensor: Int,
+ val clickListener: DialogInterface.OnClickListener
+) : SystemUIDialog(context) {
+
+ // TODO move to onCreate (b/200815309)
+ init {
+ window!!.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
+ window!!.addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+
+ val layoutInflater = LayoutInflater.from(context)
+ val customTitleView = layoutInflater.inflate(R.layout.sensor_use_started_title, null)
+ customTitleView.requireViewById<DialogTitle>(R.id.sensor_use_started_title_message)
+ .setText(when (sensor) {
+ SensorUseStartedActivity.MICROPHONE ->
+ R.string.sensor_privacy_start_use_mic_dialog_title
+ SensorUseStartedActivity.CAMERA ->
+ R.string.sensor_privacy_start_use_camera_dialog_title
+ SensorUseStartedActivity.ALL_SENSORS ->
+ R.string.sensor_privacy_start_use_mic_camera_dialog_title
+ else -> Resources.ID_NULL
+ })
+ customTitleView.requireViewById<ImageView>(R.id.sensor_use_microphone_icon).visibility =
+ if (sensor == SensorUseStartedActivity.MICROPHONE ||
+ sensor == SensorUseStartedActivity.ALL_SENSORS) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+ customTitleView.requireViewById<ImageView>(R.id.sensor_use_camera_icon).visibility =
+ if (sensor == SensorUseStartedActivity.CAMERA ||
+ sensor == SensorUseStartedActivity.ALL_SENSORS) {
+ View.VISIBLE
+ } else {
+ View.GONE
+ }
+
+ setCustomTitle(customTitleView)
+ setMessage(Html.fromHtml(context.getString(when (sensor) {
+ SensorUseStartedActivity.MICROPHONE ->
+ R.string.sensor_privacy_start_use_mic_dialog_content
+ SensorUseStartedActivity.CAMERA ->
+ R.string.sensor_privacy_start_use_camera_dialog_content
+ SensorUseStartedActivity.ALL_SENSORS ->
+ R.string.sensor_privacy_start_use_mic_camera_dialog_content
+ else -> Resources.ID_NULL
+ }), 0))
+
+ setButton(BUTTON_POSITIVE,
+ context.getString(com.android.internal.R.string
+ .sensor_privacy_start_use_dialog_turn_on_button), clickListener)
+ setButton(BUTTON_NEGATIVE,
+ context.getString(com.android.internal.R.string
+ .cancel), clickListener)
+
+ setCancelable(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index f0fb5ebf9e1d..b0071d92481d 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -16,33 +16,28 @@
package com.android.systemui.sensorprivacy
+import android.app.Activity
+import android.app.AlertDialog
import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_POSITIVE
import android.content.Intent
import android.content.Intent.EXTRA_PACKAGE_NAME
-import android.content.pm.PackageManager
-import android.content.res.Resources
import android.hardware.SensorPrivacyManager
import android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS
import android.hardware.SensorPrivacyManager.EXTRA_SENSOR
import android.hardware.SensorPrivacyManager.Sources.DIALOG
import android.os.Bundle
import android.os.Handler
-import android.text.Html
-import android.view.View.GONE
-import android.view.View.VISIBLE
-import android.widget.ImageView
-import com.android.internal.app.AlertActivity
-import com.android.internal.widget.DialogTitle
-import com.android.systemui.R
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
+import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
+import com.android.internal.util.FrameworkStatsLog.write
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__ENABLE
-import com.android.internal.util.FrameworkStatsLog.PRIVACY_TOGGLE_DIALOG_INTERACTION__ACTION__CANCEL
-import com.android.internal.util.FrameworkStatsLog.write
/**
* Dialog to be shown on top of apps that are attempting to use a sensor (e.g. microphone) which is
@@ -55,7 +50,7 @@ class SensorUseStartedActivity @Inject constructor(
private val keyguardStateController: KeyguardStateController,
private val keyguardDismissUtil: KeyguardDismissUtil,
@Background private val bgHandler: Handler
-) : AlertActivity(), DialogInterface.OnClickListener {
+) : Activity(), DialogInterface.OnClickListener {
companion object {
private val LOG_TAG = SensorUseStartedActivity::class.java.simpleName
@@ -63,9 +58,9 @@ class SensorUseStartedActivity @Inject constructor(
private const val SUPPRESS_REMINDERS_REMOVAL_DELAY_MILLIS = 2000L
private const val UNLOCK_DELAY_MILLIS = 200L
- private const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
- private const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
- private const val ALL_SENSORS = Integer.MAX_VALUE
+ internal const val CAMERA = SensorPrivacyManager.Sensors.CAMERA
+ internal const val MICROPHONE = SensorPrivacyManager.Sensors.MICROPHONE
+ internal const val ALL_SENSORS = Integer.MAX_VALUE
}
private var sensor = -1
@@ -74,6 +69,8 @@ class SensorUseStartedActivity @Inject constructor(
private lateinit var sensorPrivacyListener: IndividualSensorPrivacyController.Callback
+ private var mDialog: AlertDialog? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -91,7 +88,7 @@ class SensorUseStartedActivity @Inject constructor(
IndividualSensorPrivacyController.Callback { _, _ ->
if (!sensorPrivacyController.isSensorBlocked(MICROPHONE) &&
!sensorPrivacyController.isSensorBlocked(CAMERA)) {
- dismiss()
+ finish()
}
}
@@ -109,71 +106,22 @@ class SensorUseStartedActivity @Inject constructor(
}
}
sensorPrivacyListener =
- IndividualSensorPrivacyController.Callback {
- whichSensor: Int, isBlocked: Boolean ->
+ IndividualSensorPrivacyController.Callback { whichSensor: Int,
+ isBlocked: Boolean ->
if (whichSensor == sensor && !isBlocked) {
- dismiss()
+ finish()
}
}
sensorPrivacyController.addCallback(sensorPrivacyListener)
- sensorPrivacyController.addCallback { _, isBlocked ->
- if (!isBlocked) {
- dismiss()
- }
- }
- }
-
- mAlertParams.apply {
- try {
- mCustomTitleView = mInflater.inflate(R.layout.sensor_use_started_title, null)
- mCustomTitleView.findViewById<DialogTitle>(R.id.sensor_use_started_title_message)!!
- .setText(when (sensor) {
- MICROPHONE ->
- R.string.sensor_privacy_start_use_mic_dialog_title
- CAMERA ->
- R.string.sensor_privacy_start_use_camera_dialog_title
- ALL_SENSORS ->
- R.string.sensor_privacy_start_use_mic_camera_dialog_title
- else -> Resources.ID_NULL
- })
-
- mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_microphone_icon)!!
- .visibility = if (sensor == MICROPHONE || sensor == ALL_SENSORS) {
- VISIBLE
- } else {
- GONE
- }
- mCustomTitleView.findViewById<ImageView>(R.id.sensor_use_camera_icon)!!
- .visibility = if (sensor == CAMERA || sensor == ALL_SENSORS) {
- VISIBLE
- } else {
- GONE
- }
-
- mMessage = Html.fromHtml(getString(when (sensor) {
- MICROPHONE ->
- R.string.sensor_privacy_start_use_mic_dialog_content
- CAMERA ->
- R.string.sensor_privacy_start_use_camera_dialog_content
- ALL_SENSORS ->
- R.string.sensor_privacy_start_use_mic_camera_dialog_content
- else -> Resources.ID_NULL
- }, packageManager.getApplicationInfo(sensorUsePackageName, 0)
- .loadLabel(packageManager)), 0)
- } catch (e: PackageManager.NameNotFoundException) {
+ if (!sensorPrivacyController.isSensorBlocked(sensor)) {
finish()
return
}
-
- mPositiveButtonText = getString(
- com.android.internal.R.string.sensor_privacy_start_use_dialog_turn_on_button)
- mNegativeButtonText = getString(android.R.string.cancel)
- mPositiveButtonListener = this@SensorUseStartedActivity
- mNegativeButtonListener = this@SensorUseStartedActivity
}
- setupAlert()
+ mDialog = SensorUseDialog(this, sensor, this)
+ mDialog!!.show()
}
override fun onStart() {
@@ -212,7 +160,7 @@ class SensorUseStartedActivity @Inject constructor(
}
}
- dismiss()
+ finish()
}
override fun onStop() {
@@ -229,6 +177,7 @@ class SensorUseStartedActivity @Inject constructor(
override fun onDestroy() {
super.onDestroy()
+ mDialog?.dismiss()
sensorPrivacyController.removeCallback(sensorPrivacyListener)
}
@@ -263,4 +212,4 @@ class SensorUseStartedActivity @Inject constructor(
.suppressSensorPrivacyReminders(sensor, suppressed)
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index 8cd3632b65ba..cc5cf4b63f99 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -24,6 +24,7 @@ import android.hardware.SensorPrivacyManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
+import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -57,6 +58,8 @@ public class TvUnblockSensorActivity extends TvBottomSheetActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ getWindow().addSystemFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index dce19cf86b35..cfbe3b29783a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -54,22 +54,22 @@ open class BlurUtils @Inject constructor(
/**
* Translates a ratio from 0 to 1 to a blur radius in pixels.
*/
- fun blurRadiusOfRatio(ratio: Float): Int {
+ fun blurRadiusOfRatio(ratio: Float): Float {
if (ratio == 0f) {
- return 0
+ return 0f
}
- return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio).toInt()
+ return MathUtils.lerp(minBlurRadius.toFloat(), maxBlurRadius.toFloat(), ratio)
}
/**
* Translates a blur radius in pixels to a ratio between 0 to 1.
*/
- fun ratioOfBlurRadius(blur: Int): Float {
- if (blur == 0) {
+ fun ratioOfBlurRadius(blur: Float): Float {
+ if (blur == 0f) {
return 0f
}
return MathUtils.map(minBlurRadius.toFloat(), maxBlurRadius.toFloat(),
- 0f /* maxStart */, 1f /* maxStop */, blur.toFloat())
+ 0f /* maxStart */, 1f /* maxStop */, blur)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 8e52b0da54ef..50911d162113 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -290,8 +290,8 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showAuthenticationDialog(PromptInfo promptInfo,
IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed,
- boolean requireConfirmation, int userId, String opPackageName,
- long operationId, @BiometricMultiSensorMode int multiSensorConfig) {
+ boolean requireConfirmation, int userId, long operationId, String opPackageName,
+ long requestId, @BiometricMultiSensorMode int multiSensorConfig) {
}
/** @see IStatusBar#onBiometricAuthenticated() */
@@ -843,7 +843,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
@@ -855,6 +855,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
args.argi1 = userId;
args.arg6 = opPackageName;
args.arg7 = operationId;
+ args.arg8 = requestId;
args.argi2 = multiSensorConfig;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
@@ -1312,8 +1313,9 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
(boolean) someArgs.arg4 /* credentialAllowed */,
(boolean) someArgs.arg5 /* requireConfirmation */,
someArgs.argi1 /* userId */,
- (String) someArgs.arg6 /* opPackageName */,
(long) someArgs.arg7 /* operationId */,
+ (String) someArgs.arg6 /* opPackageName */,
+ (long) someArgs.arg8 /* requestId */,
someArgs.argi2 /* multiSensorConfig */);
}
someArgs.recycle();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 503b5c0ee4b0..8a397199dc84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -66,6 +66,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.ViewClippingUtil;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.settingslib.Utils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
@@ -334,7 +335,7 @@ public class KeyguardIndicationController {
info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
}
}
- if (info != null) {
+ if (!TextUtils.isEmpty(info)) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_OWNER_INFO,
new KeyguardIndication.Builder()
@@ -432,7 +433,7 @@ public class KeyguardIndicationController {
}
private void updateResting() {
- if (mRestingIndication != null
+ if (!TextUtils.isEmpty(mRestingIndication)
&& !mRotateTextViewController.hasIndications()) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_RESTING,
@@ -455,7 +456,8 @@ public class KeyguardIndicationController {
new KeyguardIndication.Builder()
.setMessage(mContext.getResources().getString(
com.android.internal.R.string.global_action_logout))
- .setTextColor(mInitialTextColorState)
+ .setTextColor(Utils.getColorAttr(
+ mContext, com.android.internal.R.attr.textColorOnAccent))
.setBackground(mContext.getDrawable(
com.android.systemui.R.drawable.logout_button_background))
.setClickListener((view) -> {
@@ -723,15 +725,13 @@ public class KeyguardIndicationController {
}
protected String computePowerIndication() {
- if (mPowerCharged) {
- return mContext.getResources().getString(R.string.keyguard_charged);
- }
-
int chargingId;
- String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (mBatteryOverheated) {
chargingId = R.string.keyguard_plugged_in_charging_limited;
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(chargingId, percentage);
+ } else if (mPowerCharged) {
+ return mContext.getResources().getString(R.string.keyguard_charged);
}
final boolean hasChargingTime = mChargingTimeRemaining > 0;
@@ -759,6 +759,7 @@ public class KeyguardIndicationController {
: R.string.keyguard_plugged_in_wireless;
}
+ String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
if (hasChargingTime) {
String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
mContext, mChargingTimeRemaining);
@@ -799,7 +800,9 @@ public class KeyguardIndicationController {
* Show message on the keyguard for how the user can unlock/enter their device.
*/
public void showActionToUnlock() {
- if (mDozing) {
+ if (mDozing
+ && !mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser())) {
return;
}
@@ -810,18 +813,13 @@ public class KeyguardIndicationController {
String message = mContext.getString(R.string.keyguard_retry);
mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
}
- } else if (mKeyguardUpdateMonitor.isScreenOn()) {
- if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
- showTransientIndication(mContext.getString(R.string.keyguard_unlock_press),
- false /* isError */, true /* hideOnScreenOff */);
- } else {
- showTransientIndication(mContext.getString(R.string.keyguard_unlock),
- false /* isError */, true /* hideOnScreenOff */);
- }
+ } else {
+ showTransientIndication(mContext.getString(R.string.keyguard_unlock),
+ false /* isError */, true /* hideOnScreenOff */);
}
}
- private void showTryFingerprintMsg() {
+ private void showTryFingerprintMsg(String a11yString) {
if (mKeyguardUpdateMonitor.isUdfpsAvailable()) {
// if udfps available, there will always be a tappable affordance to unlock
// For example, the lock icon
@@ -833,6 +831,11 @@ public class KeyguardIndicationController {
} else {
showTransientIndication(R.string.keyguard_try_fingerprint);
}
+
+ // Although we suppress face auth errors visually, we still announce them for a11y
+ if (!TextUtils.isEmpty(a11yString)) {
+ mLockScreenIndicationView.announceForAccessibility(a11yString);
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -913,7 +916,7 @@ public class KeyguardIndicationController {
} else if (mKeyguardUpdateMonitor.isScreenOn()) {
if (biometricSourceType == BiometricSourceType.FACE
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(helpString);
return;
}
showTransientIndication(helpString, false /* isError */, showActionToUnlock);
@@ -933,7 +936,7 @@ public class KeyguardIndicationController {
&& shouldSuppressFaceMsgAndShowTryFingerprintMsg()
&& !mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isScreenOn()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
@@ -942,7 +945,7 @@ public class KeyguardIndicationController {
if (!mStatusBarKeyguardViewManager.isBouncerShowing()
&& mKeyguardUpdateMonitor.isUdfpsEnrolled()
&& mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
- showTryFingerprintMsg();
+ showTryFingerprintMsg(errString);
} else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
mStatusBarKeyguardViewManager.showBouncerMessage(
mContext.getResources().getString(R.string.keyguard_unlock_press),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 538db6168408..21ed9da896a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -149,7 +149,10 @@ class PowerButtonReveal(
*/
class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
- lateinit var revealAmountListener: Consumer<Float>
+ /**
+ * Listener that is called if the scrim's opaqueness changes
+ */
+ lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
/**
* How much of the underlying views are revealed, in percent. 0 means they will be completely
@@ -161,7 +164,7 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
field = value
revealEffect.setRevealAmountOnScrim(value, this)
- revealAmountListener.accept(value)
+ updateScrimOpaque()
invalidate()
}
}
@@ -201,6 +204,31 @@ class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context,
}
/**
+ * Is the scrim currently fully opaque
+ */
+ var isScrimOpaque = false
+ private set(value) {
+ if (field != value) {
+ field = value
+ isScrimOpaqueChangedListener.accept(field)
+ }
+ }
+
+ private fun updateScrimOpaque() {
+ isScrimOpaque = revealAmount == 0.0f && alpha == 1.0f && visibility == VISIBLE
+ }
+
+ override fun setAlpha(alpha: Float) {
+ super.setAlpha(alpha)
+ updateScrimOpaque()
+ }
+
+ override fun setVisibility(visibility: Int) {
+ super.setVisibility(visibility)
+ updateScrimOpaque()
+ }
+
+ /**
* Paint used to draw a transparent-to-white radial gradient. This will be scaled and translated
* via local matrix in [onDraw] so we never need to construct a new shader.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 002c9c7d2544..2a8771e96e7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -68,7 +68,7 @@ class NotificationShadeDepthController @Inject constructor(
private const val VELOCITY_SCALE = 100f
private const val MAX_VELOCITY = 3000f
private const val MIN_VELOCITY = -MAX_VELOCITY
- private const val INTERACTION_BLUR_FRACTION = 0.4f
+ private const val INTERACTION_BLUR_FRACTION = 0.8f
private const val ANIMATION_BLUR_FRACTION = 1f - INTERACTION_BLUR_FRACTION
private const val TAG = "DepthController"
}
@@ -78,7 +78,8 @@ class NotificationShadeDepthController @Inject constructor(
private var keyguardAnimator: Animator? = null
private var notificationAnimator: Animator? = null
private var updateScheduled: Boolean = false
- private var shadeExpansion = 0f
+ @VisibleForTesting
+ var shadeExpansion = 0f
private var isClosed: Boolean = true
private var isOpen: Boolean = false
private var isBlurred: Boolean = false
@@ -92,8 +93,9 @@ class NotificationShadeDepthController @Inject constructor(
// Only for dumpsys
private var lastAppliedBlur = 0
- @VisibleForTesting
- var shadeSpring = DepthAnimation()
+ // Shade expansion offset that happens when pulling down on a HUN.
+ var panelPullDownMinFraction = 0f
+
var shadeAnimation = DepthAnimation()
@VisibleForTesting
@@ -101,12 +103,16 @@ class NotificationShadeDepthController @Inject constructor(
var brightnessMirrorVisible: Boolean = false
set(value) {
field = value
- brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
+ brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f).toInt()
else 0)
}
var qsPanelExpansion = 0f
set(value) {
+ if (value.isNaN()) {
+ Log.w(TAG, "Invalid qs expansion")
+ return
+ }
if (field == value) return
field = value
scheduleUpdate()
@@ -134,15 +140,13 @@ class NotificationShadeDepthController @Inject constructor(
field = value
scheduleUpdate()
- if (shadeSpring.radius == 0 && shadeAnimation.radius == 0) {
+ if (shadeExpansion == 0f && shadeAnimation.radius == 0f) {
return
}
// Do not remove blurs when we're re-enabling them
if (!value) {
return
}
- shadeSpring.animateTo(0)
- shadeSpring.finishIfRunning()
shadeAnimation.animateTo(0)
shadeAnimation.finishIfRunning()
@@ -161,7 +165,7 @@ class NotificationShadeDepthController @Inject constructor(
/**
* Blur radius of the wake-up animation on this frame.
*/
- private var wakeAndUnlockBlurRadius = 0
+ private var wakeAndUnlockBlurRadius = 0f
set(value) {
if (field == value) return
field = value
@@ -174,26 +178,31 @@ class NotificationShadeDepthController @Inject constructor(
@VisibleForTesting
val updateBlurCallback = Choreographer.FrameCallback {
updateScheduled = false
- val normalizedBlurRadius = MathUtils.constrain(shadeAnimation.radius,
- blurUtils.minBlurRadius, blurUtils.maxBlurRadius)
- var combinedBlur = (shadeSpring.radius * INTERACTION_BLUR_FRACTION +
- normalizedBlurRadius * ANIMATION_BLUR_FRACTION).toInt()
- val qsExpandedRatio = qsPanelExpansion * shadeExpansion
+ val animationRadius = MathUtils.constrain(shadeAnimation.radius,
+ blurUtils.minBlurRadius.toFloat(), blurUtils.maxBlurRadius.toFloat())
+ val expansionRadius = blurUtils.blurRadiusOfRatio(
+ Interpolators.getNotificationScrimAlpha(
+ if (shouldApplyShadeBlur()) shadeExpansion else 0f, false))
+ var combinedBlur = (expansionRadius * INTERACTION_BLUR_FRACTION +
+ animationRadius * ANIMATION_BLUR_FRACTION)
+ val qsExpandedRatio = Interpolators.getNotificationScrimAlpha(qsPanelExpansion,
+ false /* notification */) * shadeExpansion
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(qsExpandedRatio))
combinedBlur = max(combinedBlur, blurUtils.blurRadiusOfRatio(transitionToFullShadeProgress))
- var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius).toFloat()
+ var shadeRadius = max(combinedBlur, wakeAndUnlockBlurRadius)
if (blursDisabledForAppLaunch) {
shadeRadius = 0f
}
+ var zoomOut = MathUtils.saturate(blurUtils.ratioOfBlurRadius(shadeRadius))
var blur = shadeRadius.toInt()
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
blur = 0
+ zoomOut = 0f
}
- val zoomOut = blurUtils.ratioOfBlurRadius(blur)
if (!blurUtils.supportsBlursOnWindows()) {
blur = 0
@@ -266,12 +275,11 @@ class NotificationShadeDepthController @Inject constructor(
override fun onStateChanged(newState: Int) {
updateShadeAnimationBlur(
shadeExpansion, prevTracking, prevShadeVelocity, prevShadeDirection)
- updateShadeBlur()
+ scheduleUpdate()
}
override fun onDozingChanged(isDozing: Boolean) {
if (isDozing) {
- shadeSpring.finishIfRunning()
shadeAnimation.finishIfRunning()
brightnessMirrorSpring.finishIfRunning()
}
@@ -308,8 +316,10 @@ class NotificationShadeDepthController @Inject constructor(
/**
* Update blurs when pulling down the shade
*/
- override fun onPanelExpansionChanged(expansion: Float, tracking: Boolean) {
+ override fun onPanelExpansionChanged(rawExpansion: Float, tracking: Boolean) {
val timestamp = SystemClock.elapsedRealtimeNanos()
+ val expansion = MathUtils.saturate(
+ (rawExpansion - panelPullDownMinFraction) / (1f - panelPullDownMinFraction))
if (shadeExpansion == expansion && prevTracking == tracking) {
prevTimestamp = timestamp
@@ -336,7 +346,7 @@ class NotificationShadeDepthController @Inject constructor(
prevTracking = tracking
prevTimestamp = timestamp
- updateShadeBlur()
+ scheduleUpdate()
}
private fun updateShadeAnimationBlur(
@@ -399,15 +409,7 @@ class NotificationShadeDepthController @Inject constructor(
}
shadeAnimation.setStartVelocity(velocity)
- shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized))
- }
-
- private fun updateShadeBlur() {
- var newBlur = 0
- if (shouldApplyShadeBlur()) {
- newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
- }
- shadeSpring.animateTo(newBlur)
+ shadeAnimation.animateTo(blurUtils.blurRadiusOfRatio(targetBlurNormalized).toInt())
}
private fun scheduleUpdate(viewToBlur: View? = null) {
@@ -433,7 +435,8 @@ class NotificationShadeDepthController @Inject constructor(
IndentingPrintWriter(pw, " ").let {
it.println("StatusBarWindowBlurController:")
it.increaseIndent()
- it.println("shadeRadius: ${shadeSpring.radius}")
+ it.println("shadeExpansion: $shadeExpansion")
+ it.println("shouldApplyShaeBlur: ${shouldApplyShadeBlur()}")
it.println("shadeAnimation: ${shadeAnimation.radius}")
it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
@@ -452,7 +455,7 @@ class NotificationShadeDepthController @Inject constructor(
/**
* Blur radius visible on the UI, in pixels.
*/
- var radius = 0
+ var radius = 0f
/**
* Depth ratio of the current blur radius.
@@ -473,12 +476,12 @@ class NotificationShadeDepthController @Inject constructor(
private var springAnimation = SpringAnimation(this, object :
FloatPropertyCompat<DepthAnimation>("blurRadius") {
override fun setValue(rect: DepthAnimation?, value: Float) {
- radius = value.toInt()
+ radius = value
scheduleUpdate(view)
}
override fun getValue(rect: DepthAnimation?): Float {
- return radius.toFloat()
+ return radius
}
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index f0d779ce1e0f..6ea79af8b9ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -182,10 +182,10 @@ public interface NotificationShadeWindowController extends RemoteInputController
default void setFaceAuthDisplayBrightness(float brightness) {}
/**
- * How much {@link LightRevealScrim} obscures the UI.
- * @param amount 0 when opaque, 1 when not transparent
+ * If {@link LightRevealScrim} obscures the UI.
+ * @param opaque if the scrim is opaque
*/
- default void setLightRevealScrimAmount(float amount) {}
+ default void setLightRevealScrimOpaque(boolean opaque) {}
/**
* Custom listener to pipe data back to plugins about whether or not the status bar would be
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
index 4a467ce3c987..d01fc93ee84c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
@@ -104,7 +104,7 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
// the active effect area. Values here should be kept in sync with the
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
- (1 - rippleShader.progress)) * radius * 1.5f
+ (1 - rippleShader.progress)) * radius * 2
canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
index 146046b33375..5175977d4e81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
@@ -147,8 +147,12 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
val fadeIn = subProgress(0f, 0.1f, value)
val fadeOutNoise = subProgress(0.4f, 1f, value)
- val fadeOutRipple = subProgress(0.3f, 1f, value)
- val fadeCircle = subProgress(0f, 0.2f, value)
+ var fadeOutRipple = 0f
+ var fadeCircle = 0f
+ if (shouldFadeOutRipple) {
+ fadeCircle = subProgress(0f, 0.2f, value)
+ fadeOutRipple = subProgress(0.3f, 1f, value)
+ }
setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
setUniform("in_fadeCircle", 1 - fadeCircle)
setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
@@ -200,4 +204,6 @@ class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
field = value
setUniform("in_pixelDensity", value)
}
+
+ var shouldFadeOutRipple: Boolean = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 71546ae07ffc..0773460ecf67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -24,7 +24,6 @@ import android.app.smartspace.SmartspaceTarget
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
-import android.content.pm.UserInfo
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
@@ -45,6 +44,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
import java.lang.RuntimeException
@@ -67,6 +67,7 @@ class LockscreenSmartspaceController @Inject constructor(
private val contentResolver: ContentResolver,
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
+ private val deviceProvisionedController: DeviceProvisionedController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
@@ -83,6 +84,55 @@ class LockscreenSmartspaceController @Inject constructor(
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
+ private val deviceProvisionedListener =
+ object : DeviceProvisionedController.DeviceProvisionedListener {
+ override fun onDeviceProvisionedChanged() {
+ connectSession()
+ }
+
+ override fun onUserSetupChanged() {
+ connectSession()
+ }
+ }
+
+ private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
+ val filteredTargets = targets.filter(::filterSmartspaceTarget)
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
+
+ private val userTrackerCallback = object : UserTracker.Callback {
+ override fun onUserChanged(newUser: Int, userContext: Context) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uri: Uri?) {
+ execution.assertIsMainThread()
+ reloadSmartspace()
+ }
+ }
+
+ private val configChangeListener = object : ConfigurationController.ConfigurationListener {
+ override fun onThemeChanged() {
+ execution.assertIsMainThread()
+ updateTextColorFromWallpaper()
+ }
+ }
+
+ private val statusBarStateListener = object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ execution.assertIsMainThread()
+ smartspaceView.setDozeAmount(eased)
+ }
+ }
+
+ init {
+ deviceProvisionedController.addCallback(deviceProvisionedListener)
+ }
+
fun isEnabled(): Boolean {
execution.assertIsMainThread()
@@ -141,13 +191,23 @@ class LockscreenSmartspaceController @Inject constructor(
}
private fun connectSession() {
- if (plugin == null || session != null) {
+ if (plugin == null || session != null || !this::smartspaceView.isInitialized) {
return
}
- val session = smartspaceManager.createSmartspaceSession(
+
+ // Only connect after the device is fully provisioned to avoid connection caching
+ // issues
+ if (!deviceProvisionedController.isDeviceProvisioned() ||
+ !deviceProvisionedController.isCurrentUserSetup()) {
+ return
+ }
+
+ val newSession = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "lockscreen").build())
- session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ this.session = newSession
+ deviceProvisionedController.removeCallback(deviceProvisionedListener)
userTracker.addCallback(userTrackerCallback, uiExecutor)
contentResolver.registerContentObserver(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
@@ -158,8 +218,6 @@ class LockscreenSmartspaceController @Inject constructor(
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
- this.session = session
-
reloadSmartspace()
}
@@ -198,43 +256,6 @@ class LockscreenSmartspaceController @Inject constructor(
plugin?.unregisterListener(listener)
}
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
- val filteredTargets = targets.filter(::filterSmartspaceTarget)
- plugin?.onTargetsAvailable(filteredTargets)
- }
-
- private val userTrackerCallback = object : UserTracker.Callback {
- override fun onUserChanged(newUser: Int, userContext: Context) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
-
- override fun onProfilesChanged(profiles: List<UserInfo>) {
- }
- }
-
- private val settingsObserver = object : ContentObserver(handler) {
- override fun onChange(selfChange: Boolean, uri: Uri?) {
- execution.assertIsMainThread()
- reloadSmartspace()
- }
- }
-
- private val configChangeListener = object : ConfigurationController.ConfigurationListener {
- override fun onThemeChanged() {
- execution.assertIsMainThread()
- updateTextColorFromWallpaper()
- }
- }
-
- private val statusBarStateListener = object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- execution.assertIsMainThread()
- smartspaceView.setDozeAmount(eased)
- }
- }
-
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
return when (t.userHandle) {
userTracker.userHandle -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index dba3401cc28e..9c755e970a0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -85,23 +85,26 @@ public abstract class StackScrollerDecorView extends ExpandableView {
}
/**
- * Set the content of this view to be visible in an animated way.
- *
- * @param contentVisible True if the content should be visible or false if it should be hidden.
+ * @param visible True if we should animate contents visible
*/
- public void setContentVisible(boolean contentVisible) {
- setContentVisible(contentVisible, true /* animate */);
+ public void setContentVisible(boolean visible) {
+ setContentVisible(visible, true /* animate */, null /* runAfter */);
}
+
/**
- * Set the content of this view to be visible.
- * @param contentVisible True if the content should be visible or false if it should be hidden.
- * @param animate Should an animation be performed.
+ * @param visible True if the contents should be visible
+ * @param animate True if we should fade to new visibility
+ * @param runAfter Runnable to run after visibility updates
*/
- private void setContentVisible(boolean contentVisible, boolean animate) {
- if (mContentVisible != contentVisible) {
+ public void setContentVisible(boolean visible, boolean animate, Runnable runAfter) {
+ if (mContentVisible != visible) {
mContentAnimating = animate;
- mContentVisible = contentVisible;
- setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+ mContentVisible = visible;
+ Runnable endRunnable = runAfter == null ? mContentVisibilityEndRunnable : () -> {
+ mContentVisibilityEndRunnable.run();
+ runAfter.run();
+ };
+ setViewVisible(mContent, visible, animate, endRunnable);
}
if (!mContentAnimating) {
@@ -113,6 +116,10 @@ public abstract class StackScrollerDecorView extends ExpandableView {
return mContentVisible;
}
+ public void setVisible(boolean nowVisible, boolean animate) {
+ setVisible(nowVisible, animate, null);
+ }
+
/**
* Make this view visible. If {@code false} is passed, the view will fade out it's content
* and set the view Visibility to GONE. If only the content should be changed
@@ -121,7 +128,7 @@ public abstract class StackScrollerDecorView extends ExpandableView {
* @param nowVisible should the view be visible
* @param animate should the change be animated.
*/
- public void setVisible(boolean nowVisible, boolean animate) {
+ public void setVisible(boolean nowVisible, boolean animate, Runnable runAfter) {
if (mIsVisible != nowVisible) {
mIsVisible = nowVisible;
if (animate) {
@@ -132,10 +139,10 @@ public abstract class StackScrollerDecorView extends ExpandableView {
} else {
setWillBeGone(true);
}
- setContentVisible(nowVisible, true /* animate */);
+ setContentVisible(nowVisible, true /* animate */, runAfter);
} else {
setVisibility(nowVisible ? VISIBLE : GONE);
- setContentVisible(nowVisible, false /* animate */);
+ setContentVisible(nowVisible, false /* animate */, runAfter);
setWillBeGone(false);
notifyHeightChanged(false /* needsAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 23aefd9bfd8e..594afceab63a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -46,6 +46,7 @@ public class NotificationRoundnessManager {
private Runnable mRoundingChangedCallback;
private ExpandableNotificationRow mTrackedHeadsUp;
private float mAppearFraction;
+ private boolean mIsDismissAllInProgress;
private ExpandableView mSwipedView = null;
private ExpandableView mViewBeforeSwipedView = null;
@@ -162,6 +163,10 @@ public class NotificationRoundnessManager {
}
}
+ void setDismissAllInProgress(boolean isClearingAll) {
+ mIsDismissAllInProgress = isClearingAll;
+ }
+
private float getRoundness(ExpandableView view, boolean top) {
if (view == null) {
return 0f;
@@ -171,6 +176,11 @@ public class NotificationRoundnessManager {
|| view == mViewAfterSwipedView) {
return 1f;
}
+ if (view instanceof ExpandableNotificationRow
+ && ((ExpandableNotificationRow) view).canViewBeDismissed()
+ && mIsDismissAllInProgress) {
+ return 1.0f;
+ }
if ((view.isPinned()
|| (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
return 1.0f;
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 0660daab3720..733c0a92ec09 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
@@ -144,6 +144,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private static final boolean DEBUG_REMOVE_ANIMATION = SystemProperties.getBoolean(
"persist.debug.nssl.dismiss", false /* default */);
+ // Delay in milli-seconds before shade closes for clear all.
+ private final int DELAY_BEFORE_SHADE_CLOSE = 200;
+ private boolean mShadeNeedsToClose = false;
+
private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
@@ -256,10 +260,10 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private boolean mExpandedInThisMotion;
private boolean mShouldShowShelfOnly;
protected boolean mScrollingEnabled;
+ private boolean mIsCurrentUserSetup;
protected FooterView mFooterView;
protected EmptyShadeView mEmptyShadeView;
private boolean mDismissAllInProgress;
- private boolean mFadeNotificationsOnDismiss;
private FooterDismissListener mFooterDismissListener;
private boolean mFlingAfterUpEvent;
@@ -612,6 +616,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mClearAllEnabled = res.getBoolean(R.bool.config_enableNotificationsClearAll);
mGroupMembershipManager = groupMembershipManager;
mGroupExpansionManager = groupExpansionManager;
+ setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
void initializeForegroundServiceSection(ForegroundServiceDungeonView fgsSectionView) {
@@ -683,6 +688,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mController.hasActiveClearableNotifications(ROWS_ALL);
RemoteInputController remoteInputController = mRemoteInputManager.getController();
boolean showFooterView = (showDismissView || getVisibleNotificationCount() > 0)
+ && mIsCurrentUserSetup // see: b/193149550
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
&& (remoteInputController == null || !remoteInputController.isRemoteInputActive());
@@ -1192,7 +1198,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private void clampScrollPosition() {
int scrollRange = getScrollRange();
- if (scrollRange < mOwnScrollY) {
+ if (scrollRange < mOwnScrollY && !mAmbientState.isDismissAllInProgress()) {
boolean animateStackY = false;
if (scrollRange < getScrollAmountToScrollBoundary()
&& mAnimateStackYForContentHeightChange) {
@@ -1708,6 +1714,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
+ if (child instanceof SectionHeaderView) {
+ ((StackScrollerDecorView) child).setContentVisible(
+ false /* visible */, true /* animate */, endRunnable);
+ return;
+ }
mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
true /* isDismissAll */);
}
@@ -4050,6 +4061,19 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
runAnimationFinishedRunnables();
clearTransient();
clearHeadsUpDisappearRunning();
+
+ if (mAmbientState.isDismissAllInProgress()) {
+ setDismissAllInProgress(false);
+
+ if (mShadeNeedsToClose) {
+ mShadeNeedsToClose = false;
+ postDelayed(
+ () -> {
+ mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ },
+ DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+ }
+ }
}
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
@@ -4407,6 +4431,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
public void setDismissAllInProgress(boolean dismissAllInProgress) {
mDismissAllInProgress = dismissAllInProgress;
mAmbientState.setDismissAllInProgress(dismissAllInProgress);
+ mController.getNoticationRoundessManager().setDismissAllInProgress(dismissAllInProgress);
handleDismissAllClipping();
}
@@ -4947,129 +4972,137 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- @VisibleForTesting
- void clearNotifications(@SelectedRows int selection, boolean closeShade) {
- // animate-swipe all dismissable notifications, then animate the shade closed
- int numChildren = getChildCount();
+ private boolean isVisible(View child) {
+ boolean hasClipBounds = child.getClipBounds(mTmpRect);
+ return child.getVisibility() == View.VISIBLE
+ && (!hasClipBounds || mTmpRect.height() > 0);
+ }
- final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
- final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
- for (int i = 0; i < numChildren; i++) {
- final View child = getChildAt(i);
- if (child instanceof ExpandableNotificationRow) {
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean parentVisible = false;
- boolean hasClipBounds = child.getClipBounds(mTmpRect);
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(row);
- if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(child);
- parentVisible = true;
- }
- } else if (child.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- parentVisible = true;
- }
- List<ExpandableNotificationRow> children = row.getAttachedChildren();
- if (children != null) {
- for (ExpandableNotificationRow childRow : children) {
- if (includeChildInDismissAll(row, selection)) {
- viewsToRemove.add(childRow);
- if (parentVisible && row.areChildrenExpanded()) {
- hasClipBounds = childRow.getClipBounds(mTmpRect);
- if (childRow.getVisibility() == View.VISIBLE
- && (!hasClipBounds || mTmpRect.height() > 0)) {
- viewsToHide.add(childRow);
- }
- }
- }
- }
- }
+ private boolean shouldHideParent(View view, @SelectedRows int selection) {
+ final boolean silentSectionWillBeGone =
+ !mController.hasNotifications(ROWS_GENTLE, false /* clearable */);
+
+ // The only SectionHeaderView we have is the silent section header.
+ if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
+ return true;
+ }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+ if (isVisible(row) && includeChildInDismissAll(row, selection)) {
+ return true;
}
}
+ return false;
+ }
- if (mDismissListener != null) {
- mDismissListener.onDismiss(selection);
- }
+ private boolean isChildrenVisible(ExpandableNotificationRow parent) {
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ return isVisible(parent)
+ && children != null
+ && parent.areChildrenExpanded();
+ }
+
+ // Similar to #getRowsToDismissInBackend, but filters for visible views.
+ private ArrayList<View> getVisibleViewsToAnimateAway(@SelectedRows int selection) {
+ final int viewCount = getChildCount();
+ final ArrayList<View> viewsToHide = new ArrayList<>(viewCount);
- if (viewsToRemove.isEmpty()) {
- if (closeShade && mShadeController != null) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ for (int i = 0; i < viewCount; i++) {
+ final View view = getChildAt(i);
+
+ if (shouldHideParent(view, selection)) {
+ viewsToHide.add(view);
}
- return;
- }
+ if (view instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- performDismissAllAnimations(
- viewsToHide,
- closeShade,
- () -> onDismissAllAnimationsEnd(viewsToRemove, selection));
+ if (isChildrenVisible(parent)) {
+ for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
+ if (isVisible(child) && includeChildInDismissAll(child, selection)) {
+ viewsToHide.add(child);
+ }
+ }
+ }
+ }
+ }
+ return viewsToHide;
}
- private boolean includeChildInDismissAll(
- ExpandableNotificationRow row,
+ private ArrayList<ExpandableNotificationRow> getRowsToDismissInBackend(
@SelectedRows int selection) {
- return canChildBeDismissed(row) && matchesSelection(row, selection);
+ final int childCount = getChildCount();
+ final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(childCount);
+
+ for (int i = 0; i < childCount; i++) {
+ final View view = getChildAt(i);
+ if (!(view instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(parent);
+ }
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
+ if (isVisible(parent) && children != null) {
+ for (ExpandableNotificationRow child : children) {
+ if (includeChildInDismissAll(parent, selection)) {
+ viewsToRemove.add(child);
+ }
+ }
+ }
+ }
+ return viewsToRemove;
}
/**
- * Given a list of rows, animates them away in a staggered fashion as if they were dismissed.
- * Doesn't actually dismiss them, though -- that must be done in the onAnimationComplete
- * handler.
- *
- * @param hideAnimatedList List of rows to animated away. Should only be views that are
- * currently visible, or else the stagger will look funky.
- * @param closeShade Whether to close the shade after the stagger animation completes.
- * @param onAnimationComplete Called after the entire animation completes (including the shade
- * closing if appropriate). The rows must be dismissed for real here.
+ * Collects a list of visible rows, and animates them away in a staggered fashion as if they
+ * were dismissed. Notifications are dismissed in the backend via onDismissAllAnimationsEnd.
*/
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- private void performDismissAllAnimations(
- final ArrayList<View> hideAnimatedList,
- final boolean closeShade,
- final Runnable onAnimationComplete) {
-
- final Runnable onSlideAwayAnimationComplete = () -> {
- if (closeShade) {
- mShadeController.addPostCollapseAction(() -> {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- });
- mShadeController.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_NONE);
- } else {
- setDismissAllInProgress(false);
- onAnimationComplete.run();
- }
+ @VisibleForTesting
+ void clearNotifications(@SelectedRows int selection, boolean closeShade) {
+ // Animate-swipe all dismissable notifications, then animate the shade closed
+ final ArrayList<View> viewsToAnimateAway = getVisibleViewsToAnimateAway(selection);
+ final ArrayList<ExpandableNotificationRow> rowsToDismissInBackend =
+ getRowsToDismissInBackend(selection);
+ if (mDismissListener != null) {
+ mDismissListener.onDismiss(selection);
+ }
+ final Runnable dismissInBackend = () -> {
+ onDismissAllAnimationsEnd(rowsToDismissInBackend, selection);
};
-
- if (hideAnimatedList.isEmpty()) {
- onSlideAwayAnimationComplete.run();
+ if (viewsToAnimateAway.isEmpty()) {
+ dismissInBackend.run();
return;
}
-
- // let's disable our normal animations
+ // Disable normal animations
setDismissAllInProgress(true);
+ mShadeNeedsToClose = closeShade;
// Decrease the delay for every row we animate to give the sense of
// accelerating the swipes
- int rowDelayDecrement = 10;
- int currentDelay = 140;
- int totalDelay = 180;
- int numItems = hideAnimatedList.size();
+ final int rowDelayDecrement = 5;
+ int currentDelay = 60;
+ int totalDelay = 0;
+ final int numItems = viewsToAnimateAway.size();
for (int i = numItems - 1; i >= 0; i--) {
- View view = hideAnimatedList.get(i);
+ View view = viewsToAnimateAway.get(i);
Runnable endRunnable = null;
if (i == 0) {
- endRunnable = onSlideAwayAnimationComplete;
+ endRunnable = dismissInBackend;
}
dismissViewAnimated(view, endRunnable, totalDelay, ANIMATION_DURATION_SWIPE);
- currentDelay = Math.max(50, currentDelay - rowDelayDecrement);
+ currentDelay = Math.max(30, currentDelay - rowDelayDecrement);
totalDelay += currentDelay;
}
}
+ private boolean includeChildInDismissAll(
+ ExpandableNotificationRow row,
+ @SelectedRows int selection) {
+ return canChildBeDismissed(row) && matchesSelection(row, selection);
+ }
+
public void setNotificationActivityStarter(
NotificationActivityStarter notificationActivityStarter) {
mNotificationActivityStarter = notificationActivityStarter;
@@ -5085,6 +5118,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mFooterDismissListener.onDismiss();
}
clearNotifications(ROWS_ALL, true /* closeShade */);
+ footerView.setSecondaryVisible(false /* visible */, true /* animate */);
});
footerView.setManageButtonClickListener(v -> {
mNotificationActivityStarter.startHistoryIntent(v, mFooterView.isHistoryShown());
@@ -5567,6 +5601,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
/**
+ * Sets whether the current user is set up, which is required to show the footer (b/193149550)
+ */
+ public void setCurrentUserSetup(boolean isCurrentUserSetup) {
+ if (mIsCurrentUserSetup != isCurrentUserSetup) {
+ mIsCurrentUserSetup = isCurrentUserSetup;
+ updateFooter();
+ }
+ }
+
+ /**
* A listener that is notified when the empty space below the notifications is clicked on
*/
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
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 9e4adce47e0c..1e92ca9862f7 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
@@ -117,6 +117,8 @@ import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -144,6 +146,7 @@ public class NotificationStackScrollLayoutController {
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationRoundnessManager mNotificationRoundnessManager;
private final TunerService mTunerService;
+ private final DeviceProvisionedController mDeviceProvisionedController;
private final DynamicPrivacyController mDynamicPrivacyController;
private final ConfigurationController mConfigurationController;
private final ZenModeController mZenModeController;
@@ -171,6 +174,7 @@ public class NotificationStackScrollLayoutController {
private final NotificationLockscreenUserManager mLockscreenUserManager;
// TODO: StatusBar should be encapsulated behind a Controller
private final StatusBar mStatusBar;
+ private final NotificationGroupManagerLegacy mLegacyGroupManager;
private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
@@ -218,6 +222,28 @@ public class NotificationStackScrollLayoutController {
}
};
+ private final DeviceProvisionedListener mDeviceProvisionedListener =
+ new DeviceProvisionedListener() {
+ @Override
+ public void onDeviceProvisionedChanged() {
+ updateCurrentUserIsSetup();
+ }
+
+ @Override
+ public void onUserSwitched() {
+ updateCurrentUserIsSetup();
+ }
+
+ @Override
+ public void onUserSetupChanged() {
+ updateCurrentUserIsSetup();
+ }
+
+ private void updateCurrentUserIsSetup() {
+ mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
+ }
+ };
+
private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
if (mView.isExpanded()) {
// The bottom might change because we're using the final actual height of the view
@@ -587,6 +613,7 @@ public class NotificationStackScrollLayoutController {
HeadsUpManagerPhone headsUpManager,
NotificationRoundnessManager notificationRoundnessManager,
TunerService tunerService,
+ DeviceProvisionedController deviceProvisionedController,
DynamicPrivacyController dynamicPrivacyController,
ConfigurationController configurationController,
SysuiStatusBarStateController statusBarStateController,
@@ -623,6 +650,7 @@ public class NotificationStackScrollLayoutController {
mHeadsUpManager = headsUpManager;
mNotificationRoundnessManager = notificationRoundnessManager;
mTunerService = tunerService;
+ mDeviceProvisionedController = deviceProvisionedController;
mDynamicPrivacyController = dynamicPrivacyController;
mConfigurationController = configurationController;
mStatusBarStateController = statusBarStateController;
@@ -651,6 +679,8 @@ public class NotificationStackScrollLayoutController {
mStatusBar.requestNotificationUpdate("onGroupsChanged");
}
});
+ mLegacyGroupManager = featureFlags.isNewNotifPipelineRenderingEnabled()
+ ? null : legacyGroupManager;
mSilentHeaderController = silentHeaderController;
mFeatureFlags = featureFlags;
mNotifPipeline = notifPipeline;
@@ -759,6 +789,9 @@ public class NotificationStackScrollLayoutController {
return Unit.INSTANCE;
});
+ // callback is invoked synchronously, updating mView immediately
+ mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
+
if (mView.isAttachedToWindow()) {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
@@ -1122,6 +1155,10 @@ public class NotificationStackScrollLayoutController {
mZenModeController.areNotificationsHiddenInShade());
}
+ public boolean areNotificationsHiddenInShade() {
+ return mZenModeController.areNotificationsHiddenInShade();
+ }
+
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
@@ -1170,6 +1207,10 @@ public class NotificationStackScrollLayoutController {
* Return whether there are any clearable notifications
*/
public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
+ return hasNotifications(selection, true /* clearable */);
+ }
+
+ public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
if (mDynamicPrivacyController.isInLockedDownShade()) {
return false;
}
@@ -1180,9 +1221,16 @@ public class NotificationStackScrollLayoutController {
continue;
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- if (row.canViewBeDismissed() &&
- NotificationStackScrollLayout.matchesSelection(row, selection)) {
- return true;
+ final boolean matchClearable =
+ isClearable ? row.canViewBeDismissed() : !row.canViewBeDismissed();
+ final boolean inSection =
+ NotificationStackScrollLayout.matchesSelection(row, selection);
+ if (matchClearable && inSection) {
+ if (mLegacyGroupManager == null
+ || !mLegacyGroupManager.isSummaryOfSuppressedGroup(
+ row.getEntry().getSbn())) {
+ return true;
+ }
}
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 2c810c93b2ee..8be5de7ae56e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -366,6 +366,20 @@ public class StackScrollAlgorithm {
return stackHeight / stackEndHeight;
}
+ public boolean hasOngoingNotifs(StackScrollAlgorithmState algorithmState) {
+ for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
+ View child = algorithmState.visibleChildren.get(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (!row.canViewBeDismissed()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -430,7 +444,9 @@ public class StackScrollAlgorithm {
+ view.getIntrinsicHeight();
final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
((FooterView.FooterViewState) viewState).hideContent =
- isShelfShowing || noSpaceForFooter;
+ isShelfShowing || noSpaceForFooter
+ || (ambientState.isDismissAllInProgress()
+ && !hasOngoingNotifs(algorithmState));
}
} else {
if (view != ambientState.getTrackedHeadsUpRow()) {
@@ -467,7 +483,6 @@ public class StackScrollAlgorithm {
}
}
}
-
// Clip height of view right before shelf.
viewState.height = (int) (getMaxAllowedChildHeight(view) * expansionFraction);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index ee12b4b2d728..4466cfe99fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -46,7 +46,7 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_WAKEUP = 500;
public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
- public static final int ANIMATION_DURATION_SWIPE = 260;
+ public static final int ANIMATION_DURATION_SWIPE = 200;
public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 6e201048abdb..2c76cfeff733 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -116,7 +116,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
/**
* Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
- * device while being requested when keyguard is occluded.
+ * device while being requested when keyguard is occluded or showing.
*/
public static final int MODE_UNLOCK_COLLAPSING = 5;
@@ -425,6 +425,11 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
if (!wasDeviceInteractive) {
mPendingShowBouncer = true;
} else {
+ mShadeController.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE,
+ true /* force */,
+ false /* delayed */,
+ BIOMETRIC_COLLAPSE_SPEEDUP_FACTOR);
mPendingShowBouncer = false;
mKeyguardViewController.notifyKeyguardAuthenticated(
false /* strongAuth */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 5a6db213d87f..e67c6ac3a7c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -291,6 +291,7 @@ public class DozeParameters implements TunerService.Tunable,
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ pw.print("getAlwaysOn(): "); pw.println(getAlwaysOn());
pw.print("getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
pw.print("getPulseDuration(): "); pw.println(getPulseDuration());
pw.print("getPulseInDuration(): "); pw.println(getPulseInDuration());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index b2cf72aca864..21c3e5e0a8d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -116,11 +116,18 @@ public class DozeScrimController implements StateListener {
if (!mDozing || mPulseCallback != null) {
if (DEBUG) {
- Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+ Log.d(TAG, "Pulse suppressed. Dozing: " + mDozeParameters + " had callback? "
+ (mPulseCallback != null));
}
// Pulse suppressed.
callback.onPulseFinished();
+ if (!mDozing) {
+ mDozeLog.tracePulseDropped("device isn't dozing");
+ } else {
+ mDozeLog.tracePulseDropped("already has pulse callback mPulseCallback="
+ + mPulseCallback);
+ }
+
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 0a4e59c0391e..4701d8b32ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -203,13 +203,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private ControlsListingController.ControlsListingCallback mListingCallback =
new ControlsListingController.ControlsListingCallback() {
public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
- boolean available = !serviceInfos.isEmpty();
+ post(() -> {
+ boolean available = !serviceInfos.isEmpty();
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
- }
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
+ }
+ });
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index f77c0520cdb1..b58cab4f1ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -83,8 +83,7 @@ public class KeyguardClockPositionAlgorithm {
private int mNotificationStackHeight;
/**
- * Minimum top margin to avoid overlap with status bar, lock icon, or multi-user switcher
- * avatar.
+ * Minimum top margin to avoid overlap with status bar, or multi-user switcher avatar.
*/
private int mMinTopMargin;
@@ -150,6 +149,25 @@ public class KeyguardClockPositionAlgorithm {
private boolean mIsSplitShade;
/**
+ * Top location of the udfps icon. This includes the worst case (highest) burn-in
+ * offset that would make the top physically highest on the screen.
+ *
+ * Set to -1 if udfps is not enrolled on the device.
+ */
+ private float mUdfpsTop;
+
+ /**
+ * Bottom y-position of the currently visible clock
+ */
+ private float mClockBottom;
+
+ /**
+ * If true, try to keep clock aligned to the top of the display. Else, assume the clock
+ * is center aligned.
+ */
+ private boolean mIsClockTopAligned;
+
+ /**
* Refreshes the dimension values.
*/
public void loadDimens(Resources res) {
@@ -157,7 +175,7 @@ public class KeyguardClockPositionAlgorithm {
R.dimen.keyguard_status_view_bottom_margin);
mContainerTopPadding =
- res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2;
+ res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
mBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
@@ -174,7 +192,8 @@ public class KeyguardClockPositionAlgorithm {
int keyguardStatusHeight, int userSwitchHeight, int userSwitchPreferredY,
boolean hasCustomClock, boolean hasVisibleNotifs, float dark,
float overStrechAmount, boolean bypassEnabled, int unlockedStackScrollerPadding,
- float qsExpansion, int cutoutTopInset, boolean isSplitShade) {
+ float qsExpansion, int cutoutTopInset, boolean isSplitShade, float udfpsTop,
+ float clockBottom, boolean isClockTopAligned) {
mMinTopMargin = keyguardStatusBarHeaderHeight + Math.max(mContainerTopPadding,
userSwitchHeight);
mMaxShadeBottom = maxShadeBottom;
@@ -193,6 +212,9 @@ public class KeyguardClockPositionAlgorithm {
mQsExpansion = qsExpansion;
mCutoutTopInset = cutoutTopInset;
mIsSplitShade = isSplitShade;
+ mUdfpsTop = udfpsTop;
+ mClockBottom = clockBottom;
+ mIsClockTopAligned = isClockTopAligned;
}
public void run(Result result) {
@@ -247,8 +269,34 @@ public class KeyguardClockPositionAlgorithm {
if (clockY - mBurnInPreventionOffsetYLargeClock < mCutoutTopInset) {
shift = mCutoutTopInset - (clockY - mBurnInPreventionOffsetYLargeClock);
}
- float clockYDark = clockY + burnInPreventionOffsetY() + shift;
+ int burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock; // requested offset
+ final boolean hasUdfps = mUdfpsTop > -1;
+ if (hasUdfps && !mIsClockTopAligned) {
+ // ensure clock doesn't overlap with the udfps icon
+ if (mUdfpsTop < mClockBottom) {
+ // sometimes the clock textView extends beyond udfps, so let's just use the
+ // space above the KeyguardStatusView/clock as our burn-in offset
+ burnInPreventionOffsetY = (int) (clockY - mCutoutTopInset) / 2;
+ if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ }
+ shift = -burnInPreventionOffsetY;
+ } else {
+ float upperSpace = clockY - mCutoutTopInset;
+ float lowerSpace = mUdfpsTop - mClockBottom;
+ // center the burn-in offset within the upper + lower space
+ burnInPreventionOffsetY = (int) (lowerSpace + upperSpace) / 2;
+ if (mBurnInPreventionOffsetYLargeClock < burnInPreventionOffsetY) {
+ burnInPreventionOffsetY = mBurnInPreventionOffsetYLargeClock;
+ }
+ shift = (lowerSpace - upperSpace) / 2;
+ }
+ }
+
+ float clockYDark = clockY
+ + burnInPreventionOffsetY(burnInPreventionOffsetY)
+ + shift;
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mOverStretchAmount);
}
@@ -280,9 +328,7 @@ public class KeyguardClockPositionAlgorithm {
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
- private float burnInPreventionOffsetY() {
- int offset = mBurnInPreventionOffsetYLargeClock;
-
+ private float burnInPreventionOffsetY(int offset) {
return getBurnInOffset(offset * 2, false /* xAxis */) - offset;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
index f1cde8a9be7a..a5b5f1cbf1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java
@@ -28,7 +28,10 @@ import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.StyleRes;
+
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.keyguard.KeyguardIndication;
@@ -39,6 +42,12 @@ import java.util.LinkedList;
*/
public class KeyguardIndicationTextView extends TextView {
private static final long MSG_MIN_DURATION_MILLIS_DEFAULT = 1500;
+
+ @StyleRes
+ private static int sStyleId = R.style.TextAppearance_Keyguard_BottomArea;
+ @StyleRes
+ private static int sButtonStyleId = R.style.TextAppearance_Keyguard_BottomArea_Button;
+
private long mNextAnimationTime = 0;
private boolean mAnimationsEnabled = true;
private LinkedList<CharSequence> mMessages = new LinkedList<>();
@@ -136,6 +145,14 @@ public class KeyguardIndicationTextView extends TextView {
public void onAnimationEnd(Animator animator) {
KeyguardIndication info = mKeyguardIndicationInfo.poll();
if (info != null) {
+ // First, update the style.
+ // If a background is set on the text, we don't want shadow on the text
+ if (info.getBackground() != null) {
+ setTextAppearance(sButtonStyleId);
+ } else {
+ setTextAppearance(sStyleId);
+ }
+ setBackground(info.getBackground());
setTextColor(info.getTextColor());
setOnClickListener(info.getClickListener());
setClickable(info.getClickListener() != null);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index e272d2713e2a..55e0c9b979c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -96,7 +96,8 @@ public class KeyguardStatusBarView extends RelativeLayout implements
private final UserManager mUserManager;
private int mSystemIconsSwitcherHiddenExpandedMargin;
- private int mSystemIconsBaseMargin;
+ private int mStatusBarPaddingEnd;
+ private int mMinDotWidth;
private View mSystemIconsContainer;
private TintedIconManager mIconManager;
private List<String> mBlockedIcons = new ArrayList<>();
@@ -157,14 +158,7 @@ public class KeyguardStatusBarView extends RelativeLayout implements
mMultiUserAvatar.setLayoutParams(lp);
// System icons
- lp = (MarginLayoutParams) mSystemIconsContainer.getLayoutParams();
- lp.setMarginStart(getResources().getDimensionPixelSize(
- R.dimen.system_icons_super_container_margin_start));
- mSystemIconsContainer.setLayoutParams(lp);
- mSystemIconsContainer.setPaddingRelative(mSystemIconsContainer.getPaddingStart(),
- mSystemIconsContainer.getPaddingTop(),
- getResources().getDimensionPixelSize(R.dimen.system_icons_keyguard_padding_end),
- mSystemIconsContainer.getPaddingBottom());
+ updateSystemIconsLayoutParams();
// Respect font size setting.
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
@@ -194,8 +188,10 @@ public class KeyguardStatusBarView extends RelativeLayout implements
Resources res = getResources();
mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
R.dimen.system_icons_switcher_hidden_expanded_margin);
- mSystemIconsBaseMargin = res.getDimensionPixelSize(
- R.dimen.system_icons_super_container_avatarless_margin_end);
+ mStatusBarPaddingEnd = res.getDimensionPixelSize(
+ R.dimen.status_bar_padding_end);
+ mMinDotWidth = res.getDimensionPixelSize(
+ R.dimen.ongoing_appops_dot_min_padding);
mCutoutSideNudge = getResources().getDimensionPixelSize(
R.dimen.display_cutout_margin_consumption);
mShowPercentAvailable = getContext().getResources().getBoolean(
@@ -243,16 +239,24 @@ public class KeyguardStatusBarView extends RelativeLayout implements
private void updateSystemIconsLayoutParams() {
LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) mSystemIconsContainer.getLayoutParams();
- // If the avatar icon is gone, we need to have some end margin to display the system icons
- // correctly.
- int baseMarginEnd = mMultiUserAvatar.getVisibility() == View.GONE
- ? mSystemIconsBaseMargin
- : 0;
+
+ int marginStart = getResources().getDimensionPixelSize(
+ R.dimen.system_icons_super_container_margin_start);
+
+ // Use status_bar_padding_end to replace original
+ // system_icons_super_container_avatarless_margin_end to prevent different end alignment
+ // between PhoneStatusBarView and KeyguardStatusBarView
+ int baseMarginEnd = mStatusBarPaddingEnd;
int marginEnd =
mKeyguardUserSwitcherEnabled ? mSystemIconsSwitcherHiddenExpandedMargin
: baseMarginEnd;
- marginEnd = calculateMargin(marginEnd, mPadding.second);
- if (marginEnd != lp.getMarginEnd()) {
+
+ // Align PhoneStatusBar right margin/padding, only use
+ // 1. status bar layout: mPadding(consider round_corner + privacy dot)
+ // 2. icon container: R.dimen.status_bar_padding_end
+
+ if (marginEnd != lp.getMarginEnd() || marginStart != lp.getMarginStart()) {
+ lp.setMarginStart(marginStart);
lp.setMarginEnd(marginEnd);
mSystemIconsContainer.setLayoutParams(lp);
}
@@ -287,7 +291,13 @@ public class KeyguardStatusBarView extends RelativeLayout implements
mPadding =
StatusBarWindowView.paddingNeededForCutoutAndRoundedCorner(
mDisplayCutout, cornerCutoutMargins, mRoundedCornerPadding);
- setPadding(mPadding.first, waterfallTop, mPadding.second, 0);
+
+ // consider privacy dot space
+ final int minLeft = isLayoutRtl() ? Math.max(mMinDotWidth, mPadding.first) : mPadding.first;
+ final int minRight = isLayoutRtl() ? mPadding.second :
+ Math.max(mMinDotWidth, mPadding.second);
+
+ setPadding(minLeft, waterfallTop, minRight, 0);
}
private boolean updateLayoutParamsNoCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 15e0716f8c49..58cbe83d49f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -51,6 +51,7 @@ import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
@@ -621,6 +622,8 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private float mKeyguardOnlyContentAlpha = 1.0f;
+ private float mUdfpsMaxYBurnInOffset;
+
/**
* Are we currently in gesture navigation
*/
@@ -635,6 +638,7 @@ public class NotificationPanelViewController extends PanelViewController {
private int mQsClipBottom;
private boolean mQsVisible;
private final ContentResolver mContentResolver;
+ private float mMinFraction;
private final Executor mUiExecutor;
private final SecureSettings mSecureSettings;
@@ -852,13 +856,13 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- UserAvatarView userAvatarView = null;
+ FrameLayout userAvatarContainer = null;
KeyguardUserSwitcherView keyguardUserSwitcherView = null;
if (mKeyguardUserSwitcherEnabled && mUserManager.isUserSwitcherEnabled()) {
if (mKeyguardQsUserSwitchEnabled) {
ViewStub stub = mView.findViewById(R.id.keyguard_qs_user_switch_stub);
- userAvatarView = (UserAvatarView) stub.inflate();
+ userAvatarContainer = (FrameLayout) stub.inflate();
} else {
ViewStub stub = mView.findViewById(R.id.keyguard_user_switcher_stub);
keyguardUserSwitcherView = (KeyguardUserSwitcherView) stub.inflate();
@@ -867,7 +871,7 @@ public class NotificationPanelViewController extends PanelViewController {
updateViewControllers(
mView.findViewById(R.id.keyguard_status_view),
- userAvatarView,
+ userAvatarContainer,
mKeyguardStatusBar,
keyguardUserSwitcherView);
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
@@ -956,10 +960,11 @@ public class NotificationPanelViewController extends PanelViewController {
mScreenCornerRadius = (int) ScreenDecorationsUtils.getWindowCornerRadius(mResources);
mLockscreenNotificationQSPadding = mResources.getDimensionPixelSize(
R.dimen.notification_side_paddings);
+ mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
}
private void updateViewControllers(KeyguardStatusView keyguardStatusView,
- UserAvatarView userAvatarView,
+ FrameLayout userAvatarView,
KeyguardStatusBarView keyguardStatusBarView,
KeyguardUserSwitcherView keyguardUserSwitcherView) {
// Re-associate the KeyguardStatusViewController
@@ -1111,7 +1116,7 @@ public class NotificationPanelViewController extends PanelViewController {
!mKeyguardQsUserSwitchEnabled
&& mKeyguardUserSwitcherEnabled
&& isUserSwitcherEnabled;
- UserAvatarView userAvatarView = (UserAvatarView) reInflateStub(
+ FrameLayout userAvatarView = (FrameLayout) reInflateStub(
R.id.keyguard_qs_user_switch_view /* viewId */,
R.id.keyguard_qs_user_switch_stub /* stubId */,
R.layout.keyguard_qs_user_switch /* layoutId */,
@@ -1300,7 +1305,16 @@ public class NotificationPanelViewController extends PanelViewController {
float darkamount =
mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()
? 1.0f : mInterpolatedDarkAmount;
- mClockPositionAlgorithm.setup(mStatusBarHeaderHeightKeyguard,
+
+ float udfpsAodTopLocation = -1f;
+ if (mUpdateMonitor.isUdfpsEnrolled() && mAuthController.getUdfpsProps().size() > 0) {
+ FingerprintSensorPropertiesInternal props = mAuthController.getUdfpsProps().get(0);
+ udfpsAodTopLocation = props.sensorLocationY - props.sensorRadius
+ - mUdfpsMaxYBurnInOffset;
+ }
+
+ mClockPositionAlgorithm.setup(
+ mStatusBarHeaderHeightKeyguard,
totalHeight - bottomPadding,
mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
expandedFraction,
@@ -1312,7 +1326,10 @@ public class NotificationPanelViewController extends PanelViewController {
bypassEnabled, getUnlockedStackScrollerPadding(),
computeQsExpansionFraction(),
mDisplayTopInset,
- mShouldUseSplitNotificationShade);
+ mShouldUseSplitNotificationShade,
+ udfpsAodTopLocation,
+ mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
+ mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = animate || mAnimateNextPositionUpdate;
@@ -1795,6 +1812,15 @@ public class NotificationPanelViewController extends PanelViewController {
return !mQsTouchAboveFalsingThreshold;
}
+ /**
+ * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+ */
+ @Override
+ public void setMinFraction(float minFraction) {
+ mMinFraction = minFraction;
+ mDepthController.setPanelPullDownMinFraction(mMinFraction);
+ }
+
private float computeQsExpansionFraction() {
if (mQSAnimatingHiddenFromCollapsed) {
// When hiding QS from collapsed state, the expansion can sometimes temporarily
@@ -2309,6 +2335,12 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
top += mOverStretchAmount;
+ // Correction for instant expansion caused by HUN pull down/
+ if (mMinFraction > 0f && mMinFraction < 1f) {
+ float realFraction =
+ (getExpandedFraction() - mMinFraction) / (1f - mMinFraction);
+ top *= MathUtils.saturate(realFraction / mMinFraction);
+ }
bottom = getView().getBottom();
// notification bounds should take full screen width regardless of insets
left = 0;
@@ -3439,7 +3471,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
public void setPanelScrimMinFraction(float minFraction) {
- mBar.panelScrimMinFractionChanged(minFraction);
+ mBar.onPanelMinFractionChanged(minFraction);
}
public void clearNotificationEffects() {
@@ -3655,6 +3687,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
public void dozeTimeTick() {
+ mLockIconViewController.dozeTimeTick();
mKeyguardBottomArea.dozeTimeTick();
mKeyguardStatusViewController.dozeTimeTick();
if (mInterpolatedDarkAmount > 0) {
@@ -3868,6 +3901,9 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
protected TouchHandler createTouchHandler() {
return new TouchHandler() {
+
+ private long mLastTouchDownTime = -1L;
+
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
@@ -3897,6 +3933,19 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (event.getDownTime() == mLastTouchDownTime) {
+ // An issue can occur when swiping down after unlock, where multiple down
+ // events are received in this handler with identical downTimes. Until the
+ // source of the issue can be located, detect this case and ignore.
+ // see b/193350347
+ Log.w(TAG, "Duplicate down event detected... ignoring");
+ return true;
+ }
+ mLastTouchDownTime = event.getDownTime();
+ }
+
+
if (mBlockTouches || (mQsFullyExpanded && mQs != null
&& mQs.disallowPanelTouches())) {
return false;
@@ -3958,10 +4007,6 @@ public class NotificationPanelViewController extends PanelViewController {
mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX());
}
- if (mLockIconViewController.onTouchEvent(event)) {
- return true;
- }
-
handled |= super.onTouch(v, event);
return !mDozing || mPulsing || handled;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index 246810a2d70b..c26782b017c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -605,12 +605,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
}
@Override
- public void setLightRevealScrimAmount(float amount) {
- boolean lightRevealScrimOpaque = amount == 0;
- if (mCurrentState.mLightRevealScrimOpaque == lightRevealScrimOpaque) {
+ public void setLightRevealScrimOpaque(boolean opaque) {
+ if (mCurrentState.mLightRevealScrimOpaque == opaque) {
return;
}
- mCurrentState.mLightRevealScrimOpaque = lightRevealScrimOpaque;
+ mCurrentState.mLightRevealScrimOpaque = opaque;
apply(mCurrentState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index b5d9bd67bd2d..3807b4647fe1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -35,6 +35,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dock.DockManager;
@@ -88,6 +89,7 @@ public class NotificationShadeWindowViewController {
private final NotificationShadeDepthController mDepthController;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ private final LockIconViewController mLockIconViewController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private GestureDetector mGestureDetector;
@@ -138,7 +140,8 @@ public class NotificationShadeWindowViewController {
NotificationPanelViewController notificationPanelViewController,
SuperStatusBarViewFactory statusBarViewFactory,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ LockIconViewController lockIconViewController) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -163,6 +166,7 @@ public class NotificationShadeWindowViewController {
mStatusBarViewFactory = statusBarViewFactory;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mLockIconViewController = lockIconViewController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -235,6 +239,7 @@ public class NotificationShadeWindowViewController {
if (!isCancel && mService.shouldIgnoreTouch()) {
return false;
}
+
if (isDown) {
setTouchActive(true);
mTouchCancelled = false;
@@ -245,6 +250,7 @@ public class NotificationShadeWindowViewController {
if (mTouchCancelled || mExpandAnimationRunning) {
return false;
}
+
mFalsingCollector.onTouchEvent(ev);
mGestureDetector.onTouchEvent(ev);
mStatusBarKeyguardViewManager.onTouch(ev);
@@ -260,9 +266,17 @@ public class NotificationShadeWindowViewController {
if (isDown) {
mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev);
}
+
if (mStatusBarStateController.isDozing()) {
mService.mDozeScrimController.extendPulse();
}
+ mLockIconViewController.onTouchEvent(
+ ev,
+ () -> mService.wakeUpIfDozing(
+ SystemClock.uptimeMillis(),
+ mView,
+ "LOCK_ICON_TOUCH"));
+
// In case we start outside of the view bounds (below the status bar), we need to
// dispatch
// the touch manually as the view system can't accommodate for touches outside of
@@ -311,6 +325,12 @@ public class NotificationShadeWindowViewController {
// Capture all touch events in always-on.
return true;
}
+
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ // capture all touches if the alt auth bouncer is showing
+ return true;
+ }
+
boolean intercept = false;
if (mNotificationPanelViewController.isFullyExpanded()
&& mDragDownHelper.isDragDownEnabled()
@@ -338,6 +358,12 @@ public class NotificationShadeWindowViewController {
if (mStatusBarStateController.isDozing()) {
handled = !mService.isPulsing();
}
+
+ if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+ // eat the touch
+ handled = true;
+ }
+
if ((mDragDownHelper.isDragDownEnabled() && !handled)
|| mDragDownHelper.isDraggingDown()) {
// we still want to finish our drag down gesture when locking the screen
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index f1b6c7c3ad19..eca91a3f6fb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone;
import static java.lang.Float.isNaN;
+import android.annotation.CallSuper;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
@@ -162,7 +163,13 @@ public abstract class PanelBar extends FrameLayout {
return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
}
- public abstract void panelScrimMinFractionChanged(float minFraction);
+ /**
+ * Percentage of panel expansion offset, caused by pulling down on a heads-up.
+ */
+ @CallSuper
+ public void onPanelMinFractionChanged(float minFraction) {
+ mPanel.setMinFraction(minFraction);
+ }
/**
* @param frac the fraction from the expansion in [0, 1]
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 323a1128d3bb..99c0c13ad3d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -338,6 +338,13 @@ public abstract class PanelViewController {
protected abstract float getOpeningHeight();
/**
+ * Minimum fraction from where expansion should start. This is set when pulling down on a
+ * heads-up notification.
+ * @param minFraction Fraction from 0 to 1.
+ */
+ public abstract void setMinFraction(float minFraction);
+
+ /**
* @return whether the swiping direction is upwards and above a 45 degree angle compared to the
* horizontal direction
*/
@@ -1211,10 +1218,14 @@ public abstract class PanelViewController {
case MotionEvent.ACTION_MOVE:
final float h = y - mInitialTouchY;
addMovement(event);
- if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+ final boolean openShadeWithoutHun =
+ mPanelClosedOnDown && !mCollapsedAndHeadsUpOnDown;
+ if (canCollapsePanel || mTouchStartedInEmptyArea || mAnimatingOnDown
+ || openShadeWithoutHun) {
float hAbs = Math.abs(h);
float touchSlop = getTouchSlop(event);
- if ((h < -touchSlop || (mAnimatingOnDown && hAbs > touchSlop))
+ if ((h < -touchSlop
+ || ((openShadeWithoutHun || mAnimatingOnDown) && hAbs > touchSlop))
&& hAbs > Math.abs(x - mInitialTouchX)) {
cancelHeightAnimator();
startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
@@ -1227,10 +1238,7 @@ public abstract class PanelViewController {
mVelocityTracker.clear();
break;
}
-
- // Finally, if none of the above cases applies, ensure that touches do not get handled
- // by the contents of a panel that is not showing (a bit of a hack to avoid b/178277858)
- return (mView.getVisibility() != View.VISIBLE);
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index c300b11b9a34..2ca36b648b76 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -269,10 +269,11 @@ public class PhoneStatusBarView extends PanelBar {
}
@Override
- public void panelScrimMinFractionChanged(float minFraction) {
+ public void onPanelMinFractionChanged(float minFraction) {
if (isNaN(minFraction)) {
throw new IllegalArgumentException("minFraction cannot be NaN");
}
+ super.onPanelMinFractionChanged(minFraction);
if (mMinFraction != minFraction) {
mMinFraction = minFraction;
updateScrimFraction();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index cfcea9684c3b..27c129ad34c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -111,6 +111,20 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
*/
private boolean mTransitioningToFullShade;
+ /**
+ * Is there currently an unocclusion animation running. Used to avoid bright flickers
+ * of the notification scrim.
+ */
+ private boolean mUnOcclusionAnimationRunning;
+
+ /**
+ * Set whether an unocclusion animation is currently running on the notification panel. Used
+ * to avoid bright flickers of the notification scrim.
+ */
+ public void setUnocclusionAnimationRunning(boolean unocclusionAnimationRunning) {
+ mUnOcclusionAnimationRunning = unocclusionAnimationRunning;
+ }
+
@IntDef(prefix = {"VISIBILITY_"}, value = {
TRANSPARENT,
SEMI_TRANSPARENT,
@@ -418,7 +432,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
scheduleUpdate();
- } else if ((oldState == ScrimState.AOD // leaving doze
+ } else if (((oldState == ScrimState.AOD || oldState == ScrimState.PULSING) // leaving doze
&& (!mDozeParameters.getAlwaysOn() || mState == ScrimState.UNLOCKED))
|| (mState == ScrimState.AOD && !mDozeParameters.getDisplayNeedsBlanking())) {
// Scheduling a frame isn't enough when:
@@ -466,6 +480,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
public void onExpandingFinished() {
mTracking = false;
+ setUnocclusionAnimationRunning(false);
}
@VisibleForTesting
@@ -572,6 +587,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
if (isNaN(expansionFraction)) {
return;
}
+ expansionFraction = Interpolators
+ .getNotificationScrimAlpha(expansionFraction, false /* notification */);
boolean qsBottomVisible = qsPanelBottomY > 0;
if (mQsExpansion != expansionFraction || mQsBottomVisible != qsBottomVisible) {
mQsExpansion = expansionFraction;
@@ -694,6 +711,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mNotificationsTint = mState.getNotifTint();
mBehindTint = behindTint;
}
+ if (mUnOcclusionAnimationRunning && mState == ScrimState.KEYGUARD) {
+ // We're unoccluding the keyguard and don't want to have a bright flash.
+ mNotificationsAlpha = KEYGUARD_SCRIM_ALPHA;
+ mNotificationsTint = ScrimState.KEYGUARD.getNotifTint();
+ }
}
if (isNaN(mBehindAlpha) || isNaN(mInFrontAlpha) || isNaN(mNotificationsAlpha)) {
throw new IllegalStateException("Scrim opacity is NaN for state: " + mState
@@ -1242,6 +1264,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
pw.println(mDefaultScrimAlpha);
pw.print(" mExpansionFraction=");
pw.println(mPanelExpansion);
+ pw.print(" mExpansionAffectsAlpha=");
+ pw.println(mExpansionAffectsAlpha);
pw.print(" mState.getMaxLightRevealScrimAlpha=");
pw.println(mState.getMaxLightRevealScrimAlpha());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 06811932ac0c..15b8c67fd4ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -244,7 +244,8 @@ public enum ScrimState {
? mKeyguardFadingAwayDuration
: StatusBar.FADE_KEYGUARD_DURATION;
- mAnimateChange = !mLaunchingAffordanceWithPreview;
+ boolean fromAod = previousState == AOD || previousState == PULSING;
+ mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod;
mFrontTint = Color.TRANSPARENT;
mBehindTint = Color.BLACK;
@@ -278,15 +279,41 @@ public enum ScrimState {
BUBBLE_EXPANDED {
@Override
public void prepare(ScrimState previousState) {
+ mBehindAlpha = mClipQsScrim ? 1 : 0;
+ mNotifAlpha = 0;
+ mFrontAlpha = 0;
+
+ mAnimationDuration = mKeyguardFadingAway
+ ? mKeyguardFadingAwayDuration
+ : StatusBar.FADE_KEYGUARD_DURATION;
+
+ mAnimateChange = !mLaunchingAffordanceWithPreview;
+
mFrontTint = Color.TRANSPARENT;
- mBehindTint = Color.TRANSPARENT;
+ mBehindTint = Color.BLACK;
mBubbleTint = Color.BLACK;
+ mBlankScreen = false;
- mFrontAlpha = 0f;
- mBehindAlpha = mDefaultScrimAlpha;
+ if (previousState == ScrimState.AOD) {
+ // Set all scrims black, before they fade transparent.
+ updateScrimColor(mScrimInFront, 1f /* alpha */, Color.BLACK /* tint */);
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK /* tint */);
+ if (mScrimForBubble != null) {
+ updateScrimColor(mScrimForBubble, 1f /* alpha */, Color.BLACK /* tint */);
+ }
+
+ // Scrims should still be black at the end of the transition.
+ mFrontTint = Color.BLACK;
+ mBehindTint = Color.BLACK;
+ mBubbleTint = Color.BLACK;
+ mBlankScreen = true;
+ }
+
+ if (mClipQsScrim) {
+ updateScrimColor(mScrimBehind, 1f /* alpha */, Color.BLACK);
+ }
mAnimationDuration = ScrimController.ANIMATION_DURATION;
- mBlankScreen = false;
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 394e4ad76df1..88434cc214be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -592,6 +592,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationShadeWindowController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
mScrimController.setWallpaperSupportsAmbientMode(supportsAmbientMode);
+ mKeyguardViewMediator.setWallpaperSupportsAmbientMode(supportsAmbientMode);
}
};
@@ -1044,7 +1045,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mNotificationShadeWindowViewController,
mNotificationPanelViewController,
mAmbientIndicationContainer);
- mDozeParameters.addCallback(this::updateLightRevealScrimVisibility);
+ updateLightRevealScrimVisibility();
mConfigurationController.addCallback(this);
@@ -1148,6 +1149,9 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
mStatusBarView.setExpansionChangedListeners(mExpansionChangedListeners);
+ for (ExpansionChangedListener listener : mExpansionChangedListeners) {
+ sendInitialExpansionAmount(listener);
+ }
// CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
// mStatusBarView.mExpanded and mStatusBarView.mBouncerShowing are false.
@@ -1258,8 +1262,19 @@ public class StatusBar extends SystemUI implements DemoMode,
mScrimController.attachViews(scrimBehind, notificationsScrim, scrimInFront, scrimForBubble);
mLightRevealScrim = mNotificationShadeWindowView.findViewById(R.id.light_reveal_scrim);
- mLightRevealScrim.setRevealAmountListener(
- mNotificationShadeWindowController::setLightRevealScrimAmount);
+ mLightRevealScrim.setScrimOpaqueChangedListener((opaque) -> {
+ Runnable updateOpaqueness = () -> {
+ mNotificationShadeWindowController.setLightRevealScrimOpaque(
+ mLightRevealScrim.isScrimOpaque());
+ };
+ if (opaque) {
+ // Delay making the view opaque for a frame, because it needs some time to render
+ // otherwise this can lead to a flicker where the scrim doesn't cover the screen
+ mLightRevealScrim.post(updateOpaqueness);
+ } else {
+ updateOpaqueness.run();
+ }
+ });
mUnlockedScreenOffAnimationController.initialize(this, mLightRevealScrim);
updateLightRevealScrimVisibility();
@@ -3579,6 +3594,7 @@ public class StatusBar extends SystemUI implements DemoMode,
public void animateKeyguardUnoccluding() {
mNotificationPanelViewController.setExpandedFraction(0f);
animateExpandNotificationsPanel();
+ mScrimController.setUnocclusionAnimationRunning(true);
}
/**
@@ -3909,7 +3925,8 @@ public class StatusBar extends SystemUI implements DemoMode,
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mFeatureFlags.useNewLockscreenAnimations()
- && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) {
+ && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)
+ && !mBiometricUnlockController.isWakeAndUnlock()) {
mLightRevealScrim.setRevealAmount(1f - linear);
}
}
@@ -4449,8 +4466,11 @@ public class StatusBar extends SystemUI implements DemoMode,
|| mKeyguardStateController.isKeyguardFadingAway();
// Do not animate the scrim expansion when triggered by the fingerprint sensor.
- mScrimController.setExpansionAffectsAlpha(
- !mBiometricUnlockController.isBiometricUnlock());
+ boolean onKeyguardOrHidingIt = mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isKeyguardFadingAway()
+ || mKeyguardStateController.isKeyguardGoingAway();
+ mScrimController.setExpansionAffectsAlpha(!(mBiometricUnlockController.isBiometricUnlock()
+ && onKeyguardOrHidingIt));
boolean launchingAffordanceWithPreview =
mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
@@ -4465,10 +4485,8 @@ public class StatusBar extends SystemUI implements DemoMode,
ScrimState state = mStatusBarKeyguardViewManager.bouncerNeedsScrimming()
? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER;
mScrimController.transitionTo(state);
- } else if (isInLaunchTransition()
- || mLaunchCameraWhenFinishedWaking
- || launchingAffordanceWithPreview) {
- // TODO(b/170133395) Investigate whether Emergency Gesture flag should be included here.
+ } else if (launchingAffordanceWithPreview) {
+ // We want to avoid animating when launching with a preview.
mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
} else if (mBrightnessMirrorVisible) {
mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
@@ -4933,6 +4951,14 @@ public class StatusBar extends SystemUI implements DemoMode,
public void addExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
mExpansionChangedListeners.add(listener);
+ sendInitialExpansionAmount(listener);
+ }
+
+ private void sendInitialExpansionAmount(ExpansionChangedListener expansionChangedListener) {
+ if (mStatusBarView != null) {
+ expansionChangedListener.onExpansionChanged(mStatusBarView.getExpansionFraction(),
+ mStatusBarView.isExpanded());
+ }
}
public void removeExpansionChangedListener(@NonNull ExpansionChangedListener listener) {
@@ -4946,11 +4972,5 @@ public class StatusBar extends SystemUI implements DemoMode,
}
mLightRevealScrim.setAlpha(mScrimController.getState().getMaxLightRevealScrimAlpha());
- if (mFeatureFlags.useNewLockscreenAnimations()
- && (mDozeParameters.getAlwaysOn() || mDozeParameters.isQuickPickupEnabled())) {
- mLightRevealScrim.setVisibility(View.VISIBLE);
- } else {
- mLightRevealScrim.setVisibility(View.GONE);
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8a7708aaa8c2..1e3e47b75d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -53,6 +53,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.FaceAuthScreenBrightnessController;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.system.QuickStepContract;
@@ -92,7 +93,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// with the appear animations of the PIN/pattern/password views.
private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320;
- private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200;
+ // The duration to fade the nav bar content in/out when the device starts to sleep
+ private static final long NAV_BAR_CONTENT_FADE_DURATION = 125;
// Duration of the Keyguard dismissal animation in case the user is currently locked. This is to
// make everything a bit slower to bridge a gap until the user is unlocked and home screen has
@@ -196,7 +198,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private boolean mLastPulsing;
private int mLastBiometricMode;
private boolean mQsExpanded;
- private boolean mAnimatedToSleep;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -303,8 +304,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Sets a new alt auth interceptor.
*/
public void setAlternateAuthInterceptor(@NonNull AlternateAuthInterceptor authInterceptor) {
- mAlternateAuthInterceptor = authInterceptor;
- resetAlternateAuth(false);
+ if (!Objects.equals(mAlternateAuthInterceptor, authInterceptor)) {
+ mAlternateAuthInterceptor = authInterceptor;
+ resetAlternateAuth(false);
+ }
}
private void registerListeners() {
@@ -317,20 +320,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
- mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
- @Override
- public void onFinishedWakingUp() {
- mAnimatedToSleep = false;
- updateStates();
- }
-
- @Override
- public void onFinishedGoingToSleep() {
- mAnimatedToSleep =
- mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying();
- updateStates();
- }
- });
}
@Override
@@ -563,12 +552,26 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void onStartedWakingUp() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(false);
+ View currentView = getCurrentNavBarView();
+ if (currentView != null) {
+ currentView.animate()
+ .alpha(1f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start();
+ }
}
@Override
public void onStartedGoingToSleep() {
mStatusBar.getNotificationShadeWindowView().getWindowInsetsController()
.setAnimationsDisabled(true);
+ View currentView = getCurrentNavBarView();
+ if (currentView != null) {
+ currentView.animate()
+ .alpha(0f)
+ .setDuration(NAV_BAR_CONTENT_FADE_DURATION)
+ .start();
+ }
}
@Override
@@ -993,6 +996,25 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mStatusBar.onKeyguardViewManagerStatesUpdated();
}
+ /**
+ * Updates the visibility of the nav bar content views.
+ */
+ private void updateNavigationBarContentVisibility(boolean navBarContentVisible) {
+ final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ if (navBarView != null && navBarView.getCurrentView() != null) {
+ final View currentView = navBarView.getCurrentView();
+ currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE);
+ }
+ }
+
+ private View getCurrentNavBarView() {
+ final NavigationBarView navBarView = mStatusBar.getNavigationBarView();
+ return navBarView != null ? navBarView.getCurrentView() : null;
+ }
+
+ /**
+ * Updates the visibility of the nav bar window (which will cause insets changes).
+ */
protected void updateNavigationBarVisibility(boolean navBarVisible) {
if (mStatusBar.getNavigationBarView() != null) {
if (navBarVisible) {
@@ -1020,7 +1042,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean hideWhileDozing = mDozing && biometricMode != MODE_WAKE_AND_UNLOCK_PULSING;
boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
&& mGesturalNav;
- return (!mAnimatedToSleep && !keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
+ return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
|| mRemoteInputActive || keyguardWithGestureNav
|| mGlobalActionsVisible);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 47deb1f0084b..8821de077ec3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -310,17 +310,11 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
private void onNotificationRemoved(String key, StatusBarNotification old, int reason) {
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
- if (old != null) {
- if (CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
- && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()) {
- if (mStatusBarStateController.getState() == StatusBarState.SHADE
- && reason != NotificationListenerService.REASON_CLICK) {
- mCommandQueue.animateCollapsePanels();
- } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
+ if (old != null && CLOSE_PANEL_WHEN_EMPTIED && !hasActiveNotifications()
+ && !mNotificationPanel.isTracking() && !mNotificationPanel.isQsExpanded()
+ && mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED
&& !isCollapsing()) {
- mStatusBarStateController.setState(StatusBarState.KEYGUARD);
- }
- }
+ mStatusBarStateController.setState(StatusBarState.KEYGUARD);
}
}
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 6b52dca42eda..143aaba648da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -5,7 +5,9 @@ import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
+import android.database.ContentObserver
import android.os.Handler
+import android.provider.Settings
import android.view.View
import com.android.systemui.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
@@ -19,6 +21,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator
import com.android.systemui.statusbar.notification.stack.AnimationProperties
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.settings.GlobalSettings
import javax.inject.Inject
/**
@@ -46,15 +49,19 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private val statusBarStateControllerImpl: StatusBarStateControllerImpl,
private val keyguardViewMediatorLazy: dagger.Lazy<KeyguardViewMediator>,
private val keyguardStateController: KeyguardStateController,
- private val dozeParameters: dagger.Lazy<DozeParameters>
+ private val dozeParameters: dagger.Lazy<DozeParameters>,
+ private val globalSettings: GlobalSettings
) : WakefulnessLifecycle.Observer {
private val handler = Handler()
private lateinit var statusBar: StatusBar
private lateinit var lightRevealScrim: LightRevealScrim
+ private var animatorDurationScale = 1f
+ private var shouldAnimateInKeyguard = false
private var lightRevealAnimationPlaying = false
private var aodUiAnimationPlaying = false
+ private var callbacks = HashSet<Callback>()
/**
* The result of our decision whether to play the screen off animation in
@@ -66,11 +73,17 @@ class UnlockedScreenOffAnimationController @Inject constructor(
private val lightRevealAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
duration = LIGHT_REVEAL_ANIMATION_DURATION
interpolator = Interpolators.LINEAR
- addUpdateListener { lightRevealScrim.revealAmount = it.animatedValue as Float }
+ addUpdateListener {
+ lightRevealScrim.revealAmount = it.animatedValue as Float
+ sendUnlockedScreenOffProgressUpdate(
+ 1f - (it.animatedFraction as Float),
+ 1f - (it.animatedValue as Float))
+ }
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationCancel(animation: Animator?) {
lightRevealScrim.revealAmount = 1f
lightRevealAnimationPlaying = false
+ sendUnlockedScreenOffProgressUpdate(0f, 0f)
}
override fun onAnimationEnd(animation: Animator?) {
@@ -79,6 +92,12 @@ class UnlockedScreenOffAnimationController @Inject constructor(
})
}
+ val animatorDurationScaleObserver = object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ updateAnimatorDurationScale()
+ }
+ }
+
fun initialize(
statusBar: StatusBar,
lightRevealScrim: LightRevealScrim
@@ -86,14 +105,25 @@ class UnlockedScreenOffAnimationController @Inject constructor(
this.lightRevealScrim = lightRevealScrim
this.statusBar = statusBar
+ updateAnimatorDurationScale()
+ globalSettings.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE),
+ /* notify for descendants */ false,
+ animatorDurationScaleObserver)
wakefulnessLifecycle.addObserver(this)
}
+ fun updateAnimatorDurationScale() {
+ animatorDurationScale =
+ globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
+ }
+
/**
* Animates in the provided keyguard view, ending in the same position that it will be in on
* AOD.
*/
fun animateInKeyguard(keyguardView: View, after: Runnable) {
+ shouldAnimateInKeyguard = false
keyguardView.alpha = 0f
keyguardView.visibility = View.VISIBLE
@@ -138,6 +168,7 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Waking up, so reset this flag.
decidedToAnimateGoingToSleep = null
+ shouldAnimateInKeyguard = false
lightRevealAnimator.cancel()
handler.removeCallbacksAndMessages(null)
}
@@ -146,7 +177,6 @@ class UnlockedScreenOffAnimationController @Inject constructor(
// Set this to false in onFinishedWakingUp rather than onStartedWakingUp so that other
// observers (such as StatusBar) can ask us whether we were playing the screen off animation
// and reset accordingly.
- lightRevealAnimationPlaying = false
aodUiAnimationPlaying = false
// If we can't control the screen off animation, we shouldn't mess with the StatusBar's
@@ -167,15 +197,15 @@ class UnlockedScreenOffAnimationController @Inject constructor(
if (dozeParameters.get().shouldControlUnlockedScreenOff()) {
decidedToAnimateGoingToSleep = true
+ shouldAnimateInKeyguard = true
lightRevealAnimationPlaying = true
lightRevealAnimator.start()
-
handler.postDelayed({
aodUiAnimationPlaying = true
// Show AOD. That'll cause the KeyguardVisibilityHelper to call #animateInKeyguard.
statusBar.notificationPanelViewController.showAodUi()
- }, ANIMATE_IN_KEYGUARD_DELAY)
+ }, (ANIMATE_IN_KEYGUARD_DELAY * animatorDurationScale).toLong())
} else {
decidedToAnimateGoingToSleep = false
}
@@ -220,7 +250,21 @@ class UnlockedScreenOffAnimationController @Inject constructor(
return true
}
- /**
+ fun addCallback(callback: Callback) {
+ callbacks.add(callback)
+ }
+
+ fun removeCallback(callback: Callback) {
+ callbacks.remove(callback)
+ }
+
+ fun sendUnlockedScreenOffProgressUpdate(linear: Float, eased: Float) {
+ callbacks.forEach {
+ it.onUnlockedScreenOffProgressUpdate(linear, eased)
+ }
+ }
+
+/**
* Whether we're doing the light reveal animation or we're done with that and animating in the
* AOD UI.
*/
@@ -228,6 +272,10 @@ class UnlockedScreenOffAnimationController @Inject constructor(
return lightRevealAnimationPlaying || aodUiAnimationPlaying
}
+ fun shouldAnimateInKeyguard(): Boolean {
+ return shouldAnimateInKeyguard
+ }
+
/**
* Whether the light reveal animation is playing. The second part of the screen off animation,
* where AOD animates in, might still be playing if this returns false.
@@ -235,4 +283,8 @@ class UnlockedScreenOffAnimationController @Inject constructor(
fun isScreenOffLightRevealAnimationPlaying(): Boolean {
return lightRevealAnimationPlaying
}
-} \ No newline at end of file
+
+ interface Callback {
+ fun onUnlockedScreenOffProgressUpdate(linear: Float, eased: Float)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 6982631766f7..80a0a9824589 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -104,7 +104,16 @@ class OngoingCallController @Inject constructor(
}
}
+ // Fix for b/199600334
+ override fun onEntryCleanUp(entry: NotificationEntry) {
+ removeChipIfNeeded(entry)
+ }
+
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
+ removeChipIfNeeded(entry)
+ }
+
+ private fun removeChipIfNeeded(entry: NotificationEntry) {
if (entry.sbn.key == callNotificationInfo?.key) {
removeChip()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index ab58286859cc..6d6320e6962d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -40,6 +40,7 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.settings.UserTracker;
+import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import com.android.wifitrackerlib.WifiPickerTracker;
@@ -68,6 +69,7 @@ public class AccessPointControllerImpl
private final ArrayList<AccessPointCallback> mCallbacks = new ArrayList<AccessPointCallback>();
private final UserManager mUserManager;
+ private final UserTracker mUserTracker;
private final Executor mMainExecutor;
private @Nullable WifiPickerTracker mWifiPickerTracker;
@@ -84,6 +86,7 @@ public class AccessPointControllerImpl
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
mUserManager = userManager;
+ mUserTracker = userTracker;
mCurrentUser = userTracker.getUserId();
mMainExecutor = mainExecutor;
mWifiPickerTrackerFactory = wifiPickerTrackerFactory;
@@ -118,6 +121,11 @@ public class AccessPointControllerImpl
new UserHandle(mCurrentUser));
}
+ public boolean canConfigMobileData() {
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserHandle.of(mCurrentUser)) && mUserTracker.getUserInfo().isAdmin();
+ }
+
public void onUserSwitched(int newUserId) {
mCurrentUser = newUserId;
}
@@ -157,6 +165,15 @@ public class AccessPointControllerImpl
}
@Override
+ public MergedCarrierEntry getMergedCarrierEntry() {
+ if (mWifiPickerTracker == null) {
+ fireAcccessPointsCallback(Collections.emptyList());
+ return null;
+ }
+ return mWifiPickerTracker.getMergedCarrierEntry();
+ }
+
+ @Override
public int getIcon(WifiEntry ap) {
int level = ap.getLevel();
return ICONS[Math.max(0, level)];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index a0edc7c494bc..1e5251196379 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -149,7 +149,7 @@ public class BrightnessMirrorController
private void reinflate() {
int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
mStatusBarWindow.removeView(mBrightnessMirror);
- mBrightnessMirror = (FrameLayout) LayoutInflater.from(mBrightnessMirror.getContext())
+ mBrightnessMirror = (FrameLayout) LayoutInflater.from(mStatusBarWindow.getContext())
.inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false);
mToggleSliderController = setMirrorLayout();
mStatusBarWindow.addView(mBrightnessMirror, index);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
index 5e70d0dbc418..d838a05135e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchController.java
@@ -27,6 +27,7 @@ import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.FrameLayout;
import com.android.keyguard.KeyguardConstants;
import com.android.keyguard.KeyguardVisibilityHelper;
@@ -56,7 +57,7 @@ import javax.inject.Provider;
* Manages the user switch on the Keyguard that is used for opening the QS user panel.
*/
@KeyguardUserSwitcherScope
-public class KeyguardQsUserSwitchController extends ViewController<UserAvatarView> {
+public class KeyguardQsUserSwitchController extends ViewController<FrameLayout> {
private static final String TAG = "KeyguardQsUserSwitchController";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -76,6 +77,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
private final KeyguardVisibilityHelper mKeyguardVisibilityHelper;
private final KeyguardUserDetailAdapter mUserDetailAdapter;
private NotificationPanelViewController mNotificationPanelViewController;
+ private UserAvatarView mUserAvatarView;
UserSwitcherController.UserRecord mCurrentUser;
// State info for the user switch and keyguard
@@ -111,7 +113,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
@Inject
public KeyguardQsUserSwitchController(
- UserAvatarView view,
+ FrameLayout view,
Context context,
@Main Resources resources,
ScreenLifecycle screenLifecycle,
@@ -143,6 +145,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
protected void onInit() {
super.onInit();
if (DEBUG) Log.d(TAG, "onInit");
+ mUserAvatarView = mView.findViewById(R.id.kg_multi_user_avatar);
mAdapter = new UserSwitcherController.BaseUserAdapter(mUserSwitcherController) {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
@@ -150,11 +153,10 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
}
};
- mView.setOnClickListener(v -> {
+ mUserAvatarView.setOnClickListener(v -> {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
-
if (isListAnimating()) {
return;
}
@@ -163,7 +165,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
openQsUserPanel();
});
- mView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ mUserAvatarView.setAccessibilityDelegate(new View.AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
@@ -237,12 +239,12 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
R.string.accessibility_multi_user_switch_switcher);
}
- if (!TextUtils.equals(mView.getContentDescription(), contentDescription)) {
- mView.setContentDescription(contentDescription);
+ if (!TextUtils.equals(mUserAvatarView.getContentDescription(), contentDescription)) {
+ mUserAvatarView.setContentDescription(contentDescription);
}
int userId = mCurrentUser != null ? mCurrentUser.resolveId() : UserHandle.USER_NULL;
- mView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
+ mUserAvatarView.setDrawableWithBadge(getCurrentUserIcon().mutate(), userId);
}
Drawable getCurrentUserIcon() {
@@ -269,7 +271,7 @@ public class KeyguardQsUserSwitchController extends ViewController<UserAvatarVie
* Get the height of the keyguard user switcher view when closed.
*/
public int getUserIconHeight() {
- return mView.getHeight();
+ return mUserAvatarView.getHeight();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index fcfc9670b8b0..705761854532 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -245,5 +245,11 @@ public interface KeyguardStateController extends CallbackController<Callback> {
* animation.
*/
default void onKeyguardDismissAmountChanged() {}
+
+ /**
+ * Triggered when the notification panel is starting or has finished
+ * fading away on transition to an app.
+ */
+ default void onLaunchTransitionFadingAwayChanged() {}
}
}
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 64750bd803d5..f787ecf37372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -343,6 +343,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
@Override
public void setLaunchTransitionFadingAway(boolean fadingAway) {
mLaunchTransitionFadingAway = fadingAway;
+ new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
index 20b66f03192e..cd8894cae684 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherListView.java
@@ -103,7 +103,7 @@ public class KeyguardUserSwitcherListView extends AlphaOptimizedLinearLayout {
}
}
- if (animate) {
+ if (animate && userItemViews.length > 1) {
// AnimationUtils will immediately hide/show the first item in the array. Since the
// first view is the current user, we want to manage its visibility separately.
// Set first item to null so AnimationUtils ignores it.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index ef2ca985858d..eeea699a0b74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -23,6 +23,7 @@ import android.telephony.SubscriptionInfo;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+import com.android.wifitrackerlib.MergedCarrierEntry;
import com.android.wifitrackerlib.WifiEntry;
import java.util.List;
@@ -223,9 +224,11 @@ public interface NetworkController extends CallbackController<SignalCallback>, D
void addAccessPointCallback(AccessPointCallback callback);
void removeAccessPointCallback(AccessPointCallback callback);
void scanForAccessPoints();
+ MergedCarrierEntry getMergedCarrierEntry();
int getIcon(WifiEntry ap);
boolean connect(WifiEntry ap);
boolean canConfigWifi();
+ boolean canConfigMobileData();
public interface AccessPointCallback {
void onAccessPointsChanged(List<WifiEntry> accessPoints);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c49de7ab0e5d..af0d41331c28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -68,9 +68,12 @@ import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
+import com.android.systemui.qs.tiles.dialog.InternetDialogUtil;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -191,6 +194,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
private boolean mUserSetup;
private boolean mSimDetected;
private boolean mForceCellularValidated;
+ private InternetDialogFactory mInternetDialogFactory;
+ private Handler mMainHandler;
private ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@@ -221,7 +226,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
DemoModeController demoModeController,
CarrierConfigTracker carrierConfigTracker,
FeatureFlags featureFlags,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Main Handler handler,
+ InternetDialogFactory internetDialogFactory) {
this(context, connectivityManager,
telephonyManager,
telephonyListenerManager,
@@ -242,6 +249,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
featureFlags,
dumpManager);
mReceiverHandler.post(mRegisterListeners);
+ mMainHandler = handler;
+ mInternetDialogFactory = internetDialogFactory;
}
@VisibleForTesting
@@ -480,6 +489,9 @@ public class NetworkControllerImpl extends BroadcastReceiver
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ if (InternetDialogUtil.isProviderModelEnabled(mContext)) {
+ filter.addAction(Settings.Panel.ACTION_INTERNET_CONNECTIVITY);
+ }
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
@@ -788,6 +800,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
mConfig = Config.readConfig(mContext);
mReceiverHandler.post(this::handleConfigurationChanged);
break;
+ case Settings.Panel.ACTION_INTERNET_CONNECTIVITY:
+ mMainHandler.post(() -> mInternetDialogFactory.create(true,
+ mAccessPoints.canConfigMobileData(), mAccessPoints.canConfigWifi()));
+ break;
default:
int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
index 22fd93ed10ed..2d47c8f0b577 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -71,7 +71,11 @@ public class NextAlarmControllerImpl extends BroadcastReceiver
if (mNextAlarm != null) {
pw.println(new Date(mNextAlarm.getTriggerTime()));
pw.print(" PendingIntentPkg=");
- pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+ if (mNextAlarm.getShowIntent() != null) {
+ pw.println(mNextAlarm.getShowIntent().getCreatorPackage());
+ } else {
+ pw.println("showIntent=null");
+ }
} else {
pw.println("null");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 84d7c05ddc14..5d7d4809dd57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -77,7 +77,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -204,7 +203,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
final int stroke = colorized ? mContext.getResources().getDimensionPixelSize(
R.dimen.remote_input_view_text_stroke) : 0;
if (colorized) {
- final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final boolean dark = Notification.Builder.isColorDark(backgroundColor);
final int foregroundColor = dark ? Color.WHITE : Color.BLACK;
final int inverseColor = dark ? Color.BLACK : Color.WHITE;
editBgColor = backgroundColor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
index f258fb19ff7d..1158324567ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockController.java
@@ -23,6 +23,7 @@ public interface RotationLockController extends Listenable,
int getRotationLockOrientation();
boolean isRotationLockAffordanceVisible();
boolean isRotationLocked();
+ boolean isCameraRotationEnabled();
void setRotationLocked(boolean locked);
void setRotationLockedAtAngle(boolean locked, int rotation);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index 53d68d0ff0ac..c185928998c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -18,11 +18,13 @@ package com.android.systemui.statusbar.policy;
import android.content.Context;
import android.os.UserHandle;
+import android.provider.Settings.Secure;
import androidx.annotation.NonNull;
import com.android.internal.view.RotationPolicy;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -32,20 +34,22 @@ import javax.inject.Inject;
@SysUISingleton
public final class RotationLockControllerImpl implements RotationLockController {
private final Context mContext;
+ private final SecureSettings mSecureSettings;
private final CopyOnWriteArrayList<RotationLockControllerCallback> mCallbacks =
new CopyOnWriteArrayList<RotationLockControllerCallback>();
private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
new RotationPolicy.RotationPolicyListener() {
- @Override
- public void onChange() {
- notifyChanged();
- }
- };
+ @Override
+ public void onChange() {
+ notifyChanged();
+ }
+ };
@Inject
- public RotationLockControllerImpl(Context context) {
+ public RotationLockControllerImpl(Context context, SecureSettings secureSettings) {
mContext = context;
+ mSecureSettings = secureSettings;
setListening(true);
}
@@ -68,11 +72,16 @@ public final class RotationLockControllerImpl implements RotationLockController
return RotationPolicy.isRotationLocked(mContext);
}
+ public boolean isCameraRotationEnabled() {
+ return mSecureSettings.getIntForUser(Secure.CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT)
+ == 1;
+ }
+
public void setRotationLocked(boolean locked) {
RotationPolicy.setRotationLock(mContext, locked);
}
- public void setRotationLockedAtAngle(boolean locked, int rotation){
+ public void setRotationLockedAtAngle(boolean locked, int rotation) {
RotationPolicy.setRotationLockAtAngle(mContext, locked, rotation);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 41b1dd12639a..4e33529f3c36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -628,7 +628,7 @@ public class SmartReplyView extends ViewGroup {
mCurrentBackgroundColor = backgroundColor;
mCurrentColorized = colorized;
- final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
+ final boolean dark = Notification.Builder.isColorDark(backgroundColor);
mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
new file mode 100644
index 000000000000..ae9d9ee445f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.Context
+import android.text.StaticLayout
+import android.util.AttributeSet
+import android.widget.TextView
+import com.android.systemui.R
+
+/**
+ * View for showing a date that can toggle between two different formats depending on size.
+ *
+ * If no pattern can fit, it will display empty.
+ *
+ * @see R.styleable.VariableDateView_longDatePattern
+ * @see R.styleable.VariableDateView_shortDatePattern
+ */
+class VariableDateView(context: Context, attrs: AttributeSet) : TextView(context, attrs) {
+
+ val longerPattern: String
+ val shorterPattern: String
+
+ init {
+ val a = context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.VariableDateView,
+ 0, 0)
+ longerPattern = a.getString(R.styleable.VariableDateView_longDatePattern)
+ ?: context.getString(R.string.system_ui_date_pattern)
+ shorterPattern = a.getString(R.styleable.VariableDateView_shortDatePattern)
+ ?: context.getString(R.string.abbrev_month_day_no_year)
+
+ a.recycle()
+ }
+
+ /**
+ * Freeze the pattern switching
+ *
+ * Use during animations if the container will change its size but this view should not change
+ */
+ var freezeSwitching = false
+
+ private var onMeasureListener: OnMeasureListener? = null
+
+ fun onAttach(listener: OnMeasureListener?) {
+ onMeasureListener = listener
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd
+ if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) {
+ onMeasureListener?.onMeasureAction(availableWidth)
+ }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ }
+
+ fun getDesiredWidthForText(text: CharSequence): Float {
+ return StaticLayout.getDesiredWidth(text, paint)
+ }
+
+ interface OnMeasureListener {
+ fun onMeasureAction(availableWidth: Int)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
new file mode 100644
index 000000000000..99d84c4d0ced
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.icu.text.DateFormat
+import android.icu.text.DisplayContext
+import android.icu.util.Calendar
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.UserHandle
+import android.text.TextUtils
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.Dependency
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.ViewController
+import com.android.systemui.util.time.SystemClock
+import java.text.FieldPosition
+import java.text.ParsePosition
+import java.util.Date
+import java.util.Locale
+import javax.inject.Inject
+import javax.inject.Named
+
+@VisibleForTesting
+internal fun getTextForFormat(date: Date?, format: DateFormat): String {
+ return if (format === EMPTY_FORMAT) { // Check if same object
+ ""
+ } else format.format(date)
+}
+
+@VisibleForTesting
+internal fun getFormatFromPattern(pattern: String?): DateFormat {
+ if (TextUtils.equals(pattern, "")) {
+ return EMPTY_FORMAT
+ }
+ val l = Locale.getDefault()
+ val format = DateFormat.getInstanceForSkeleton(pattern, l)
+ format.setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)
+ return format
+}
+
+private val EMPTY_FORMAT: DateFormat = object : DateFormat() {
+ override fun format(
+ cal: Calendar,
+ toAppendTo: StringBuffer,
+ fieldPosition: FieldPosition
+ ): StringBuffer? {
+ return null
+ }
+
+ override fun parse(text: String, cal: Calendar, pos: ParsePosition) {}
+}
+
+private const val DEBUG = false
+private const val TAG = "VariableDateViewController"
+
+class VariableDateViewController(
+ private val systemClock: SystemClock,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ private val timeTickHandler: Handler,
+ view: VariableDateView
+) : ViewController<VariableDateView>(view) {
+
+ private var dateFormat: DateFormat? = null
+ private var datePattern = view.longerPattern
+ set(value) {
+ if (field == value) return
+ field = value
+ dateFormat = null
+ if (isAttachedToWindow) {
+ post(::updateClock)
+ }
+ }
+ private var lastWidth = Integer.MAX_VALUE
+ private var lastText = ""
+ private var currentTime = Date()
+
+ // View class easy accessors
+ private val longerPattern: String
+ get() = mView.longerPattern
+ private val shorterPattern: String
+ get() = mView.shorterPattern
+ private fun post(block: () -> Unit) = mView.handler?.post(block)
+
+ private val intentReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ // If the handler is null, it means we received a broadcast while the view has not
+ // finished being attached or in the process of being detached.
+ // In that case, do not post anything.
+ val handler = mView.handler ?: return
+ val action = intent.action
+ if (
+ Intent.ACTION_TIME_TICK == action ||
+ Intent.ACTION_TIME_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action ||
+ Intent.ACTION_LOCALE_CHANGED == action
+ ) {
+ if (
+ Intent.ACTION_LOCALE_CHANGED == action ||
+ Intent.ACTION_TIMEZONE_CHANGED == action
+ ) {
+ // need to get a fresh date format
+ handler.post { dateFormat = null }
+ }
+ handler.post(::updateClock)
+ }
+ }
+ }
+
+ private val onMeasureListener = object : VariableDateView.OnMeasureListener {
+ override fun onMeasureAction(availableWidth: Int) {
+ if (availableWidth != lastWidth) {
+ // maybeChangeFormat will post if the pattern needs to change.
+ maybeChangeFormat(availableWidth)
+ lastWidth = availableWidth
+ }
+ }
+ }
+
+ override fun onViewAttached() {
+ val filter = IntentFilter().apply {
+ addAction(Intent.ACTION_TIME_TICK)
+ addAction(Intent.ACTION_TIME_CHANGED)
+ addAction(Intent.ACTION_TIMEZONE_CHANGED)
+ addAction(Intent.ACTION_LOCALE_CHANGED)
+ }
+
+ broadcastDispatcher.registerReceiver(intentReceiver, filter,
+ HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
+
+ post(::updateClock)
+ mView.onAttach(onMeasureListener)
+ }
+
+ override fun onViewDetached() {
+ dateFormat = null
+ mView.onAttach(null)
+ broadcastDispatcher.unregisterReceiver(intentReceiver)
+ }
+
+ private fun updateClock() {
+ if (dateFormat == null) {
+ dateFormat = getFormatFromPattern(datePattern)
+ }
+
+ currentTime.time = systemClock.currentTimeMillis()
+
+ val text = getTextForFormat(currentTime, dateFormat!!)
+ if (text != lastText) {
+ mView.setText(text)
+ lastText = text
+ }
+ }
+
+ private fun maybeChangeFormat(availableWidth: Int) {
+ if (mView.freezeSwitching ||
+ availableWidth > lastWidth && datePattern == longerPattern ||
+ availableWidth < lastWidth && datePattern == ""
+ ) {
+ // Nothing to do
+ return
+ }
+ if (DEBUG) Log.d(TAG, "Width changed. Maybe changing pattern")
+ // Start with longer pattern and see what fits
+ var text = getTextForFormat(currentTime, getFormatFromPattern(longerPattern))
+ var length = mView.getDesiredWidthForText(text)
+ if (length <= availableWidth) {
+ changePattern(longerPattern)
+ return
+ }
+
+ text = getTextForFormat(currentTime, getFormatFromPattern(shorterPattern))
+ length = mView.getDesiredWidthForText(text)
+ if (length <= availableWidth) {
+ changePattern(shorterPattern)
+ return
+ }
+
+ changePattern("")
+ }
+
+ private fun changePattern(newPattern: String) {
+ if (newPattern.equals(datePattern)) return
+ if (DEBUG) Log.d(TAG, "Changing pattern to $newPattern")
+ datePattern = newPattern
+ }
+
+ class Factory @Inject constructor(
+ private val systemClock: SystemClock,
+ private val broadcastDispatcher: BroadcastDispatcher,
+ @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
+ ) {
+ fun create(view: VariableDateView): VariableDateViewController {
+ return VariableDateViewController(
+ systemClock,
+ broadcastDispatcher,
+ handler,
+ view
+ )
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
index c3b4fbe9a13d..e2d0bb9991ed 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayApplier.java
@@ -132,14 +132,18 @@ public class ThemeOverlayApplier implements Dumpable {
/* Target package for each overlay category. */
private final Map<String, String> mCategoryToTargetPackage = new ArrayMap<>();
private final OverlayManager mOverlayManager;
- private final Executor mExecutor;
+ private final Executor mBgExecutor;
+ private final Executor mMainExecutor;
private final String mLauncherPackage;
private final String mThemePickerPackage;
- public ThemeOverlayApplier(OverlayManager overlayManager, Executor executor,
+ public ThemeOverlayApplier(OverlayManager overlayManager,
+ Executor bgExecutor,
+ Executor mainExecutor,
String launcherPackage, String themePickerPackage, DumpManager dumpManager) {
mOverlayManager = overlayManager;
- mExecutor = executor;
+ mBgExecutor = bgExecutor;
+ mMainExecutor = mainExecutor;
mLauncherPackage = launcherPackage;
mThemePickerPackage = themePickerPackage;
mTargetPackageToCategories.put(ANDROID_PACKAGE, Sets.newHashSet(
@@ -170,12 +174,12 @@ public class ThemeOverlayApplier implements Dumpable {
* Apply the set of overlay packages to the set of {@code UserHandle}s provided. Overlays that
* affect sysui will also be applied to the system user.
*/
- void applyCurrentUserOverlays(
+ public void applyCurrentUserOverlays(
Map<String, OverlayIdentifier> categoryToPackage,
FabricatedOverlay[] pendingCreation,
int currentUser,
Set<UserHandle> managedProfiles) {
- mExecutor.execute(() -> {
+ mBgExecutor.execute(() -> {
// Disable all overlays that have not been specified in the user setting.
final Set<String> overlayCategoriesToDisable = new HashSet<>(THEME_CATEGORIES);
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 98b4209ede00..bfa50bcee270 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -36,6 +36,7 @@ import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
@@ -63,6 +64,8 @@ public class UsbPermissionActivity extends AlertActivity
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ getWindow().addPrivateFlags(
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
Intent intent = getIntent();
mDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
diff --git a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
index de5a3637fe9f..14190fa752c3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/util/CarrierConfigTracker.java
@@ -23,8 +23,9 @@ import android.content.IntentFilter;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import javax.inject.Inject;
@@ -34,39 +35,54 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class CarrierConfigTracker extends BroadcastReceiver {
- private final SparseArray<Boolean> mCallStrengthConfigs = new SparseArray<>();
- private final SparseArray<Boolean> mNoCallingConfigs = new SparseArray<>();
+ private final SparseBooleanArray mCallStrengthConfigs = new SparseBooleanArray();
+ private final SparseBooleanArray mNoCallingConfigs = new SparseBooleanArray();
+ private final SparseBooleanArray mCarrierProvisionsWifiMergedNetworks =
+ new SparseBooleanArray();
private final CarrierConfigManager mCarrierConfigManager;
private boolean mDefaultCallStrengthConfigLoaded;
private boolean mDefaultCallStrengthConfig;
private boolean mDefaultNoCallingConfigLoaded;
private boolean mDefaultNoCallingConfig;
+ private boolean mDefaultCarrierProvisionsWifiMergedNetworksLoaded;
+ private boolean mDefaultCarrierProvisionsWifiMergedNetworks;
@Inject
- public CarrierConfigTracker(Context context) {
+ public CarrierConfigTracker(Context context, BroadcastDispatcher broadcastDispatcher) {
mCarrierConfigManager = context.getSystemService(CarrierConfigManager.class);
- context.registerReceiver(
+ broadcastDispatcher.registerReceiver(
this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
}
@Override
public void onReceive(Context context, Intent intent) {
- if (intent.getAction() == CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED) {
- int subId = intent.getIntExtra(
- CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
- SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- return;
- }
- PersistableBundle b = mCarrierConfigManager.getConfigForSubId(subId);
- if (b != null) {
- boolean hideNoCallingConfig = b.getBoolean(
- CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL);
- boolean displayCallStrengthIcon = b.getBoolean(
- CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL);
- mCallStrengthConfigs.put(subId, displayCallStrengthIcon);
- mNoCallingConfigs.put(subId, hideNoCallingConfig);
- }
+ if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ return;
+ }
+
+ final int subId = intent.getIntExtra(
+ CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ return;
+ }
+
+ final PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+ if (config == null) {
+ return;
+ }
+
+ synchronized (mCallStrengthConfigs) {
+ mCallStrengthConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL));
+ }
+ synchronized (mNoCallingConfigs) {
+ mNoCallingConfigs.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL));
+ }
+ synchronized (mCarrierProvisionsWifiMergedNetworks) {
+ mCarrierProvisionsWifiMergedNetworks.put(subId, config.getBoolean(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL));
}
}
@@ -74,8 +90,10 @@ public class CarrierConfigTracker extends BroadcastReceiver {
* Returns the KEY_DISPLAY_CALL_STRENGTH_INDICATOR_BOOL value for the given subId.
*/
public boolean getCallStrengthConfig(int subId) {
- if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
- return mCallStrengthConfigs.get(subId);
+ synchronized (mCallStrengthConfigs) {
+ if (mCallStrengthConfigs.indexOfKey(subId) >= 0) {
+ return mCallStrengthConfigs.get(subId);
+ }
}
if (!mDefaultCallStrengthConfigLoaded) {
mDefaultCallStrengthConfig =
@@ -90,8 +108,10 @@ public class CarrierConfigTracker extends BroadcastReceiver {
* Returns the KEY_USE_IP_FOR_CALLING_INDICATOR_BOOL value for the given subId.
*/
public boolean getNoCallingConfig(int subId) {
- if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
- return mNoCallingConfigs.get(subId);
+ synchronized (mNoCallingConfigs) {
+ if (mNoCallingConfigs.indexOfKey(subId) >= 0) {
+ return mNoCallingConfigs.get(subId);
+ }
}
if (!mDefaultNoCallingConfigLoaded) {
mDefaultNoCallingConfig =
@@ -101,4 +121,22 @@ public class CarrierConfigTracker extends BroadcastReceiver {
}
return mDefaultNoCallingConfig;
}
+
+ /**
+ * Returns the KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL value for the given subId.
+ */
+ public boolean getCarrierProvisionsWifiMergedNetworksBool(int subId) {
+ synchronized (mCarrierProvisionsWifiMergedNetworks) {
+ if (mCarrierProvisionsWifiMergedNetworks.indexOfKey(subId) >= 0) {
+ return mCarrierProvisionsWifiMergedNetworks.get(subId);
+ }
+ }
+ if (!mDefaultCarrierProvisionsWifiMergedNetworksLoaded) {
+ mDefaultCarrierProvisionsWifiMergedNetworks =
+ CarrierConfigManager.getDefaultConfig().getBoolean(
+ CarrierConfigManager.KEY_CARRIER_PROVISIONS_WIFI_MERGED_NETWORKS_BOOL);
+ mDefaultCarrierProvisionsWifiMergedNetworksLoaded = true;
+ }
+ return mDefaultCarrierProvisionsWifiMergedNetworks;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
index 90e022a52d7a..bd1103982017 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/ProximitySensor.java
@@ -87,15 +87,23 @@ public class ProximitySensor implements ThresholdSensor {
&& (mLastPrimaryEvent == null
|| !mLastPrimaryEvent.getBelow()
|| !event.getBelow())) {
- mSecondaryThresholdSensor.pause();
+ chooseSensor();
if (mLastPrimaryEvent == null || !mLastPrimaryEvent.getBelow()) {
// Only check the secondary as long as the primary thinks we're near.
- mCancelSecondaryRunnable = null;
+ if (mCancelSecondaryRunnable != null) {
+ mCancelSecondaryRunnable.run();
+ mCancelSecondaryRunnable = null;
+ }
return;
} else {
// Check this sensor again in a moment.
- mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(
- mSecondaryThresholdSensor::resume, SECONDARY_PING_INTERVAL_MS);
+ mCancelSecondaryRunnable = mDelayableExecutor.executeDelayed(() -> {
+ // This is safe because we know that mSecondaryThresholdSensor
+ // is loaded, otherwise we wouldn't be here.
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.resume();
+ },
+ SECONDARY_PING_INTERVAL_MS);
}
}
logDebug("Secondary sensor event: " + event.getBelow() + ".");
@@ -159,12 +167,8 @@ public class ProximitySensor implements ThresholdSensor {
* of what is reported by the primary sensor.
*/
public void setSecondarySafe(boolean safe) {
- mSecondarySafe = safe;
- if (!mSecondarySafe) {
- mSecondaryThresholdSensor.pause();
- } else {
- mSecondaryThresholdSensor.resume();
- }
+ mSecondarySafe = mSecondaryThresholdSensor.isLoaded() && safe;
+ chooseSensor();
}
/**
@@ -209,16 +213,30 @@ public class ProximitySensor implements ThresholdSensor {
return;
}
if (!mInitializedListeners) {
+ mPrimaryThresholdSensor.pause();
+ mSecondaryThresholdSensor.pause();
mPrimaryThresholdSensor.register(mPrimaryEventListener);
- if (!mSecondarySafe) {
- mSecondaryThresholdSensor.pause();
- }
mSecondaryThresholdSensor.register(mSecondaryEventListener);
mInitializedListeners = true;
}
logDebug("Registering sensor listener");
- mPrimaryThresholdSensor.resume();
+
mRegistered = true;
+ chooseSensor();
+ }
+
+ private void chooseSensor() {
+ mExecution.assertIsMainThread();
+ if (!mRegistered || mPaused || mListeners.isEmpty()) {
+ return;
+ }
+ if (mSecondarySafe) {
+ mSecondaryThresholdSensor.resume();
+ mPrimaryThresholdSensor.pause();
+ } else {
+ mPrimaryThresholdSensor.resume();
+ mSecondaryThresholdSensor.pause();
+ }
}
/**
@@ -312,7 +330,7 @@ public class ProximitySensor implements ThresholdSensor {
}
if (!mSecondarySafe && !event.getBelow()) {
- mSecondaryThresholdSensor.pause();
+ chooseSensor();
}
mLastEvent = event;
diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
index 2b4b49b82df1..99177778310d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletActivity.java
@@ -116,7 +116,7 @@ public class WalletActivity extends LifecycleActivity implements
if (toolbar != null) {
setActionBar(toolbar);
}
- setTitle("");
+ getActionBar().setDisplayShowTitleEnabled(false);
getActionBar().setDisplayHomeAsUpEnabled(true);
getActionBar().setHomeAsUpIndicator(getHomeIndicatorDrawable());
getActionBar().setHomeActionContentDescription(R.string.accessibility_desc_close);
@@ -220,6 +220,12 @@ public class WalletActivity extends LifecycleActivity implements
}
@Override
+ protected void onStop() {
+ super.onStop();
+ finish();
+ }
+
+ @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.wallet_activity_options_menu, menu);
return super.onCreateOptionsMenu(menu);
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 5f1fe927f5d6..763a5cb810f9 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -93,6 +93,13 @@
<activity android:name="com.android.systemui.screenshot.RecyclerViewActivity"
android:exported="false" />
+ <!-- started from UsbDeviceSettingsManager -->
+ <activity android:name=".usb.UsbPermissionActivityTest$UsbPermissionActivityTestable"
+ android:exported="false"
+ android:theme="@style/Theme.SystemUI.Dialog.Alert"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true" />
+
<provider
android:name="androidx.startup.InitializationProvider"
tools:replace="android:authorities"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 06b0bb25e01c..8077deaf5fda 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -119,6 +119,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mNotificationIcons.getLayoutParams()).thenReturn(
mock(RelativeLayout.LayoutParams.class));
when(mView.getContext()).thenReturn(getContext());
+ when(mView.getResources()).thenReturn(mResources);
when(mView.findViewById(R.id.animatable_clock_view)).thenReturn(mClockView);
when(mView.findViewById(R.id.animatable_clock_view_large)).thenReturn(mLargeClockView);
@@ -127,7 +128,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mView.isAttachedToWindow()).thenReturn(true);
- when(mResources.getString(anyInt())).thenReturn("h:mm");
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController = new KeyguardClockSwitchController(
mView,
@@ -142,7 +142,8 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mBypassController,
mSmartspaceController,
mKeyguardUnlockAnimationController,
- mSmartSpaceTransitionController
+ mSmartSpaceTransitionController,
+ mResources
);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 39d5314107ee..8dd5d6c01394 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -565,8 +565,9 @@ public class AuthControllerTest extends SysuiTestCase {
credentialAllowed,
true /* requireConfirmation */,
0 /* userId */,
- "testPackage",
0 /* operationId */,
+ "testPackage",
+ 1 /* requestId */,
BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT);
}
@@ -612,7 +613,7 @@ public class AuthControllerTest extends SysuiTestCase {
@Override
protected AuthDialog buildDialog(PromptInfo promptInfo,
boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed,
- String opPackageName, boolean skipIntro, long operationId,
+ String opPackageName, boolean skipIntro, long operationId, long requestId,
@BiometricManager.BiometricMultiSensorMode int multiSensorConfig) {
mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index d87a26b096fd..f2f0029708ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -19,16 +19,23 @@ package com.android.systemui.biometrics
import android.graphics.PointF
import android.hardware.biometrics.BiometricSourceType
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,6 +49,8 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import javax.inject.Provider
+
@SmallTest
@RunWith(AndroidTestingRunner::class)
class AuthRippleControllerTest : SysuiTestCase() {
@@ -52,26 +61,39 @@ class AuthRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
@Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock private lateinit var bypassController: KeyguardBypassController
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
+ @Mock private lateinit var udfpsControllerProvider: Provider<UdfpsController>
+ @Mock private lateinit var udfpsController: UdfpsController
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var lightRevealScrim: LightRevealScrim
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ `when`(udfpsControllerProvider.get()).thenReturn(udfpsController)
+
controller = AuthRippleController(
statusBar,
context,
authController,
configurationController,
keyguardUpdateMonitor,
+ keyguardStateController,
+ wakefulnessLifecycle,
commandRegistry,
notificationShadeWindowController,
bypassController,
biometricUnlockController,
+ udfpsControllerProvider,
+ statusBarStateController,
rippleView
)
controller.init()
+ `when`(statusBar.lightRevealScrim).thenReturn(lightRevealScrim)
}
@Test
@@ -93,7 +115,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
// THEN update sensor location and show ripple
verify(rippleView).setSensorLocation(fpsLocation)
- verify(rippleView).startRipple(any(), any())
+ verify(rippleView).startUnlockedRipple(any())
}
@Test
@@ -114,7 +136,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */)
// THEN no ripple
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -135,7 +157,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */)
// THEN no ripple
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -159,7 +181,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
// THEN show ripple
verify(rippleView).setSensorLocation(faceLocation)
- verify(rippleView).startRipple(any(), any())
+ verify(rippleView).startUnlockedRipple(any())
}
@Test
@@ -179,7 +201,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
false /* isStrongBiometric */)
// THEN no ripple
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -194,7 +216,7 @@ class AuthRippleControllerTest : SysuiTestCase() {
0 /* userId */,
BiometricSourceType.FACE /* type */,
false /* isStrongBiometric */)
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
}
@Test
@@ -209,7 +231,39 @@ class AuthRippleControllerTest : SysuiTestCase() {
0 /* userId */,
BiometricSourceType.FINGERPRINT /* type */,
false /* isStrongBiometric */)
- verify(rippleView, never()).startRipple(any(), any())
+ verify(rippleView, never()).startUnlockedRipple(any())
+ }
+
+ @Test
+ fun registersAndDeregisters() {
+ controller.onViewAttached()
+ val captor = ArgumentCaptor
+ .forClass(KeyguardStateController.Callback::class.java)
+ verify(keyguardStateController).addCallback(captor.capture())
+ val captor2 = ArgumentCaptor
+ .forClass(WakefulnessLifecycle.Observer::class.java)
+ verify(wakefulnessLifecycle).addObserver(captor2.capture())
+ controller.onViewDetached()
+ verify(keyguardStateController).removeCallback(any())
+ verify(wakefulnessLifecycle).removeObserver(any())
+ }
+
+ @Test
+ @RunWithLooper(setAsMainLooper = true)
+ fun testAnimatorRunWhenWakeAndUnlock() {
+ val fpsLocation = PointF(5f, 5f)
+ `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
+ controller.onViewAttached()
+ `when`(keyguardUpdateMonitor.isKeyguardVisible).thenReturn(true)
+ `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+
+ controller.showRipple(BiometricSourceType.FINGERPRINT)
+ assertTrue("reveal didn't start on keyguardFadingAway",
+ controller.startLightRevealScrimOnKeyguardFadingAway)
+ `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true)
+ controller.onKeyguardFadingAwayChanged()
+ assertFalse("reveal triggers multiple times",
+ controller.startLightRevealScrimOnKeyguardFadingAway)
}
@Test
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 2120b0ee4790..04ebee8913c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.biometrics;
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -55,7 +57,6 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -63,12 +64,14 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.util.time.SystemClock;
import org.junit.Before;
import org.junit.Rule;
@@ -120,8 +123,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
private IUdfpsOverlayControllerCallback mUdfpsOverlayControllerCallback;
@Mock
private FalsingManager mFalsingManager;
@@ -147,6 +148,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
private Handler mHandler;
@Mock
private ConfigurationController mConfigurationController;
+ @Mock
+ private SystemClock mSystemClock;
+ @Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFgExecutor;
@@ -186,6 +191,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view, null))
.thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD
when(mEnrollView.getContext()).thenReturn(mContext);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
@@ -216,7 +222,6 @@ public class UdfpsControllerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager,
mDumpManager,
mKeyguardUpdateMonitor,
- mKeyguardViewMediator,
mFalsingManager,
mPowerManager,
mAccessibilityManager,
@@ -229,7 +234,9 @@ public class UdfpsControllerTest extends SysuiTestCase {
mKeyguardBypassController,
mDisplayManager,
mHandler,
- mConfigurationController);
+ mConfigurationController,
+ mSystemClock,
+ mUnlockedScreenOffAnimationController);
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
@@ -413,6 +420,21 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
+ public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
+ // GIVEN overlay was showing and the udfps bouncer is showing
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
+
+ // WHEN the overlay is hidden
+ mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+ mFgExecutor.runAllReady();
+
+ // THEN the udfps bouncer is reset
+ verify(mStatusBarKeyguardViewManager).resetAlternateAuth(eq(true));
+ }
+
+ @Test
public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
@@ -456,11 +478,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Test
public void aodInterrupt() throws RemoteException {
- // GIVEN that the overlay is showing and screen is on
+ // GIVEN that the overlay is showing and screen is on and fp is running
mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
// WHEN fingerprint is requested because of AOD interrupt
mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
// THEN illumination begins
@@ -478,6 +501,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
// WHEN it is cancelled
@@ -493,6 +517,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
mScreenObserver.onScreenTurnedOn();
mFgExecutor.runAllReady();
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
when(mUdfpsView.isIlluminationRequested()).thenReturn(true);
// WHEN it times out
@@ -511,6 +536,23 @@ public class UdfpsControllerTest extends SysuiTestCase {
mFgExecutor.runAllReady();
// WHEN aod interrupt is received
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+
+ // THEN no illumination because screen is off
+ verify(mUdfpsView, never()).startIllumination(any());
+ }
+
+ @Test
+ public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+ // GIVEN showing overlay
+ mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID,
+ IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+
+ // WHEN aod interrupt is received when the fingerprint service isn't running
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false);
mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
// THEN no illumination because screen is off
@@ -544,5 +586,10 @@ public class UdfpsControllerTest extends SysuiTestCase {
eq(mUdfpsController.EFFECT_CLICK),
eq("udfps-onStart"),
eq(UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES));
+
+ // THEN make sure vibration attributes has so that it always will play the haptic,
+ // even in battery saver mode
+ assertEquals(USAGE_ASSISTANCE_ACCESSIBILITY,
+ UdfpsController.VIBRATION_SONIFICATION_ATTRIBUTES.getUsage());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0c03a51f816e..4b35de15147b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -18,8 +18,13 @@ package com.android.systemui.biometrics;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -30,7 +35,6 @@ import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -39,8 +43,11 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +57,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -75,11 +84,16 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
private KeyguardViewMediator mKeyguardViewMediator;
@Mock
private ConfigurationController mConfigurationController;
@Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ @Mock
private UdfpsController mUdfpsController;
+ private FakeSystemClock mSystemClock = new FakeSystemClock();
private UdfpsKeyguardViewController mController;
@@ -88,14 +102,15 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
private StatusBarStateController.StateListener mStatusBarStateListener;
@Captor private ArgumentCaptor<StatusBar.ExpansionChangedListener> mExpansionListenerCaptor;
- private StatusBar.ExpansionChangedListener mExpansionListener;
+ private List<StatusBar.ExpansionChangedListener> mExpansionListeners;
@Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
mAltAuthInterceptorCaptor;
private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
- @Captor private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateMonitorCallbackCaptor;
- private KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback;
+ @Captor private ArgumentCaptor<KeyguardStateController.Callback>
+ mKeyguardStateControllerCallbackCaptor;
+ private KeyguardStateController.Callback mKeyguardStateControllerCallback;
@Before
public void setUp() {
@@ -109,18 +124,19 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
mStatusBar,
mStatusBarKeyguardViewManager,
mKeyguardUpdateMonitor,
- mExecutor,
mDumpManager,
- mKeyguardViewMediator,
mLockscreenShadeTransitionController,
mConfigurationController,
+ mSystemClock,
+ mKeyguardStateController,
+ mUnlockedScreenOffAnimationController,
mUdfpsController);
}
@Test
public void testRegistersExpansionChangedListenerOnAttached() {
mController.onViewAttached();
- captureExpansionListener();
+ captureExpansionListeners();
}
@Test
@@ -149,11 +165,15 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
public void testListenersUnregisteredOnDetached() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
mController.onViewDetached();
verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
- verify(mStatusBar).removeExpansionChangedListener(mExpansionListener);
+ for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
+ verify(mStatusBar).removeExpansionChangedListener(listener);
+ }
+ verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
}
@Test
@@ -172,7 +192,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
public void testShouldPauseAuthBouncerShowing() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -183,7 +202,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
public void testShouldNotPauseAuthOnKeyguard() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -191,10 +209,25 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testShouldPauseAuthIsLaunchTransitionFadingAway() {
+ // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard)
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+ // WHEN isLaunchTransitionFadingAway=true
+ captureKeyguardStateControllerCallback();
+ when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true);
+ mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged();
+
+ // THEN pause auth
+ assertTrue(mController.shouldPauseAuth());
+ }
+
+ @Test
public void testShouldPauseAuthOnShadeLocked() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED);
@@ -205,7 +238,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
public void testShouldPauseAuthOnShade() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
// WHEN not on keyguard yet (shade = home)
sendStatusBarStateChanged(StatusBarState.SHADE);
@@ -218,7 +250,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
public void testShouldPauseAuthAnimatingScreenOffFromShade() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
// WHEN transitioning from home/shade => keyguard + animating screen off
mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD);
@@ -232,7 +263,6 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
public void testDoNotPauseAuthAnimatingScreenOffFromLS() {
mController.onViewAttached();
captureStatusBarStateListeners();
- captureExpansionListener();
// WHEN animating screen off transition from LS => AOD
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
@@ -273,6 +303,127 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
verify(mStatusBarKeyguardViewManager).removeAlternateAuthInterceptor(mAltAuthInterceptor);
}
+ @Test
+ public void testHiddenUdfpsBouncerOnTouchOutside_nothingHappens() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer isn't showing
+ mAltAuthInterceptor.hideAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view
+ mController.onTouchOutsideView();
+
+ // THEN bouncer / alt auth methods are never called
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testShowingUdfpsBouncerOnTouchOutsideWithinThreshold_nothingHappens() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer is showing
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view 200ms later (just within threshold)
+ mSystemClock.advanceTime(200);
+ mController.onTouchOutsideView();
+
+ // THEN bouncer / alt auth methods are never called because not enough time has passed
+ verify(mStatusBarKeyguardViewManager, never()).showBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testShowingUdfpsBouncerOnTouchOutsideAboveThreshold_showInputBouncer() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureAltAuthInterceptor();
+
+ // GIVEN udfps bouncer is showing
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // WHEN touch is observed outside the view 205ms later
+ mSystemClock.advanceTime(205);
+ mController.onTouchOutsideView();
+
+ // THEN show the bouncer and reset alt auth
+ verify(mStatusBarKeyguardViewManager).showBouncer(eq(true));
+ verify(mStatusBarKeyguardViewManager).resetAlternateAuth(anyBoolean());
+ }
+
+ @Test
+ public void testFadeInWithStatusBarExpansion() {
+ // GIVEN view is attached
+ mController.onViewAttached();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ reset(mView);
+
+ // WHEN status bar expansion is 0
+ updateStatusBarExpansion(0, true);
+
+ // THEN alpha is 0
+ verify(mView).setUnpausedAlpha(0);
+ }
+
+ @Test
+ public void testShowUdfpsBouncer() {
+ // GIVEN view is attached and status bar expansion is 0
+ mController.onViewAttached();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ captureAltAuthInterceptor();
+ updateStatusBarExpansion(0, true);
+ reset(mView);
+ when(mView.getContext()).thenReturn(mResourceContext);
+ when(mResourceContext.getString(anyInt())).thenReturn("test string");
+
+ // WHEN status bar expansion is 0 but udfps bouncer is requested
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+
+ // THEN alpha is 255
+ verify(mView).setUnpausedAlpha(255);
+ }
+
+ @Test
+ public void testTransitionToFullShadeProgress() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureExpansionListeners();
+ updateStatusBarExpansion(1f, true);
+ reset(mView);
+
+ // WHEN we're transitioning to the full shade
+ float transitionProgress = .6f;
+ mController.setTransitionToFullShadeProgress(transitionProgress);
+
+ // THEN alpha is between 0 and 255
+ verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
+ }
+
+ @Test
+ public void testShowUdfpsBouncer_transitionToFullShadeProgress() {
+ // GIVEN view is attached and status bar expansion is 1f
+ mController.onViewAttached();
+ captureExpansionListeners();
+ captureKeyguardStateControllerCallback();
+ captureAltAuthInterceptor();
+ updateStatusBarExpansion(1f, true);
+ mAltAuthInterceptor.showAlternateAuthBouncer();
+ reset(mView);
+
+ // WHEN we're transitioning to the full shade
+ mController.setTransitionToFullShadeProgress(1.0f);
+
+ // THEN alpha is 255 (b/c udfps bouncer is requested)
+ verify(mView).setUnpausedAlpha(255);
+ }
+
private void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
@@ -282,9 +433,18 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
mStatusBarStateListener = mStateListenerCaptor.getValue();
}
- private void captureExpansionListener() {
- verify(mStatusBar).addExpansionChangedListener(mExpansionListenerCaptor.capture());
- mExpansionListener = mExpansionListenerCaptor.getValue();
+ private void captureExpansionListeners() {
+ verify(mStatusBar, times(2))
+ .addExpansionChangedListener(mExpansionListenerCaptor.capture());
+ // first (index=0) is from super class, UdfpsAnimationViewController.
+ // second (index=1) is from UdfpsKeyguardViewController
+ mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+ }
+
+ private void updateStatusBarExpansion(float expansion, boolean expanded) {
+ for (StatusBar.ExpansionChangedListener listener : mExpansionListeners) {
+ listener.onExpansionChanged(expansion, expanded);
+ }
}
private void captureAltAuthInterceptor() {
@@ -293,8 +453,11 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
}
- private void captureKeyguardUpdateMonitorCallback() {
- verify(mKeyguardUpdateMonitor).registerCallback(mUpdateMonitorCallbackCaptor.capture());
- mKeyguardUpdateMonitorCallback = mUpdateMonitorCallbackCaptor.getValue();
+
+
+ private void captureKeyguardStateControllerCallback() {
+ verify(mKeyguardStateController).addCallback(
+ mKeyguardStateControllerCallbackCaptor.capture());
+ mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 4e8b59c95681..b8d465a8c881 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.doze;
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
@@ -29,6 +30,7 @@ import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -42,13 +44,14 @@ import android.os.PowerManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
-import android.view.Display;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -82,6 +85,10 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
DozeParameters mDozeParameters;
+ @Mock
+ DockManager mDockManager;
+ @Mock
+ private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mFakeThreadFactory = new FakeThreadFactory(mFakeExecutor);
@@ -109,9 +116,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
mSensor = fakeSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.of(mSensor.getSensor()), mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
-
- mScreen.onScreenState(Display.STATE_ON);
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
+ mUnlockedScreenOffAnimationController);
}
@Test
@@ -123,18 +129,9 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
- public void testAod_usesLightSensor() {
- mScreen.onScreenState(Display.STATE_DOZE);
- waitForSensorManager();
-
- mSensor.sendSensorEvent(3);
-
- assertEquals(3, mServiceFake.screenBrightness);
- }
-
- @Test
public void testAod_usesDebugValue() throws Exception {
- mScreen.onScreenState(Display.STATE_DOZE);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
waitForSensorManager();
Intent intent = new Intent(DozeScreenBrightness.ACTION_AOD_BRIGHTNESS);
@@ -157,10 +154,53 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
+ public void doze_doesNotUseLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is NOT changed, it's set to the default brightness
+ assertNotSame(3, mServiceFake.screenBrightness);
+ assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void aod_usesLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
+ public void docked_usesLightSensor() {
+ // GIVEN the device is docked and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ mScreen.transitionTo(DOZE_AOD, DOZE_AOD_DOCKED);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is updated
+ assertEquals(3, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void testPausingAod_doesNotResetBrightness() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -175,7 +215,8 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
public void testPulsing_withoutLightSensor_setsAoDDimmingScrimTransparent() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
+ mUnlockedScreenOffAnimationController);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
reset(mDozeHost);
@@ -201,36 +242,22 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
- public void testOnScreenStateSetBeforeTransition_stillRegistersSensor() {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.onScreenState(Display.STATE_DOZE);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- waitForSensorManager();
-
- mSensor.sendSensorEvent(1);
-
- assertEquals(1, mServiceFake.screenBrightness);
- }
-
- @Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
Optional.empty() /* sensor */, mDozeHost, null /* handler */,
- mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters);
+ mAlwaysOnDisplayPolicy, mWakefulnessLifecycle, mDozeParameters, mDockManager,
+ mUnlockedScreenOffAnimationController);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
- mScreen.onScreenState(Display.STATE_DOZE);
- mScreen.onScreenState(Display.STATE_OFF);
}
@Test
public void testNoBrightnessDeliveredAfterFinish() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
mScreen.transitionTo(DOZE_AOD, FINISH);
waitForSensorManager();
@@ -243,7 +270,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
public void testNonPositiveBrightness_keepsPreviousBrightnessAndScrim() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(1);
@@ -257,7 +283,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
public void pausingAod_unblanksAfterSensor() {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
@@ -269,7 +294,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
reset(mDozeHost);
mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
verify(mDozeHost).setAodDimmingScrim(eq(0f));
@@ -279,7 +303,6 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
public void pausingAod_unblanksIfSensorWasAlwaysReady() throws Exception {
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
- mScreen.onScreenState(Display.STATE_DOZE);
waitForSensorManager();
mSensor.sendSensorEvent(2);
@@ -296,13 +319,14 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+ when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
// If we're dozing after a timeout, and playing the unlocked screen animation, we should
- // stay at dim brightness, because the screen dims just before timeout.
- assertEquals(mServiceFake.screenBrightness, DIM_BRIGHTNESS);
+ // stay at or below dim brightness, because the screen dims just before timeout.
+ assertTrue(mServiceFake.screenBrightness <= DIM_BRIGHTNESS);
}
@Test
@@ -310,6 +334,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(true);
+ when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(true);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
@@ -324,6 +349,7 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
when(mWakefulnessLifecycle.getLastSleepReason()).thenReturn(
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT);
when(mDozeParameters.shouldControlUnlockedScreenOff()).thenReturn(false);
+ when(mUnlockedScreenOffAnimationController.isScreenOffAnimationPlaying()).thenReturn(false);
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index 41d7fd64fe7a..3e19cc436dca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -21,6 +21,7 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -34,6 +35,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,6 +47,8 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.UdfpsController;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.wakelock.WakeLockFake;
import com.android.systemui.utils.os.FakeHandler;
@@ -56,6 +60,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import javax.inject.Provider;
+
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DozeScreenStateTest extends SysuiTestCase {
@@ -68,17 +74,32 @@ public class DozeScreenStateTest extends SysuiTestCase {
private DozeParameters mDozeParameters;
private WakeLockFake mWakeLock;
private DozeScreenState mScreen;
+ @Mock
+ private Provider<UdfpsController> mUdfpsControllerProvider;
+ @Mock
+ private AuthController mAuthController;
+ @Mock
+ private UdfpsController mUdfpsController;
+ @Mock
+ private DozeLog mDozeLog;
+ @Mock
+ private DozeScreenBrightness mDozeScreenBrightness;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mUdfpsControllerProvider.get()).thenReturn(mUdfpsController);
+ when(mAuthController.isUdfpsEnrolled(anyInt())).thenReturn(true);
+ when(mUdfpsController.isFingerDown()).thenReturn(false);
+
mServiceFake = new DozeServiceFake();
mHandlerFake = new FakeHandler(Looper.getMainLooper());
mWakeLock = new WakeLockFake();
mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
- mWakeLock);
+ mWakeLock, mAuthController, mUdfpsControllerProvider, mDozeLog,
+ mDozeScreenBrightness);
}
@Test
@@ -233,4 +254,56 @@ public class DozeScreenStateTest extends SysuiTestCase {
assertEquals(Display.STATE_OFF, mServiceFake.screenState);
}
+ @Test
+ public void testDelayEnterDozeScreenState_whenUdfpsFingerDown() {
+ // GIVEN AOD is initialized
+ when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+ mHandlerFake.setMode(QUEUEING);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mHandlerFake.dispatchQueuedMessages();
+
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+
+ // WHEN udfps is activated (fingerDown)
+ when(mUdfpsController.isFingerDown()).thenReturn(true);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state doesn't immediately change
+ assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+ // WHEN udfpsController finger is no longer down and the queued messages are run
+ when(mUdfpsController.isFingerDown()).thenReturn(false);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state will change
+ assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+ }
+
+ @Test
+ public void testDelayExitPulsingScreenState_whenUdfpsFingerDown() {
+ // GIVEN we're pulsing
+ when(mDozeParameters.shouldControlScreenOff()).thenReturn(true);
+ mHandlerFake.setMode(QUEUEING);
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD);
+ mScreen.transitionTo(DOZE_AOD, DOZE_REQUEST_PULSE);
+ mScreen.transitionTo(DOZE_REQUEST_PULSE, DOZE_PULSING);
+ mScreen.transitionTo(DOZE_PULSING, DOZE_PULSE_DONE);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // WHEN udfps is activated while are transitioning back to DOZE_AOD
+ mScreen.transitionTo(DOZE_PULSE_DONE, DOZE_AOD);
+ when(mUdfpsController.isFingerDown()).thenReturn(true);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state doesn't immediately change
+ assertEquals(Display.STATE_ON, mServiceFake.screenState);
+
+ // WHEN udfpsController finger is no longer down and the queued messages are run
+ when(mUdfpsController.isFingerDown()).thenReturn(false);
+ mHandlerFake.dispatchQueuedMessages();
+
+ // THEN the display screen state will change
+ assertEquals(Display.STATE_DOZE_SUSPEND, mServiceFake.screenState);
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 0c94f09c8572..5c4c27ccc4ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -88,7 +88,7 @@ public class DozeSensorsTest extends SysuiTestCase {
private FakeSettings mFakeSettings = new FakeSettings();
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
- private DozeSensors mDozeSensors;
+ private TestableDozeSensors mDozeSensors;
private TriggerSensor mSensorTap;
@Before
@@ -170,6 +170,94 @@ public class DozeSensorsTest extends SysuiTestCase {
assertTrue(mSensorTap.mRequested);
}
+ @Test
+ public void testDozeSensorSetListening() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled */ true,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN we want to listen for the trigger sensor
+ triggerSensor.setListening(true);
+
+ // THEN the sensor is registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testDozeSensorSettingDisabled() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ false,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN setListening is called
+ triggerSensor.setListening(true);
+
+ // THEN the sensor is not registered
+ assertFalse(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testDozeSensorIgnoreSetting() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor that's
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ false,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // GIVEN sensor is listening
+ triggerSensor.setListening(true);
+
+ // WHEN ignoreSetting is called
+ triggerSensor.ignoreSetting(true);
+
+ // THEN the sensor is registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
+ @Test
+ public void testUpdateListeningAfterAlreadyRegistered() {
+ // GIVEN doze sensors enabled
+ when(mAmbientDisplayConfiguration.enabled(anyInt())).thenReturn(true);
+
+ // GIVEN a trigger sensor
+ Sensor mockSensor = mock(Sensor.class);
+ TriggerSensor triggerSensor = mDozeSensors.createDozeSensor(
+ mockSensor,
+ /* settingEnabled*/ true,
+ /* requiresTouchScreen */ true);
+ when(mSensorManager.requestTriggerSensor(eq(triggerSensor), eq(mockSensor)))
+ .thenReturn(true);
+
+ // WHEN setListening is called AND updateListening is called
+ triggerSensor.setListening(true);
+ triggerSensor.updateListening();
+
+ // THEN the sensor is still registered
+ assertTrue(triggerSensor.mRegistered);
+ }
+
private class TestableDozeSensors extends DozeSensors {
TestableDozeSensors() {
@@ -187,5 +275,17 @@ public class DozeSensorsTest extends SysuiTestCase {
}
mSensors = new TriggerSensor[] {mTriggerSensor, mSensorTap};
}
+
+ public TriggerSensor createDozeSensor(Sensor sensor, boolean settingEnabled,
+ boolean requiresTouchScreen) {
+ return new TriggerSensor(/* sensor */ sensor,
+ /* setting name */ "test_setting",
+ /* settingDefault */ settingEnabled,
+ /* configured */ true,
+ /* pulseReason*/ 0,
+ /* reportsTouchCoordinate*/ false,
+ requiresTouchScreen,
+ mDozeLog);
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 10997fab081f..9577c7a2d6fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -45,6 +45,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent;
import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -83,6 +84,8 @@ public class DozeTriggersTest extends SysuiTestCase {
private AuthController mAuthController;
@Mock
private UiEventLogger mUiEventLogger;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
@@ -114,7 +117,7 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers = new DozeTriggers(mContext, mHost, config, dozeParameters,
asyncSensorManager, wakeLock, mDockManager, mProximitySensor,
mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(),
- mAuthController, mExecutor, mUiEventLogger);
+ mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController);
mTriggers.setDozeMachine(mMachine);
waitForSensorManager();
}
@@ -217,6 +220,32 @@ public class DozeTriggersTest extends SysuiTestCase {
}
@Test
+ public void testPickupGesture() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard isn't occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN wakeup
+ verify(mMachine).wakeUp();
+ }
+
+ @Test
+ public void testPickupGestureDroppedKeyguardOccluded() {
+ // GIVEN device is in doze (screen blank, but running doze sensors)
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+
+ // WHEN the pick up gesture is triggered and keyguard IS occluded
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null);
+
+ // THEN never wakeup
+ verify(mMachine, never()).wakeUp();
+ }
+
+ @Test
public void testOnSensor_Fingerprint() {
// GIVEN dozing state
when(mMachine.getState()).thenReturn(DOZE_AOD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index 578c2d976cce..509ef82ee426 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -51,6 +52,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -112,6 +114,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private Handler mHandler;
@Mock private UserContextProvider mUserContextProvider;
@Mock private StatusBar mStatusBar;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableLooper mTestableLooper;
@@ -156,7 +159,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar
+ mStatusBar,
+ mKeyguardUpdateMonitor
);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -422,4 +426,31 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
restartAction.onLongPress();
verifyLogPosted(GlobalActionsDialog.GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
}
+
+ @Test
+ public void testOnLockScreen_disableSmartLock() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ int user = KeyguardUpdateMonitor.getCurrentUser();
+ doReturn(4).when(mGlobalActionsDialogLite).getMaxShownPowerItems();
+ doReturn(true).when(mGlobalActionsDialogLite).shouldDisplayLockdown(any());
+ doReturn(true).when(mGlobalActionsDialogLite).shouldShowAction(any());
+ doReturn(false).when(mStatusBar).isKeyguardShowing();
+ String[] actions = {
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_EMERGENCY,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_LOCKDOWN,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_POWER,
+ GlobalActionsDialog.GLOBAL_ACTION_KEY_RESTART,
+ };
+ doReturn(actions).when(mGlobalActionsDialogLite).getDefaultActions();
+
+ // When entering power menu from lockscreen, with smart lock enabled
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ mGlobalActionsDialogLite.showOrHideDialog(true, true);
+
+ // Then smart lock will be disabled
+ verify(mLockPatternUtils).requireCredentialEntry(eq(user));
+
+ // hide dialog again
+ mGlobalActionsDialogLite.showOrHideDialog(true, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 2fa67cc0be60..338bb3037331 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -56,6 +56,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -126,6 +127,7 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
@Mock private PackageManager mPackageManager;
@Mock private SecureSettings mSecureSettings;
@Mock private StatusBar mStatusBar;
+ @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private TestableLooper mTestableLooper;
@@ -169,7 +171,8 @@ public class GlobalActionsDialogTest extends SysuiTestCase {
mSysUiState,
mHandler,
mPackageManager,
- mStatusBar
+ mStatusBar,
+ mKeyguardUpdateMonitor
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
new file mode 100644
index 000000000000..df112840ed87
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.AnimatableClockController;
+import com.android.keyguard.AnimatableClockView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.Utils;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.policy.BatteryController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class AnimatableClockControllerTest extends SysuiTestCase {
+ @Mock
+ private AnimatableClockView mClockView;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private BatteryController mBatteryController;
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private KeyguardBypassController mBypassController;
+ @Mock
+ private Resources mResources;
+
+ private MockitoSession mStaticMockSession;
+ private AnimatableClockController mAnimatableClockController;
+
+ // Capture listeners so that they can be used to send events
+ @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ private View.OnAttachStateChangeListener mAttachListener;
+
+ @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor;
+ private StatusBarStateController.StateListener mStatusBarStateCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(Utils.class)
+ .strictness(Strictness.LENIENT) // it's ok if mocked classes aren't used
+ .startMocking();
+ when(Utils.getColorAttrDefaultColor(anyObject(), anyInt())).thenReturn(0);
+
+ mAnimatableClockController = new AnimatableClockController(
+ mClockView,
+ mStatusBarStateController,
+ mBroadcastDispatcher,
+ mBatteryController,
+ mKeyguardUpdateMonitor,
+ mBypassController,
+ mResources
+ );
+ mAnimatableClockController.init();
+ captureAttachListener();
+ }
+
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
+ @Test
+ public void testOnAttachedUpdatesDozeStateToTrue() {
+ // GIVEN dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(true);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(1f);
+
+ // WHEN the clock view gets attached
+ mAttachListener.onViewAttachedToWindow(mClockView);
+
+ // THEN the clock controller updated its dozing state to true
+ assertTrue(mAnimatableClockController.isDozing());
+ }
+
+ @Test
+ public void testOnAttachedUpdatesDozeStateToFalse() {
+ // GIVEN not dozing
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarStateController.getDozeAmount()).thenReturn(0f);
+
+ // WHEN the clock view gets attached
+ mAttachListener.onViewAttachedToWindow(mClockView);
+
+ // THEN the clock controller updated its dozing state to false
+ assertFalse(mAnimatableClockController.isDozing());
+ }
+
+ private void captureAttachListener() {
+ verify(mClockView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachListener = mAttachCaptor.getValue();
+ }
+}
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 ad0878031679..31d70f5c811f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -67,7 +67,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@TestableLooper.RunWithLooper
@SmallTest
public class KeyguardViewMediatorTest extends SysuiTestCase {
private KeyguardViewMediator mViewMediator;
@@ -126,7 +126,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController,
() -> mNotificationShadeDepthController);
mViewMediator.start();
- mViewMediator.onSystemReady();
}
@Test
@@ -165,8 +164,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
}
@Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
public void restoreBouncerWhenSimLockedAndKeyguardIsGoingAway() {
// When showing and provisioned
+ mViewMediator.onSystemReady();
when(mUpdateMonitor.isDeviceProvisioned()).thenReturn(true);
mViewMediator.setShowingLocked(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
new file mode 100644
index 000000000000..9356c547db70
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PointF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.os.Vibrator;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardViewController;
+import com.android.keyguard.LockIconView;
+import com.android.keyguard.LockIconViewController;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import com.airbnb.lottie.LottieAnimationView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class LockIconViewControllerTest extends SysuiTestCase {
+ private @Mock LockIconView mLockIconView;
+ private @Mock Context mContext;
+ private @Mock Resources mResources;
+ private @Mock DisplayMetrics mDisplayMetrics;
+ private @Mock StatusBarStateController mStatusBarStateController;
+ private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private @Mock KeyguardViewController mKeyguardViewController;
+ private @Mock KeyguardStateController mKeyguardStateController;
+ private @Mock FalsingManager mFalsingManager;
+ private @Mock AuthController mAuthController;
+ private @Mock DumpManager mDumpManager;
+ private @Mock AccessibilityManager mAccessibilityManager;
+ private @Mock ConfigurationController mConfigurationController;
+ private @Mock DelayableExecutor mDelayableExecutor;
+ private @Mock Vibrator mVibrator;
+ private @Mock AuthRippleController mAuthRippleController;
+ private @Mock LottieAnimationView mAodFp;
+
+ private LockIconViewController mLockIconViewController;
+
+ // Capture listeners so that they can be used to send events
+ @Captor private ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
+ private View.OnAttachStateChangeListener mAttachListener;
+
+ @Captor private ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor;
+ private AuthController.Callback mAuthControllerCallback;
+
+ @Captor private ArgumentCaptor<PointF> mPointCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ when(mLockIconView.getResources()).thenReturn(mResources);
+ when(mLockIconView.getContext()).thenReturn(mContext);
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+ when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp);
+
+ mLockIconViewController = new LockIconViewController(
+ mLockIconView,
+ mStatusBarStateController,
+ mKeyguardUpdateMonitor,
+ mKeyguardViewController,
+ mKeyguardStateController,
+ mFalsingManager,
+ mAuthController,
+ mDumpManager,
+ mAccessibilityManager,
+ mConfigurationController,
+ mDelayableExecutor,
+ mVibrator,
+ mAuthRippleController
+ );
+ }
+
+ @Test
+ public void testUpdateFingerprintLocationOnInit() {
+ // GIVEN fp sensor location is available pre-attached
+ Pair<Integer, PointF> udfps = setupUdfps();
+
+ // WHEN lock icon view controller is initialized and attached
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
+ assertEquals(udfps.second, mPointCaptor.getValue());
+ }
+
+ @Test
+ public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() {
+ // GIVEN fp sensor location is not available pre-init
+ when(mAuthController.getFingerprintSensorLocation()).thenReturn(null);
+ when(mAuthController.getUdfpsProps()).thenReturn(null);
+ mLockIconViewController.init();
+ captureAttachListener();
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // GIVEN fp sensor location is available post-atttached
+ captureAuthControllerCallback();
+ Pair<Integer, PointF> udfps = setupUdfps();
+
+ // WHEN all authenticators are registered
+ mAuthControllerCallback.onAllAuthenticatorsRegistered();
+
+ // THEN lock icon view location is updated with the same coordinates as fpProps
+ verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(udfps.first));
+ assertEquals(udfps.second, mPointCaptor.getValue());
+ }
+
+ @Test
+ public void testLockIconViewBackgroundEnabledWhenUdfpsIsAvailable() {
+ // GIVEN Udpfs sensor location is available
+ setupUdfps();
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon view background should be enabled
+ verify(mLockIconView).setUseBackground(true);
+ }
+
+ @Test
+ public void testLockIconViewBackgroundDisabledWhenUdfpsIsUnavailable() {
+ // GIVEN Udfps sensor location is not available
+ when(mAuthController.getUdfpsSensorLocation()).thenReturn(null);
+
+ mLockIconViewController.init();
+ captureAttachListener();
+
+ // WHEN the view is attached
+ mAttachListener.onViewAttachedToWindow(mLockIconView);
+
+ // THEN the lock icon view background should be disabled
+ verify(mLockIconView).setUseBackground(false);
+ }
+
+ private Pair<Integer, PointF> setupUdfps() {
+ final PointF udfpsLocation = new PointF(50, 75);
+ final int radius = 33;
+ final FingerprintSensorPropertiesInternal fpProps =
+ new FingerprintSensorPropertiesInternal(
+ /* sensorId */ 0,
+ /* strength */ 0,
+ /* max enrollments per user */ 5,
+ /* component info */ new ArrayList<>(),
+ /* sensorType */ 3,
+ /* resetLockoutRequiresHwToken */ false,
+ (int) udfpsLocation.x, (int) udfpsLocation.y, radius);
+ when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation);
+ when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps));
+
+ return new Pair(radius, udfpsLocation);
+ }
+
+ private void captureAuthControllerCallback() {
+ verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture());
+ mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue();
+ }
+
+ private void captureAttachListener() {
+ verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture());
+ mAttachListener = mAttachCaptor.getValue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index b129fdd0d844..42629f545559 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -37,6 +37,7 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.dialog.MediaOutputDialogFactory
import com.android.systemui.plugins.ActivityStarter
@@ -101,7 +102,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var seamless: ViewGroup
private lateinit var seamlessIcon: ImageView
private lateinit var seamlessText: TextView
- private lateinit var seamlessFallback: ImageView
private lateinit var seekBar: SeekBar
private lateinit var elapsedTimeView: TextView
private lateinit var totalTimeView: TextView
@@ -154,8 +154,6 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(holder.seamlessIcon).thenReturn(seamlessIcon)
seamlessText = TextView(context)
whenever(holder.seamlessText).thenReturn(seamlessText)
- seamlessFallback = ImageView(context)
- whenever(holder.seamlessFallback).thenReturn(seamlessFallback)
seekBar = SeekBar(context)
whenever(holder.seekBar).thenReturn(seekBar)
elapsedTimeView = TextView(context)
@@ -239,21 +237,19 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Test
fun bindDisabledDevice() {
seamless.id = 1
- seamlessFallback.id = 2
+ val fallbackString = context.getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, disabledDevice, true, null)
player.bindPlayer(state, PACKAGE)
- verify(expandedSet).setVisibility(seamless.id, View.GONE)
- verify(expandedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
- verify(collapsedSet).setVisibility(seamless.id, View.GONE)
- verify(collapsedSet).setVisibility(seamlessFallback.id, View.VISIBLE)
+ assertThat(seamless.isEnabled()).isFalse()
+ assertThat(seamlessText.getText()).isEqualTo(fallbackString)
+ assertThat(seamless.contentDescription).isEqualTo(fallbackString)
}
@Test
fun bindNullDevice() {
- val fallbackString = context.getResources().getString(
- com.android.internal.R.string.ext_media_seamless_action)
+ val fallbackString = context.getResources().getString(R.string.media_seamless_other_device)
player.attachPlayer(holder)
val state = MediaData(USER_ID, true, BG_COLOR, APP, null, ARTIST, TITLE, null, emptyList(),
emptyList(), PACKAGE, session.getSessionToken(), null, null, true, null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
index d8791867cb45..28aed2085528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataFilterTest.kt
@@ -86,7 +86,7 @@ class MediaDataFilterTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- mediaDataFilter = MediaDataFilter(broadcastDispatcher, mediaResumeListener,
+ mediaDataFilter = MediaDataFilter(context, broadcastDispatcher, mediaResumeListener,
lockscreenUserManager, executor, clock)
mediaDataFilter.mediaDataManager = mediaDataManager
mediaDataFilter.addListener(listener)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
index ba6dfd3c453a..47c5545ab587 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt
@@ -4,6 +4,7 @@ import android.app.Notification.MediaStyle
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.app.smartspace.SmartspaceTarget
+import android.content.Intent
import android.graphics.Bitmap
import android.media.MediaDescription
import android.media.MediaMetadata
@@ -51,6 +52,7 @@ private const val APP_NAME = "SystemUI"
private const val SESSION_ARTIST = "artist"
private const val SESSION_TITLE = "title"
private const val USER_ID = 0
+private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
private fun <T> anyObject(): T {
return Mockito.anyObject<T>()
@@ -83,6 +85,7 @@ class MediaDataManagerTest : SysuiTestCase() {
lateinit var smartspaceMediaDataProvider: SmartspaceMediaDataProvider
@Mock lateinit var mediaSmartspaceTarget: SmartspaceTarget
@Mock private lateinit var mediaRecommendationItem: SmartspaceAction
+ @Mock private lateinit var mediaSmartspaceBaseAction: SmartspaceAction
lateinit var mediaDataManager: MediaDataManager
lateinit var mediaNotification: StatusBarNotification
@Captor lateinit var mediaDataCaptor: ArgumentCaptor<MediaData>
@@ -146,8 +149,12 @@ class MediaDataManagerTest : SysuiTestCase() {
// treat mediaSessionBasedFilter as a listener for testing.
listener = mediaSessionBasedFilter
- val recommendationExtras = Bundle()
- recommendationExtras.putString("package_name", PACKAGE_NAME)
+ val recommendationExtras = Bundle().apply {
+ putString("package_name", PACKAGE_NAME)
+ putParcelable("dismiss_intent", DISMISS_INTENT)
+ }
+ whenever(mediaSmartspaceBaseAction.extras).thenReturn(recommendationExtras)
+ whenever(mediaSmartspaceTarget.baseAction).thenReturn(mediaSmartspaceBaseAction)
whenever(mediaRecommendationItem.extras).thenReturn(recommendationExtras)
whenever(mediaSmartspaceTarget.smartspaceTargetId).thenReturn(KEY_MEDIA_SMARTSPACE)
whenever(mediaSmartspaceTarget.featureType).thenReturn(SmartspaceTarget.FEATURE_MEDIA)
@@ -163,7 +170,7 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
- fun testSetTimedOut_deactivatesMedia() {
+ fun testSetTimedOut_active_deactivatesMedia() {
val data = MediaData(userId = USER_ID, initialized = true, backgroundColor = 0, app = null,
appIcon = null, artist = null, song = null, artwork = null, actions = emptyList(),
actionsToShowInCompact = emptyList(), packageName = "INVALID", token = null,
@@ -176,6 +183,25 @@ class MediaDataManagerTest : SysuiTestCase() {
}
@Test
+ fun testSetTimedOut_resume_dismissesMedia() {
+ // WHEN resume controls are present, and time out
+ val desc = MediaDescription.Builder().run {
+ setTitle(SESSION_TITLE)
+ build()
+ }
+ mediaDataManager.addResumptionControls(USER_ID, desc, Runnable {}, session.sessionToken,
+ APP_NAME, pendingIntent, PACKAGE_NAME)
+ backgroundExecutor.runAllReady()
+ foregroundExecutor.runAllReady()
+ mediaDataManager.setTimedOut(PACKAGE_NAME, timedOut = true)
+
+ // THEN it is removed and listeners are informed
+ foregroundExecutor.advanceClockToLast()
+ foregroundExecutor.runAllReady()
+ verify(listener).onMediaDataRemoved(PACKAGE_NAME)
+ }
+
+ @Test
fun testLoadsMetadataOnBackground() {
mediaDataManager.onNotificationAdded(KEY, mediaNotification)
assertThat(backgroundExecutor.numPending()).isEqualTo(1)
@@ -361,7 +387,8 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(SmartspaceMediaData(KEY_MEDIA_SMARTSPACE, true /* isActive */, true /*isValid */,
- PACKAGE_NAME, null, listOf(mediaRecommendationItem), 0)),
+ PACKAGE_NAME, mediaSmartspaceBaseAction, listOf(mediaRecommendationItem),
+ DISMISS_INTENT, 0)),
eq(false))
}
@@ -372,7 +399,8 @@ class MediaDataManagerTest : SysuiTestCase() {
verify(listener).onSmartspaceMediaDataLoaded(
eq(KEY_MEDIA_SMARTSPACE),
eq(EMPTY_SMARTSPACE_MEDIA_DATA
- .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true, isValid = false)),
+ .copy(targetId = KEY_MEDIA_SMARTSPACE, isActive = true,
+ isValid = false, dismissIntent = DISMISS_INTENT)),
eq(false))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
index 150f4545bd43..359746bca0ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaResumeListenerTest.kt
@@ -91,10 +91,12 @@ class MediaResumeListenerTest : SysuiTestCase() {
@Captor lateinit var callbackCaptor: ArgumentCaptor<ResumeMediaBrowser.Callback>
@Captor lateinit var actionCaptor: ArgumentCaptor<Runnable>
+ @Captor lateinit var componentCaptor: ArgumentCaptor<String>
private lateinit var executor: FakeExecutor
private lateinit var data: MediaData
private lateinit var resumeListener: MediaResumeListener
+ private val clock = FakeSystemClock()
private var originalQsSetting = Settings.Global.getInt(context.contentResolver,
Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1)
@@ -122,9 +124,9 @@ class MediaResumeListenerTest : SysuiTestCase() {
whenever(mockContext.packageManager).thenReturn(context.packageManager)
whenever(mockContext.contentResolver).thenReturn(context.contentResolver)
- executor = FakeExecutor(FakeSystemClock())
+ executor = FakeExecutor(clock)
resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
- tunerService, resumeBrowserFactory, dumpManager)
+ tunerService, resumeBrowserFactory, dumpManager, clock)
resumeListener.setManager(mediaDataManager)
mediaDataManager.addListener(resumeListener)
@@ -163,7 +165,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
// When listener is created, we do NOT register a user change listener
val listener = MediaResumeListener(context, broadcastDispatcher, executor, tunerService,
- resumeBrowserFactory, dumpManager)
+ resumeBrowserFactory, dumpManager, clock)
listener.setManager(mediaDataManager)
verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
any(), any(), any())
@@ -328,4 +330,109 @@ class MediaResumeListenerTest : SysuiTestCase() {
// Then we call restart
verify(resumeBrowser).restart()
}
+
+ @Test
+ fun testOnUserUnlock_missingTime_saves() {
+ val currentTime = clock.currentTimeMillis()
+
+ // When resume components without a last played time are loaded
+ testOnUserUnlock_loadsTracks()
+
+ // Then we save an update with the current time
+ verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+ componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+ ?.dropLastWhile { it.isEmpty() }.forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
+ verify(sharedPrefsEditor, times(1)).apply()
+ }
+
+ @Test
+ fun testLoadComponents_recentlyPlayed_adds() {
+ // Set up browser to return successfully
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Set up shared preferences to have a component with a recent lastplayed time
+ val lastPlayed = clock.currentTimeMillis()
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When we load a component that was played recently
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+ // We add its resume controls
+ verify(resumeBrowser, times(1)).findRecentMedia()
+ verify(mediaDataManager, times(1)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
+ }
+
+ @Test
+ fun testLoadComponents_old_ignores() {
+ // Set up shared preferences to have a component with an old lastplayed time
+ val lastPlayed = clock.currentTimeMillis() - RESUME_MEDIA_TIMEOUT - 100
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When we load a component that is not recent
+ val intent = Intent(Intent.ACTION_USER_UNLOCKED)
+ resumeListener.userChangeReceiver.onReceive(mockContext, intent)
+
+ // We do not try to add resume controls
+ verify(resumeBrowser, times(0)).findRecentMedia()
+ verify(mediaDataManager, times(0)).addResumptionControls(anyInt(),
+ any(), any(), any(), any(), any(), any())
+ }
+
+ @Test
+ fun testOnLoad_hasService_updatesLastPlayed() {
+ // Set up browser to return successfully
+ val description = MediaDescription.Builder().setTitle(TITLE).build()
+ val component = ComponentName(PACKAGE_NAME, CLASS_NAME)
+ whenever(resumeBrowser.token).thenReturn(token)
+ whenever(resumeBrowser.appIntent).thenReturn(pendingIntent)
+ whenever(resumeBrowser.findRecentMedia()).thenAnswer {
+ callbackCaptor.value.addTrack(description, component, resumeBrowser)
+ }
+
+ // Set up shared preferences to have a component with a lastplayed time
+ val currentTime = clock.currentTimeMillis()
+ val lastPlayed = currentTime - 1000
+ val componentsString = "$PACKAGE_NAME/$CLASS_NAME/$lastPlayed:"
+ whenever(sharedPrefs.getString(any(), any())).thenReturn(componentsString)
+ val resumeListener = MediaResumeListener(mockContext, broadcastDispatcher, executor,
+ tunerService, resumeBrowserFactory, dumpManager, clock)
+ resumeListener.setManager(mediaDataManager)
+ mediaDataManager.addListener(resumeListener)
+
+ // When media data is loaded that has not been checked yet, and does have a MBS
+ val dataCopy = data.copy(resumeAction = null, hasCheckedForResume = false)
+ resumeListener.onMediaDataLoaded(KEY, null, dataCopy)
+
+ // Then we store the new lastPlayed time
+ verify(sharedPrefsEditor).putString(any(), (capture(componentCaptor)))
+ componentCaptor.value.split(ResumeMediaBrowser.DELIMITER.toRegex())
+ ?.dropLastWhile { it.isEmpty() }.forEach {
+ val result = it.split("/")
+ assertThat(result.size).isEqualTo(3)
+ assertThat(result[2].toLong()).isEqualTo(currentTime)
+ }
+ verify(sharedPrefsEditor, times(1)).apply()
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
index 0a573cd6020c..de2235dd1c42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTimeoutListenerTest.kt
@@ -71,6 +71,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
private lateinit var playbackBuilder: PlaybackState.Builder
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
+ private lateinit var resumeData: MediaData
private lateinit var mediaTimeoutListener: MediaTimeoutListener
@Before
@@ -97,6 +98,10 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
emptyList(), emptyList(), PACKAGE, session.sessionToken, clickIntent = null,
device = null, active = true, resumeAction = null)
+
+ resumeData = MediaData(USER_ID, true, 0, PACKAGE, null, null, SESSION_TITLE, null,
+ emptyList(), emptyList(), PACKAGE, null, clickIntent = null,
+ device = null, active = false, resumeAction = null, resumption = true)
}
@Test
@@ -120,6 +125,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
assertThat(executor.numPending()).isEqualTo(1)
verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@Test
@@ -188,6 +194,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
assertThat(executor.numPending()).isEqualTo(1)
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
}
@Test
@@ -229,7 +236,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
@Test
- fun testOnSessionDestroyed_clearsTimeout() {
+ fun testOnSessionDestroyed_active_clearsTimeout() {
// GIVEN media that is paused
val mediaPaused = mediaData.copy(isPlaying = false)
mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaPaused)
@@ -247,7 +254,7 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
@Test
fun testSessionDestroyed_thenRestarts_resetsTimeout() {
// Assuming we have previously destroyed the session
- testOnSessionDestroyed_clearsTimeout()
+ testOnSessionDestroyed_active_clearsTimeout()
// WHEN we get an update with media playing
val playingState = mock(android.media.session.PlaybackState::class.java)
@@ -264,4 +271,91 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
}
verify(timeoutCallback).invoke(eq(KEY), eq(false))
}
+
+ @Test
+ fun testOnSessionDestroyed_resume_continuesTimeout() {
+ // GIVEN resume media with session info
+ val resumeWithSession = resumeData.copy(token = session.sessionToken)
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeWithSession)
+ verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // WHEN the session is destroyed
+ mediaCallbackCaptor.value.onSessionDestroyed()
+
+ // THEN the controller is unregistered, but the timeout is still scheduled
+ verify(mediaController).unregisterCallback(anyObject())
+ assertThat(executor.numPending()).isEqualTo(1)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_activeToResume_registersTimeout() {
+ // WHEN a regular media is loaded
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+
+ // AND it turns into a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+ // THEN we register a timeout
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_pausedToResume_updatesTimeout() {
+ // WHEN regular media is paused
+ val pausedState = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+ .build()
+ `when`(mediaController.playbackState).thenReturn(pausedState)
+ mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
+ assertThat(executor.numPending()).isEqualTo(1)
+
+ // AND it turns into a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, KEY, resumeData)
+
+ // THEN we update the timeout length
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_resumption_registersTimeout() {
+ // WHEN a resume media is loaded
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+ // THEN we register a timeout
+ assertThat(executor.numPending()).isEqualTo(1)
+ verify(timeoutCallback, never()).invoke(anyString(), anyBoolean())
+ assertThat(executor.advanceClockToNext()).isEqualTo(RESUME_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataLoaded_resumeToActive_updatesTimeout() {
+ // WHEN we have a resume control
+ mediaTimeoutListener.onMediaDataLoaded(PACKAGE, null, resumeData)
+
+ // AND that media is resumed
+ val playingState = PlaybackState.Builder()
+ .setState(PlaybackState.STATE_PAUSED, 0L, 0f)
+ .build()
+ `when`(mediaController.playbackState).thenReturn(playingState)
+ mediaTimeoutListener.onMediaDataLoaded(KEY, PACKAGE, mediaData)
+
+ // THEN the timeout length is changed to a regular media control
+ assertThat(executor.advanceClockToNext()).isEqualTo(PAUSED_MEDIA_TIMEOUT)
+ }
+
+ @Test
+ fun testOnMediaDataRemoved_resume_timeoutCancelled() {
+ // WHEN we have a resume control
+ testOnMediaDataLoaded_resumption_registersTimeout()
+ // AND the media is removed
+ mediaTimeoutListener.onMediaDataRemoved(PACKAGE)
+
+ // THEN the timeout runnable is cancelled
+ assertThat(executor.numPending()).isEqualTo(0)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
index 03248f7e3a70..511848d8a3af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt
@@ -22,11 +22,13 @@ import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.UserInfo
+import android.os.Process.SYSTEM_UID
import android.os.UserHandle
import android.permission.PermGroupUsage
import android.permission.PermissionManager
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.plugins.ActivityStarter
@@ -53,6 +55,7 @@ import org.mockito.Mockito.`when`
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -96,6 +99,8 @@ class PrivacyDialogControllerTest : SysuiTestCase() {
private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
@Captor
private lateinit var intentCaptor: ArgumentCaptor<Intent>
+ @Mock
+ private lateinit var uiEventLogger: UiEventLogger
private val backgroundExecutor = FakeExecutor(FakeSystemClock())
private val uiExecutor = FakeExecutor(FakeSystemClock())
@@ -136,6 +141,7 @@ class PrivacyDialogControllerTest : SysuiTestCase() {
privacyLogger,
keyguardStateController,
appOpsController,
+ uiEventLogger,
dialogProvider
)
}
@@ -550,6 +556,49 @@ class PrivacyDialogControllerTest : SysuiTestCase() {
verify(dialog, never()).dismiss()
}
+ @Test
+ fun testCallOnSecondaryUser() {
+ // Calls happen in
+ val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ `when`(userTracker.userProfiles).thenReturn(listOf(
+ UserInfo(ENT_USER_ID, "", 0)
+ ))
+
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ verify(dialog).show()
+ }
+
+ @Test
+ fun testStartActivityLogs() {
+ val usage = createMockPermGroupUsage()
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ dialogProvider.starter?.invoke(TEST_PACKAGE_NAME, USER_ID)
+ verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ USER_ID, TEST_PACKAGE_NAME)
+ }
+
+ @Test
+ fun testDismissedDialogLogs() {
+ val usage = createMockPermGroupUsage()
+ `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
+ controller.showDialog(context)
+ exhaustExecutors()
+
+ verify(dialog).addOnDismissListener(capture(dialogDismissedCaptor))
+
+ dialogDismissedCaptor.value.onDialogDismissed()
+
+ controller.dismissDialog()
+
+ verify(uiEventLogger, times(1)).log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+ }
+
private fun exhaustExecutors() {
FakeExecutor.exhaustExecutors(backgroundExecutor, uiExecutor)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
index de7abf866f6a..922c6b648aab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
@@ -14,13 +14,19 @@
package com.android.systemui.qs;
-import static com.android.systemui.statusbar.phone.AutoTileManager.INVERSION;
import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-import static com.android.systemui.statusbar.phone.AutoTileManager.WORK;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.testing.AndroidTestingRunner;
@@ -28,13 +34,24 @@ import android.testing.TestableLooper.RunWithLooper;
import androidx.test.filters.SmallTest;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.Executor;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -43,42 +60,38 @@ public class AutoAddTrackerTest extends SysuiTestCase {
private static final int USER = 0;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private QSHost mQSHost;
+ @Mock
+ private DumpManager mDumpManager;
+ @Captor
+ private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
+ @Captor
+ private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
+
+ private Executor mBackgroundExecutor = Runnable::run; // Direct executor
private AutoAddTracker mAutoTracker;
+ private SecureSettings mSecureSettings;
@Before
public void setUp() {
- Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, "");
- }
+ MockitoAnnotations.initMocks(this);
- @Test
- public void testMigration() {
- Prefs.putBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true);
- Prefs.putBoolean(mContext, Key.QS_WORK_ADDED, true);
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
+ mSecureSettings = new FakeSettings();
- assertTrue(mAutoTracker.isAdded(SAVER));
- assertTrue(mAutoTracker.isAdded(WORK));
- assertFalse(mAutoTracker.isAdded(INVERSION));
+ mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
- // These keys have been removed; retrieving their values should always return the default.
- assertTrue(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, true ));
- assertFalse(Prefs.getBoolean(mContext, Key.QS_DATA_SAVER_ADDED, false));
- assertTrue(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, true));
- assertFalse(Prefs.getBoolean(mContext, Key.QS_WORK_ADDED, false));
-
- mAutoTracker.destroy();
+ mAutoTracker = createAutoAddTracker(USER);
+ mAutoTracker.initialize();
}
@Test
public void testChangeFromBackup() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
- Secure.putString(mContext.getContentResolver(), Secure.QS_AUTO_ADDED_TILES, SAVER);
- mAutoTracker.mObserver.onChange(false);
+ mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
assertTrue(mAutoTracker.isAdded(SAVER));
@@ -87,9 +100,6 @@ public class AutoAddTrackerTest extends SysuiTestCase {
@Test
public void testSetAdded() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
@@ -100,14 +110,12 @@ public class AutoAddTrackerTest extends SysuiTestCase {
@Test
public void testPersist() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
-
assertFalse(mAutoTracker.isAdded(SAVER));
mAutoTracker.setTileAdded(SAVER);
mAutoTracker.destroy();
- mAutoTracker = new AutoAddTracker(mContext, USER);
+ mAutoTracker = createAutoAddTracker(USER);
+ mAutoTracker.initialize();
assertTrue(mAutoTracker.isAdded(SAVER));
@@ -116,22 +124,158 @@ public class AutoAddTrackerTest extends SysuiTestCase {
@Test
public void testIndependentUsers() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
mAutoTracker.setTileAdded(SAVER);
- mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+ mAutoTracker = createAutoAddTracker(USER + 1);
+ mAutoTracker.initialize();
assertFalse(mAutoTracker.isAdded(SAVER));
}
@Test
public void testChangeUser() {
- mAutoTracker = new AutoAddTracker(mContext, USER);
- mAutoTracker.initialize();
mAutoTracker.setTileAdded(SAVER);
- mAutoTracker = new AutoAddTracker(mContext, USER + 1);
+ mAutoTracker = createAutoAddTracker(USER + 1);
mAutoTracker.changeUser(UserHandle.of(USER));
assertTrue(mAutoTracker.isAdded(SAVER));
}
+
+ @Test
+ public void testBroadcastReceiverRegistered() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)));
+
+ assertTrue(
+ mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
+ }
+
+ @Test
+ public void testBroadcastReceiverChangesWithUser() {
+ mAutoTracker.changeUser(UserHandle.of(USER + 1));
+
+ InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
+ inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
+ inOrder.verify(mBroadcastDispatcher)
+ .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
+ verify(mBroadcastDispatcher).registerReceiver(
+ mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,work,internet,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, don't remove any current tiles
+ verify(mQSHost, never()).removeTiles(any());
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent =
+ makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+ assertEquals(restoredAutoAddTiles,
+ mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
+ }
+
+ @Test
+ public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
+ verify(mBroadcastDispatcher)
+ .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any());
+
+ // These tiles were present in the original device
+ String restoredTiles = "saver,internet,cast";
+ Intent restoreTilesIntent =
+ makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
+
+ // And these tiles have been auto-added in the original device
+ // (no auto-added before restore)
+ String restoredAutoAddTiles = "work";
+ Intent restoreAutoAddTilesIntent =
+ makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
+ mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
+
+ // Then, remove work tile
+ verify(mQSHost).removeTiles(List.of("work"));
+
+ String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
+ assertEquals(2, setting.split(",").length);
+ assertTrue(setting.contains("work"));
+ assertTrue(setting.contains("inversion"));
+ }
+
+
+ private Intent makeRestoreIntent(
+ String settingName, String previousValue, String restoredValue) {
+ Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
+ intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
+ intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
+ intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
+ return intent;
+ }
+
+ private AutoAddTracker createAutoAddTracker(int user) {
+ // Null handler wil dispatch sync.
+ return new AutoAddTracker(
+ mSecureSettings,
+ mBroadcastDispatcher,
+ mQSHost,
+ mDumpManager,
+ null,
+ mBackgroundExecutor,
+ user
+ );
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index 65e5f9703d84..faef87069084 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -27,6 +27,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -98,11 +99,11 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
Resources mResources;
@Mock
Configuration mConfiguration;
+ @Mock
+ Runnable mHorizontalLayoutListener;
private QSPanelControllerBase<QSPanel> mController;
-
-
/** Implementation needed to ensure we have a reflectively-available class name. */
private class TestableQSPanelControllerBase extends QSPanelControllerBase<QSPanel> {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
@@ -242,18 +243,44 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
when(mMediaHost.getVisible()).thenReturn(true);
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(false);
+ when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
when(mFeatureFlags.isTwoColumnNotificationShadeEnabled()).thenReturn(true);
when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true);
+ when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mFeatureFlags);
+ mController.init();
+
+ assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ }
+
+ @Test
+ public void testChangeConfiguration_shouldUseHorizontalLayout() {
+ when(mMediaHost.getVisible()).thenReturn(true);
+ mController.setUsingHorizontalLayoutChangeListener(mHorizontalLayoutListener);
+
+ // When device is rotated to landscape
+ mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+
+ // Then the layout changes
+ assertThat(mController.shouldUseHorizontalLayout()).isTrue();
+ verify(mHorizontalLayoutListener).run(); // not invoked
+
+ // When it is rotated back to portrait
+ mConfiguration.orientation = Configuration.ORIENTATION_PORTRAIT;
+ mController.mOnConfigurationChangedListener.onConfigurationChange(mConfiguration);
+ // Then the layout changes back
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
+ verify(mHorizontalLayoutListener, times(2)).run();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
index bf6c981bf05c..35ebacb85203 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.java
@@ -23,6 +23,8 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
@@ -98,6 +100,10 @@ public class QSPanelControllerTest extends SysuiTestCase {
FalsingManagerFake mFalsingManager = new FalsingManagerFake();
@Mock
FeatureFlags mFeatureFlags;
+ @Mock
+ Resources mResources;
+ @Mock
+ Configuration mConfiguration;
private QSPanelController mController;
@@ -109,6 +115,8 @@ public class QSPanelControllerTest extends SysuiTestCase {
when(mQSPanel.getDumpableTag()).thenReturn("QSPanel");
when(mQSPanel.getOrCreateTileLayout()).thenReturn(mPagedTileLayout);
when(mQSPanel.getTileLayout()).thenReturn(mPagedTileLayout);
+ when(mQSPanel.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
when(mQSTileHost.getTiles()).thenReturn(Collections.singleton(mQSTile));
when(mQSTileHost.createTileView(any(), eq(mQSTile), anyBoolean())).thenReturn(mQSTileView);
when(mToggleSliderViewControllerFactory.create(any(), any()))
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 4cbad5f15c5f..0b67c9c51079 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -18,14 +18,11 @@ package com.android.systemui.qs;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
-import static junit.framework.TestCase.assertFalse;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -68,12 +65,12 @@ import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -121,7 +118,6 @@ public class QSTileHostTest extends SysuiTestCase {
private UiEventLogger mUiEventLogger;
@Mock
private UserTracker mUserTracker;
- @Mock
private SecureSettings mSecureSettings;
@Mock
private CustomTileStatePersister mCustomTileStatePersister;
@@ -137,14 +133,16 @@ public class QSTileHostTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mLooper = TestableLooper.get(this);
mHandler = new Handler(mLooper.getLooper());
+
+ mSecureSettings = new FakeSettings();
+ mSecureSettings.putStringForUser(
+ QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
mBroadcastDispatcher, mStatusBar, mQSLogger, mUiEventLogger, mUserTracker,
mSecureSettings, mCustomTileStatePersister, mFeatureFlags);
setUpTileFactory();
when(mFeatureFlags.isProviderModelSettingEnabled()).thenReturn(false);
- when(mSecureSettings.getStringForUser(eq(QSTileHost.TILES_SETTING), anyInt()))
- .thenReturn("");
}
private void setUpTileFactory() {
@@ -416,6 +414,16 @@ public class QSTileHostTest extends SysuiTestCase {
.removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
}
+ @Test
+ public void testRemoveTiles() {
+ List<String> tiles = List.of("spec1", "spec2", "spec3");
+ mQSTileHost.saveTilesToSettings(tiles);
+
+ mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+
+ assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
@@ -442,14 +450,11 @@ public class QSTileHostTest extends SysuiTestCase {
@Override
void saveTilesToSettings(List<String> tileSpecs) {
super.saveTilesToSettings(tileSpecs);
-
- ArgumentCaptor<String> specs = ArgumentCaptor.forClass(String.class);
- verify(mSecureSettings, atLeastOnce()).putStringForUser(eq(QSTileHost.TILES_SETTING),
- specs.capture(), isNull(), eq(false), anyInt(), eq(true));
-
// After tiles are changed, make sure to call onTuningChanged with the new setting if it
// changed
- onTuningChanged(TILES_SETTING, specs.getValue());
+ String specs = mSecureSettings.getStringForUser(
+ QSTileHost.TILES_SETTING, mUserTracker.getUserId());
+ onTuningChanged(TILES_SETTING, specs);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index 35360bd19393..8b7e20ed0e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -36,6 +36,8 @@ import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.phone.StatusBarIconController
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.Clock
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -87,8 +89,14 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
@Mock
private lateinit var privacyDialogController: PrivacyDialogController
@Mock
+ private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock
+ private lateinit var variableDateViewController: VariableDateViewController
+ @Mock
private lateinit var clock: Clock
@Mock
+ private lateinit var variableDateView: VariableDateView
+ @Mock
private lateinit var mockView: View
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var context: Context
@@ -109,6 +117,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
stubViews()
`when`(iconContainer.context).thenReturn(context)
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+ `when`(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
`when`(view.resources).thenReturn(mContext.resources)
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)
@@ -133,7 +143,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
colorExtractor,
privacyDialogController,
qsExpansionPathInterpolator,
- featureFlags
+ featureFlags,
+ variableDateViewControllerFactory
)
}
@@ -274,6 +285,8 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
`when`(view.findViewById<StatusIconContainer>(R.id.statusIcons)).thenReturn(iconContainer)
`when`(view.findViewById<OngoingPrivacyChip>(R.id.privacy_chip)).thenReturn(privacyChip)
`when`(view.findViewById<Clock>(R.id.clock)).thenReturn(clock)
+ `when`(view.requireViewById<VariableDateView>(R.id.date)).thenReturn(variableDateView)
+ `when`(view.requireViewById<VariableDateView>(R.id.date_clock)).thenReturn(variableDateView)
}
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 63ebe9290f64..23e51687f9e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -18,6 +18,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -74,6 +75,24 @@ public class QSIconViewImplTest extends SysuiTestCase {
}
@Test
+ public void testMutateIconDrawable() {
+ SlashImageView iv = mock(SlashImageView.class);
+ Drawable originalDrawable = mock(Drawable.class);
+ Drawable otherDrawable = mock(Drawable.class);
+ State s = new State();
+ s.icon = mock(Icon.class);
+ when(s.icon.getInvisibleDrawable(eq(mContext))).thenReturn(originalDrawable);
+ when(s.icon.getDrawable(eq(mContext))).thenReturn(originalDrawable);
+ when(iv.isShown()).thenReturn(true);
+ when(originalDrawable.getConstantState()).thenReturn(fakeConstantState(otherDrawable));
+
+
+ mIconView.updateIcon(iv, s, /* allowAnimations= */true);
+
+ verify(iv).setState(any(), eq(otherDrawable));
+ }
+
+ @Test
public void testNoFirstFade() {
ImageView iv = mock(ImageView.class);
State s = new State();
@@ -104,4 +123,18 @@ public class QSIconViewImplTest extends SysuiTestCase {
public void testIconNotSet_toString() {
assertFalse(mIconView.toString().contains("lastIcon"));
}
+
+ private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
+ return new Drawable.ConstantState() {
+ @Override
+ public Drawable newDrawable() {
+ return otherDrawable;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return 1;
+ }
+ };
+ }
}
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 5e2d8fde84da..b4a662974d22 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
@@ -3,6 +3,7 @@ package com.android.systemui.qs.tiles
import android.app.AlarmManager
import android.app.PendingIntent
import android.os.Handler
+import android.provider.AlarmClock
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -98,6 +99,11 @@ class AlarmTileTest : SysuiTestCase() {
}
@Test
+ fun testDefaultIntentShowAlarms() {
+ assertThat(tile.defaultIntent.action).isEqualTo(AlarmClock.ACTION_SHOW_ALARMS)
+ }
+
+ @Test
fun testInactiveByDefault() {
assertThat(tile.state.state).isEqualTo(Tile.STATE_INACTIVE)
}
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 d44a52607707..e939411e4a2a 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
@@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -327,4 +328,77 @@ public class CastTileTest extends SysuiTestCase {
assertEquals(Tile.STATE_ACTIVE, mCastTile.getState().state);
assertTrue(mCastTile.getState().secondaryLabel.toString().startsWith(connected.name));
}
+
+ @Test
+ public void testExpandView_wifiNotConnected() {
+ mCastTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_wifiEnabledNotCasting() {
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_casting_projection() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastController.CastDevice.STATE_CONNECTED;
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_connecting_projection() {
+ CastController.CastDevice connecting = new CastController.CastDevice();
+ connecting.state = CastDevice.STATE_CONNECTING;
+ connecting.name = "Test Casting Device";
+
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(connecting);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertFalse(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_casting_mediaRoute() {
+ CastController.CastDevice device = new CastController.CastDevice();
+ device.state = CastDevice.STATE_CONNECTED;
+ device.tag = mock(MediaRouter.RouteInfo.class);
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(device);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
+
+ @Test
+ public void testExpandView_connecting_mediaRoute() {
+ CastController.CastDevice connecting = new CastController.CastDevice();
+ connecting.state = CastDevice.STATE_CONNECTING;
+ connecting.tag = mock(RouteInfo.class);
+ connecting.name = "Test Casting Device";
+
+ List<CastDevice> devices = new ArrayList<>();
+ devices.add(connecting);
+ when(mController.getCastDevices()).thenReturn(devices);
+
+ enableWifiAndProcessMessages();
+
+ assertTrue(mCastTile.getState().forceExpandIcon);
+ }
}
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
new file mode 100644
index 000000000000..49ab777624e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.provider.Settings.Secure.CAMERA_AUTOROTATE;
+
+import static junit.framework.TestCase.assertEquals;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.hardware.SensorPrivacyManager;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.text.TextUtils;
+
+import com.android.internal.logging.MetricsLogger;
+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.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class RotationLockTileTest extends SysuiTestCase {
+
+ private static final String PACKAGE_NAME = "package_name";
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private QSTileHost mHost;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private QSLogger mQSLogger;
+ @Mock
+ private SensorPrivacyManager mPrivacyManager;
+ @Mock
+ private BatteryController mBatteryController;
+
+ private SecureSettings mSecureSettings;
+ private RotationLockController mController;
+ private TestableLooper mTestableLooper;
+ private RotationLockTile mLockTile;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTestableLooper = TestableLooper.get(this);
+
+ when(mHost.getContext()).thenReturn(mContext);
+ when(mHost.getUserContext()).thenReturn(mContext);
+
+ mSecureSettings = new FakeSettings();
+ mController = new RotationLockControllerImpl(mContext, mSecureSettings);
+
+ mLockTile = new RotationLockTile(
+ mHost,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mController,
+ mPrivacyManager,
+ mBatteryController,
+ mSecureSettings
+ );
+
+ mLockTile.initialize();
+
+ // We are not setting the mocks to listening, so we trigger a first refresh state to
+ // set the initial state
+ mLockTile.refreshState();
+
+ mTestableLooper.processAllMessages();
+
+ mContext.setMockPackageManager(mPackageManager);
+ doReturn(PACKAGE_NAME).when(mPackageManager).getRotationResolverPackageName();
+ doReturn(PackageManager.PERMISSION_GRANTED).when(mPackageManager).checkPermission(
+ Manifest.permission.CAMERA, PACKAGE_NAME);
+
+ when(mBatteryController.isPowerSave()).thenReturn(false);
+ when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(false);
+ enableAutoRotation();
+ enableCameraBasedRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+ }
+
+ @Test
+ public void testSecondaryString_cameraRotateOn_returnsFaceBased() {
+ assertEquals("On - Face-based", mLockTile.getState().secondaryLabel);
+ }
+
+ @Test
+ public void testSecondaryString_rotateOff_isEmpty() {
+ disableAutoRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_cameraRotateOff_isEmpty() {
+ disableCameraBasedRotation();
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_powerSaveEnabled_isEmpty() {
+ when(mBatteryController.isPowerSave()).thenReturn(true);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_cameraDisabled_isEmpty() {
+ when(mPrivacyManager.isSensorPrivacyEnabled(CAMERA)).thenReturn(true);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ @Test
+ public void testSecondaryString_noCameraPermission_isEmpty() {
+ doReturn(PackageManager.PERMISSION_DENIED).when(mPackageManager).checkPermission(
+ Manifest.permission.CAMERA, PACKAGE_NAME);
+
+ mLockTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(TextUtils.isEmpty(mLockTile.getState().secondaryLabel));
+ }
+
+ private void enableAutoRotation() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 1, UserHandle.USER_CURRENT);
+ }
+
+ private void disableAutoRotation() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 0, UserHandle.USER_CURRENT);
+ }
+
+ private void enableCameraBasedRotation() {
+ mSecureSettings.putIntForUser(
+ CAMERA_AUTOROTATE, 1, UserHandle.USER_CURRENT);
+ }
+
+ private void disableCameraBasedRotation() {
+ mSecureSettings.putIntForUser(
+ CAMERA_AUTOROTATE, 0, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
new file mode 100644
index 000000000000..77946cf791aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java
@@ -0,0 +1,167 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableResources;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class InternetAdapterTest extends SysuiTestCase {
+
+ private static final String WIFI_TITLE = "Wi-Fi Title";
+ private static final String WIFI_SUMMARY = "Wi-Fi Summary";
+ private static final int GEAR_ICON_RES_ID = R.drawable.ic_settings_24dp;
+ private static final int LOCK_ICON_RES_ID = R.drawable.ic_friction_lock_closed;
+
+ @Rule
+ public MockitoRule mRule = MockitoJUnit.rule();
+
+ @Mock
+ private WifiEntry mInternetWifiEntry;
+ @Mock
+ private List<WifiEntry> mWifiEntries;
+ @Mock
+ private WifiEntry mWifiEntry;
+ @Mock
+ private InternetDialogController mInternetDialogController;
+ @Mock
+ private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ private Drawable mGearIcon;
+ @Mock
+ private Drawable mLockIcon;
+
+ private TestableResources mTestableResources;
+ private InternetAdapter mInternetAdapter;
+ private InternetAdapter.InternetViewHolder mViewHolder;
+
+ @Before
+ public void setUp() {
+ mTestableResources = mContext.getOrCreateTestableResources();
+ when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+ when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+
+ mInternetAdapter = new InternetAdapter(mInternetDialogController);
+ mViewHolder = mInternetAdapter.onCreateViewHolder(new LinearLayout(mContext), 0);
+ mInternetAdapter.setWifiEntries(Arrays.asList(mWifiEntry), 1 /* wifiEntriesCount */);
+ mViewHolder.mWifiIconInjector = mWifiIconInjector;
+ }
+
+ @Test
+ public void getItemCount_returnWifiEntriesCount() {
+ for (int i = 0; i < InternetDialogController.MAX_WIFI_ENTRY_COUNT; i++) {
+ mInternetAdapter.setWifiEntries(mWifiEntries, i /* wifiEntriesCount */);
+
+ assertThat(mInternetAdapter.getItemCount()).isEqualTo(i);
+ }
+ }
+
+ @Test
+ public void onBindViewHolder_bindWithOpenWifiNetwork_verifyView() {
+ when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_NONE);
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onBindViewHolder_bindWithSecurityWifiNetwork_verifyView() {
+ when(mWifiEntry.getSecurity()).thenReturn(WifiEntry.SECURITY_PSK);
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ assertThat(mViewHolder.mWifiTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onBindViewHolder_wifiLevelUnreachable_shouldNotGetWifiIcon() {
+ reset(mWifiIconInjector);
+ when(mWifiEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector, never()).getIcon(anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_shouldNotShowXLevelIcon_getIconWithInternet() {
+ when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(false);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector).getIcon(eq(false) /* noInternet */, anyInt());
+ }
+
+ @Test
+ public void onBindViewHolder_shouldShowXLevelIcon_getIconWithNoInternet() {
+ when(mWifiEntry.shouldShowXLevelIcon()).thenReturn(true);
+
+ mInternetAdapter.onBindViewHolder(mViewHolder, 0);
+
+ verify(mWifiIconInjector).getIcon(eq(true) /* noInternet */, anyInt());
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiConnected_updateGearIcon() {
+ mTestableResources.addOverride(GEAR_ICON_RES_ID, mGearIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_CONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mGearIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityPsk_updateLockIcon() {
+ mTestableResources.addOverride(LOCK_ICON_RES_ID, mLockIcon);
+
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_PSK);
+
+ assertThat(mViewHolder.mWifiEndIcon.getDrawable()).isEqualTo(mLockIcon);
+ }
+
+ @Test
+ public void viewHolderUpdateEndIcon_wifiDisconnectedAndSecurityNone_hideIcon() {
+ mViewHolder.updateEndIcon(WifiEntry.CONNECTED_STATE_DISCONNECTED, WifiEntry.SECURITY_NONE);
+
+ assertThat(mViewHolder.mWifiEndIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
new file mode 100644
index 000000000000..f9d5be6c4807
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java
@@ -0,0 +1,646 @@
+package com.android.systemui.qs.tiles.dialog;
+
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT;
+import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.animation.Animator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.text.TextUtils;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.settingslib.wifi.WifiUtils;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
+import com.android.systemui.toast.SystemUIToast;
+import com.android.systemui.toast.ToastFactory;
+import com.android.systemui.util.CarrierConfigTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
+import com.android.wifitrackerlib.MergedCarrierEntry;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogControllerTest extends SysuiTestCase {
+
+ private static final int SUB_ID = 1;
+ private static final String CONNECTED_TITLE = "Connected Wi-Fi Title";
+ private static final String CONNECTED_SUMMARY = "Connected Wi-Fi Summary";
+
+ //SystemUIToast
+ private static final int GRAVITY_FLAGS = Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL;
+ private static final int TOAST_MESSAGE_STRING_ID = 1;
+ private static final String TOAST_MESSAGE_STRING = "toast message";
+
+ @Mock
+ private WifiManager mWifiManager;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private SubscriptionManager mSubscriptionManager;
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private Handler mWorkerHandler;
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private GlobalSettings mGlobalSettings;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private NetworkController.AccessPointController mAccessPointController;
+ @Mock
+ private WifiEntry mConnectedEntry;
+ @Mock
+ private WifiEntry mWifiEntry1;
+ @Mock
+ private WifiEntry mWifiEntry2;
+ @Mock
+ private WifiEntry mWifiEntry3;
+ @Mock
+ private WifiEntry mWifiEntry4;
+ @Mock
+ private MergedCarrierEntry mMergedCarrierEntry;
+ @Mock
+ private ServiceState mServiceState;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private WifiUtils.InternetIconInjector mWifiIconInjector;
+ @Mock
+ InternetDialogController.InternetDialogCallback mInternetDialogCallback;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private ToastFactory mToastFactory;
+ @Mock
+ private SystemUIToast mSystemUIToast;
+ @Mock
+ private View mToastView;
+ @Mock
+ private Animator mAnimator;
+ @Mock
+ private CarrierConfigTracker mCarrierConfigTracker;
+
+ private TestableResources mTestableResources;
+ private MockInternetDialogController mInternetDialogController;
+ private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private List<WifiEntry> mAccessPoints = new ArrayList<>();
+ private List<WifiEntry> mWifiEntries = new ArrayList<>();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mTestableResources = mContext.getOrCreateTestableResources();
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+ when(mConnectedEntry.isDefaultNetwork()).thenReturn(true);
+ when(mConnectedEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntry1.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry2.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry3.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ when(mWifiEntry4.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_DISCONNECTED);
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
+ when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{SUB_ID});
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(mMergedCarrierEntry);
+ when(mToastFactory.createToast(any(), anyString(), anyString(), anyInt(), anyInt()))
+ .thenReturn(mSystemUIToast);
+ when(mSystemUIToast.getView()).thenReturn(mToastView);
+ when(mSystemUIToast.getGravity()).thenReturn(GRAVITY_FLAGS);
+ when(mSystemUIToast.getInAnimation()).thenReturn(mAnimator);
+
+ mInternetDialogController = new MockInternetDialogController(mContext,
+ mock(UiEventLogger.class), mock(ActivityStarter.class), mAccessPointController,
+ mSubscriptionManager, mTelephonyManager, mWifiManager,
+ mock(ConnectivityManager.class), mHandler, mExecutor, mBroadcastDispatcher,
+ mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
+ mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker);
+ mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
+ mInternetDialogController.mOnSubscriptionsChangedListener);
+ mInternetDialogController.onStart(mInternetDialogCallback, true);
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+ mInternetDialogController.mActivityStarter = mActivityStarter;
+ mInternetDialogController.mWifiIconInjector = mWifiIconInjector;
+ }
+
+ @Test
+ public void connectCarrierNetwork_mergedCarrierEntryCanConnect_connectAndCreateSysUiToast() {
+ when(mMergedCarrierEntry.canConnect()).thenReturn(true);
+ mTestableResources.addOverride(R.string.wifi_wont_autoconnect_for_now,
+ TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.connectCarrierNetwork();
+
+ verify(mMergedCarrierEntry).connect(null /* callback */, false /* showToast */);
+ verify(mToastFactory).createToast(any(), eq(TOAST_MESSAGE_STRING), anyString(), anyInt(),
+ anyInt());
+ }
+
+ @Test
+ public void makeOverlayToast_withGravityFlags_addViewWithLayoutParams() {
+ mTestableResources.addOverride(TOAST_MESSAGE_STRING_ID, TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.makeOverlayToast(TOAST_MESSAGE_STRING_ID);
+
+ ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor = ArgumentCaptor.forClass(
+ WindowManager.LayoutParams.class);
+ verify(mWindowManager).addView(eq(mToastView), paramsCaptor.capture());
+ WindowManager.LayoutParams params = paramsCaptor.getValue();
+ assertThat(params.format).isEqualTo(PixelFormat.TRANSLUCENT);
+ assertThat(params.type).isEqualTo(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ assertThat(params.horizontalWeight).isEqualTo(TOAST_PARAMS_HORIZONTAL_WEIGHT);
+ assertThat(params.verticalWeight).isEqualTo(TOAST_PARAMS_VERTICAL_WEIGHT);
+ }
+
+ @Test
+ public void makeOverlayToast_withAnimation_verifyAnimatorStart() {
+ mTestableResources.addOverride(TOAST_MESSAGE_STRING_ID, TOAST_MESSAGE_STRING);
+
+ mInternetDialogController.makeOverlayToast(TOAST_MESSAGE_STRING_ID);
+
+ verify(mAnimator).start();
+ }
+
+ @Test
+ public void getDialogTitleText_withAirplaneModeOn_returnAirplaneMode() {
+ mInternetDialogController.setAirplaneModeEnabled(true);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+ getResourcesString("airplane_mode")));
+ }
+
+ @Test
+ public void getDialogTitleText_withAirplaneModeOff_returnInternet() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getDialogTitleText(),
+ getResourcesString("quick_settings_internet_label")));
+ }
+
+ @Test
+ public void getSubtitleText_withAirplaneModeOn_returnNull() {
+ mInternetDialogController.setAirplaneModeEnabled(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false)).isNull();
+ }
+
+ @Test
+ public void getSubtitleText_withWifiOff_returnWifiIsOff() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("wifi_is_off"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("wifi_is_off"));
+ }
+
+ @Test
+ public void getSubtitleText_withNoWifiEntry_returnSearchWifi() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ assertThat(mInternetDialogController.getSubtitleText(true))
+ .isEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(true))
+ .isNotEqualTo(getResourcesString("wifi_empty_list_wifi_on"));
+ }
+
+ @Test
+ public void getSubtitleText_withWifiEntry_returnTapToConnect() {
+ // The preconditions WiFi Entries is already in setUp()
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("tap_a_network_to_connect"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("tap_a_network_to_connect"));
+ }
+
+ @Test
+ public void getSubtitleText_deviceLockedWithWifiOn_returnUnlockToViewNetworks() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("unlock_to_view_networks")));
+ }
+
+ @Test
+ public void getSubtitleText_withNoService_returnNoNetworksAvailable() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_OUT_OF_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+ doReturn(TelephonyManager.DATA_DISCONNECTED).when(mTelephonyManager).getDataState();
+
+ assertTrue(TextUtils.equals(mInternetDialogController.getSubtitleText(false),
+ getResourcesString("all_network_unavailable")));
+ }
+
+ @Test
+ public void getSubtitleText_withMobileDataDisabled_returnNoOtherAvailable() {
+ mInternetDialogController.setAirplaneModeEnabled(false);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ doReturn(ServiceState.STATE_IN_SERVICE).when(mServiceState).getState();
+ doReturn(mServiceState).when(mTelephonyManager).getServiceState();
+
+ when(mTelephonyManager.isDataEnabled()).thenReturn(false);
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isEqualTo(getResourcesString("non_carrier_network_unavailable"));
+
+ // if the Wi-Fi disallow config, then don't return Wi-Fi related string.
+ mInternetDialogController.mCanConfigWifi = false;
+
+ assertThat(mInternetDialogController.getSubtitleText(false))
+ .isNotEqualTo(getResourcesString("non_carrier_network_unavailable"));
+ }
+
+ @Test
+ public void getWifiDetailsSettingsIntent_withNoKey_returnNull() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent(null)).isNull();
+ }
+
+ @Test
+ public void getWifiDetailsSettingsIntent_withKey_returnIntent() {
+ assertThat(mInternetDialogController.getWifiDetailsSettingsIntent("test_key")).isNotNull();
+ }
+
+ @Test
+ public void getInternetWifiDrawable_withConnectedEntry_returnIntentIconWithCorrectColor() {
+ final Drawable drawable = mock(Drawable.class);
+ when(mWifiIconInjector.getIcon(anyBoolean(), anyInt())).thenReturn(drawable);
+
+ mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ verify(mWifiIconInjector).getIcon(eq(false), anyInt());
+ verify(drawable).setTint(mContext.getColor(R.color.connected_network_primary_color));
+ }
+
+ @Test
+ public void getInternetWifiDrawable_withWifiLevelUnreachable_returnNull() {
+ when(mConnectedEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE);
+
+ Drawable drawable = mInternetDialogController.getInternetWifiDrawable(mConnectedEntry);
+
+ assertThat(drawable).isNull();
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withNoWifiEntryKey_doNothing() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting(null /* key */);
+
+ verify(mActivityStarter, never())
+ .postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void launchWifiNetworkDetailsSetting_withWifiEntryKey_startActivity() {
+ mInternetDialogController.launchWifiNetworkDetailsSetting("wifi_entry_key");
+
+ verify(mActivityStarter).postStartActivityDismissingKeyguard(any(Intent.class), anyInt());
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsUnlocked_returnFalse() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(true);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isFalse();
+ }
+
+ @Test
+ public void isDeviceLocked_keyguardIsLocked_returnTrue() {
+ when(mKeyguardStateController.isUnlocked()).thenReturn(false);
+
+ assertThat(mInternetDialogController.isDeviceLocked()).isTrue();
+ }
+
+ @Test
+ public void onAccessPointsChanged_canNotConfigWifi_doNothing() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.mCanConfigWifi = false;
+
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ verify(mInternetDialogCallback, never()).onAccessPointsChanged(any(), any());
+ }
+
+ @Test
+ public void onAccessPointsChanged_nullAccessPoints_callbackBothNull() {
+ reset(mInternetDialogCallback);
+
+ mInternetDialogController.onAccessPointsChanged(null /* accessPoints */);
+
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntry_callbackConnectedEntryOnly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_noConnectedEntryAndOneOther_callbackWifiEntriesOnly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndOneOther_callbackCorrectly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndTwoOthers_callbackCorrectly() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndThreeOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_oneConnectedEntryAndFourOthers_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mConnectedEntry);
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback).onAccessPointsChanged(mWifiEntries, mConnectedEntry);
+ }
+
+ @Test
+ public void onAccessPointsChanged_fourWifiEntries_callbackCutMore() {
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(true);
+ mAccessPoints.clear();
+ mAccessPoints.add(mWifiEntry1);
+ mAccessPoints.add(mWifiEntry2);
+ mAccessPoints.add(mWifiEntry3);
+ mAccessPoints.add(mWifiEntry4);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.clear();
+ mWifiEntries.add(mWifiEntry1);
+ mWifiEntries.add(mWifiEntry2);
+ mWifiEntries.add(mWifiEntry3);
+ mWifiEntries.add(mWifiEntry4);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+ // If the Ethernet exists, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.mHasEthernet = true;
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry4);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+
+ // Turn off airplane mode to has carrier network, then Wi-Fi entries will cut last one.
+ reset(mInternetDialogCallback);
+ mInternetDialogController.setAirplaneModeEnabled(false);
+
+ mInternetDialogController.onAccessPointsChanged(mAccessPoints);
+
+ mWifiEntries.remove(mWifiEntry3);
+ verify(mInternetDialogCallback)
+ .onAccessPointsChanged(mWifiEntries, null /* connectedEntry */);
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_carrierProvisionsEnabled_doNothing() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(true);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+
+ verify(mMergedCarrierEntry, never()).setEnabled(anyBoolean());
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_mergedCarrierEntryEmpty_doesntCrash() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(false);
+ when(mAccessPointController.getMergedCarrierEntry()).thenReturn(null);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+ }
+
+ @Test
+ public void setMergedCarrierWifiEnabledIfNeed_neededSetMergedCarrierEntry_setTogether() {
+ when(mCarrierConfigTracker.getCarrierProvisionsWifiMergedNetworksBool(SUB_ID))
+ .thenReturn(false);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, true);
+
+ verify(mMergedCarrierEntry).setEnabled(true);
+
+ mInternetDialogController.setMergedCarrierWifiEnabledIfNeed(SUB_ID, false);
+
+ verify(mMergedCarrierEntry).setEnabled(false);
+ }
+
+ private String getResourcesString(String name) {
+ return mContext.getResources().getString(getResourcesId(name));
+ }
+
+ private int getResourcesId(String name) {
+ return mContext.getResources().getIdentifier(name, "string",
+ mContext.getPackageName());
+ }
+
+ private class MockInternetDialogController extends InternetDialogController {
+
+ private GlobalSettings mGlobalSettings;
+ private boolean mIsAirplaneModeOn;
+
+ MockInternetDialogController(Context context, UiEventLogger uiEventLogger,
+ ActivityStarter starter, AccessPointController accessPointController,
+ SubscriptionManager subscriptionManager, TelephonyManager telephonyManager,
+ @Nullable WifiManager wifiManager, ConnectivityManager connectivityManager,
+ @Main Handler handler, @Main Executor mainExecutor,
+ BroadcastDispatcher broadcastDispatcher,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, GlobalSettings globalSettings,
+ KeyguardStateController keyguardStateController, WindowManager windowManager,
+ ToastFactory toastFactory, Handler workerHandler,
+ CarrierConfigTracker carrierConfigTracker) {
+ super(context, uiEventLogger, starter, accessPointController, subscriptionManager,
+ telephonyManager, wifiManager, connectivityManager, handler, mainExecutor,
+ broadcastDispatcher, keyguardUpdateMonitor, globalSettings,
+ keyguardStateController, windowManager, toastFactory, workerHandler,
+ carrierConfigTracker);
+ mGlobalSettings = globalSettings;
+ }
+
+ @Override
+ boolean isAirplaneModeEnabled() {
+ return mIsAirplaneModeOn;
+ }
+
+ public void setAirplaneModeEnabled(boolean enabled) {
+ mIsAirplaneModeOn = enabled;
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
new file mode 100644
index 000000000000..c42b64a09985
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -0,0 +1,334 @@
+package com.android.systemui.qs.tiles.dialog;
+
+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.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.wifitrackerlib.WifiEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class InternetDialogTest extends SysuiTestCase {
+
+ private static final String MOBILE_NETWORK_TITLE = "Mobile Title";
+ private static final String MOBILE_NETWORK_SUMMARY = "Mobile Summary";
+ private static final String WIFI_TITLE = "Connected Wi-Fi Title";
+ private static final String WIFI_SUMMARY = "Connected Wi-Fi Summary";
+
+ @Mock
+ private Handler mHandler;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private WifiManager mWifiManager;
+ @Mock
+ private WifiEntry mInternetWifiEntry;
+ @Mock
+ private List<WifiEntry> mWifiEntries;
+ @Mock
+ private InternetAdapter mInternetAdapter;
+ @Mock
+ private InternetDialogController mInternetDialogController;
+
+ private InternetDialog mInternetDialog;
+ private View mDialogView;
+ private View mSubTitle;
+ private LinearLayout mEthernet;
+ private LinearLayout mMobileDataToggle;
+ private LinearLayout mWifiToggle;
+ private LinearLayout mConnectedWifi;
+ private RecyclerView mWifiList;
+ private LinearLayout mSeeAll;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ when(mInternetWifiEntry.getTitle()).thenReturn(WIFI_TITLE);
+ when(mInternetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY);
+ when(mInternetWifiEntry.isDefaultNetwork()).thenReturn(true);
+ when(mInternetWifiEntry.hasInternetAccess()).thenReturn(true);
+ when(mWifiEntries.size()).thenReturn(1);
+
+ when(mInternetDialogController.getMobileNetworkTitle()).thenReturn(MOBILE_NETWORK_TITLE);
+ when(mInternetDialogController.getMobileNetworkSummary())
+ .thenReturn(MOBILE_NETWORK_SUMMARY);
+ when(mInternetDialogController.getWifiManager()).thenReturn(mWifiManager);
+
+ mInternetDialog = new InternetDialog(mContext, mock(InternetDialogFactory.class),
+ mInternetDialogController, true, true, true, mock(UiEventLogger.class), mHandler);
+ mInternetDialog.mAdapter = mInternetAdapter;
+ mInternetDialog.onAccessPointsChanged(mWifiEntries, mInternetWifiEntry);
+ mInternetDialog.show();
+
+ mDialogView = mInternetDialog.mDialogView;
+ mSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
+ mEthernet = mDialogView.requireViewById(R.id.ethernet_layout);
+ mMobileDataToggle = mDialogView.requireViewById(R.id.mobile_network_layout);
+ mWifiToggle = mDialogView.requireViewById(R.id.turn_on_wifi_layout);
+ mConnectedWifi = mDialogView.requireViewById(R.id.wifi_connected_layout);
+ mWifiList = mDialogView.requireViewById(R.id.wifi_list_layout);
+ mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
+ }
+
+ @After
+ public void tearDown() {
+ mInternetDialog.dismissDialog();
+ }
+
+ @Test
+ public void hideWifiViews_WifiViewsGone() {
+ mInternetDialog.hideWifiViews();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOn_internetDialogSubTitleGone() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mSubTitle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndHasEthernet_showEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOffAndNoEthernet_hideEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
+ when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndHasEthernet_showEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mInternetDialogController.hasEthernet()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_apmOnAndNoEthernet_hideEthernet() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+ when(mInternetDialogController.hasEthernet()).thenReturn(false);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_withApmOn_mobileDataLayoutGone() {
+ when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mMobileDataToggle.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.onAccessPointsChanged(mWifiEntries, null /* connectedEntry*/);
+ doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndNoWifiList_hideWifiListAndSeeAll() {
+ // The precondition WiFi ON is already in setUp()
+ mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, mInternetWifiEntry);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_wifiOnAndHasWifiList_showWifiListAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasInternetWifi_showHighlightWifiToggle() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mWifiToggle.getBackground()).isNotNull();
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasInternetWifi_hideConnectedWifi() {
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void updateDialog_deviceLockedAndHasWifiList_hideWifiListAndSeeAll() {
+ // The preconditions WiFi entries are already in setUp()
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.updateDialog();
+
+ assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+ assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+ mSeeAll.performClick();
+
+ verify(mInternetDialogController).launchNetworkSetting();
+ }
+
+ @Test
+ public void showProgressBar_wifiDisabled_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(false);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
+ public void showProgressBar_deviceLocked_hideProgressBar() {
+ Mockito.reset(mHandler);
+ when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ verify(mHandler, never()).postDelayed(any(Runnable.class), anyLong());
+ }
+
+ @Test
+ public void showProgressBar_wifiEnabledWithWifiEntry_showProgressBarThenHide() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+
+ mInternetDialog.showProgressBar();
+
+ // Show progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableCaptor.capture(),
+ eq(InternetDialog.PROGRESS_DELAY_MS));
+ runnableCaptor.getValue().run();
+
+ // Then hide progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isFalse();
+ }
+
+ @Test
+ public void showProgressBar_wifiEnabledWithoutWifiEntries_showProgressBarThenHideSearch() {
+ Mockito.reset(mHandler);
+ when(mWifiManager.isWifiEnabled()).thenReturn(true);
+ mInternetDialog.onAccessPointsChanged(null /* wifiEntries */, null /* connectedEntry*/);
+
+ mInternetDialog.showProgressBar();
+
+ // Show progress bar
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ verify(mHandler).postDelayed(runnableCaptor.capture(),
+ eq(InternetDialog.PROGRESS_DELAY_MS));
+ runnableCaptor.getValue().run();
+
+ // Then hide searching sub-title only
+ assertThat(mInternetDialog.mIsProgressBarVisible).isTrue();
+ assertThat(mInternetDialog.mIsSearchingHidden).isTrue();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
index 10c878a92745..6f081c759df7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScrollCaptureControllerTest.java
@@ -34,6 +34,7 @@ import android.view.ScrollCaptureResponse;
import androidx.test.filters.SmallTest;
+import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.ScrollCaptureClient.Session;
@@ -274,7 +275,8 @@ public class ScrollCaptureControllerTest extends SysuiTestCase {
when(client.start(/* response */ any(), /* maxPages */ anyFloat()))
.thenReturn(immediateFuture(session));
return new ScrollCaptureController(context, context.getMainExecutor(),
- client, new ImageTileSet(context.getMainThreadHandler()));
+ client, new ImageTileSet(context.getMainThreadHandler()),
+ new UiEventLoggerFake());
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 21c6292c151f..f3762c566731 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -423,17 +423,18 @@ public class CommandQueueTest extends SysuiTestCase {
final boolean credentialAllowed = true;
final boolean requireConfirmation = true;
final int userId = 10;
- final String packageName = "test";
final long operationId = 1;
+ final String packageName = "test";
+ final long requestId = 10;
final int multiSensorConfig = BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT;
mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
- credentialAllowed, requireConfirmation , userId, packageName, operationId,
+ credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId,
multiSensorConfig);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
- eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(packageName),
- eq(operationId), eq(multiSensorConfig));
+ eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId),
+ eq(packageName), eq(requestId), eq(multiSensorConfig));
}
@Test
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 f5ce673c249e..f5cab1df9fa2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -23,6 +23,7 @@ import static android.content.pm.UserInfo.FLAG_MANAGED_PROFILE;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE;
+import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
@@ -111,6 +112,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
private static final ComponentName DEVICE_OWNER_COMPONENT = new ComponentName("com.android.foo",
"bar");
+ private String mKeyguardTryFingerprintMsg;
private String mDisclosureWithOrganization;
private String mDisclosureGeneric;
private String mFinancedDisclosureWithOrganization;
@@ -182,6 +184,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mContext.addMockSystemService(UserManager.class, mUserManager);
mContext.addMockSystemService(Context.TRUST_SERVICE, mock(TrustManager.class));
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE, mock(FingerprintManager.class));
+ mKeyguardTryFingerprintMsg = mContext.getString(R.string.keyguard_try_fingerprint);
mDisclosureWithOrganization = mContext.getString(R.string.do_disclosure_with_name,
ORGANIZATION_NAME);
mDisclosureGeneric = mContext.getString(R.string.do_disclosure_generic);
@@ -637,7 +640,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
- public void onRefreshBatteryInfo_fullChargedWithOverheat_presentCharged() {
+ public void onRefreshBatteryInfo_fullChargedWithOverheat_presentChargingLimited() {
createController();
BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
@@ -649,6 +652,24 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
verifyIndicationMessage(
INDICATION_TYPE_BATTERY,
+ mContext.getString(
+ R.string.keyguard_plugged_in_charging_limited,
+ NumberFormat.getPercentInstance().format(100 / 100f)));
+ }
+
+ @Test
+ public void onRefreshBatteryInfo_fullChargedWithoutOverheat_presentCharged() {
+ createController();
+ BatteryStatus status = new BatteryStatus(BatteryManager.BATTERY_STATUS_CHARGING,
+ 100 /* level */, BatteryManager.BATTERY_PLUGGED_AC,
+ BatteryManager.BATTERY_HEALTH_GOOD, 0 /* maxChargingWattage */,
+ true /* present */);
+
+ mController.getKeyguardCallback().onRefreshBatteryInfo(status);
+ mController.setVisible(true);
+
+ verifyIndicationMessage(
+ INDICATION_TYPE_BATTERY,
mContext.getString(R.string.keyguard_charged));
}
@@ -677,6 +698,48 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
verifyTransientMessage(message);
}
+ @Test
+ public void faceAuthMessageSuppressed() {
+ createController();
+ String faceHelpMsg = "Face auth help message";
+
+ // GIVEN state of showing message when keyguard screen is on
+ when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.isScreenOn()).thenReturn(true);
+
+ // GIVEN fingerprint is also running (not udfps)
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsAvailable()).thenReturn(false);
+
+ mController.setVisible(true);
+
+ // WHEN a face help message comes in
+ mController.getKeyguardCallback().onBiometricHelp(
+ KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED, faceHelpMsg,
+ BiometricSourceType.FACE);
+
+ // THEN "try fingerprint" message appears (and not the face help message)
+ verifyTransientMessage(mKeyguardTryFingerprintMsg);
+
+ // THEN the face help message is still announced for a11y
+ verify(mIndicationAreaBottom).announceForAccessibility(eq(faceHelpMsg));
+ }
+
+ @Test
+ public void testEmptyOwnerInfoHidesIndicationArea() {
+ createController();
+
+ // GIVEN the owner info is set to an empty string
+ when(mLockPatternUtils.getDeviceOwnerInfo()).thenReturn("");
+
+ // WHEN asked to update the indication area
+ mController.setVisible(true);
+
+ // THEN the owner info should be hidden
+ verifyHideIndication(INDICATION_TYPE_OWNER_INFO);
+ }
+
private void sendUpdateDisclosureBroadcast() {
mBroadcastReceiver.onReceive(mContext, new Intent());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
new file mode 100644
index 000000000000..97fe25d9a619
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LightRevealScrimTest : SysuiTestCase() {
+
+ private lateinit var scrim: LightRevealScrim
+ private var isOpaque = false
+
+ @Before
+ fun setUp() {
+ scrim = LightRevealScrim(context, null)
+ scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
+ isOpaque = opaque
+ }
+ scrim.revealAmount = 0f
+ assertTrue("Scrim is not opaque in initial setup", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testAlphaSetsOpaque() {
+ scrim.alpha = 0.5f
+ assertFalse("Scrim is opaque even though alpha is set", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testVisibilitySetsOpaque() {
+ scrim.visibility = View.INVISIBLE
+ assertFalse("Scrim is opaque even though it's invisible", scrim.isScrimOpaque)
+ scrim.visibility = View.GONE
+ assertFalse("Scrim is opaque even though it's gone", scrim.isScrimOpaque)
+ }
+
+ @Test
+ fun testRevealSetsOpaque() {
+ scrim.revealAmount = 0.5f
+ assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 7c045c1f2894..465370b59553 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -25,6 +25,7 @@ import android.view.View
import android.view.ViewRootImpl
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
@@ -32,6 +33,7 @@ import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.eq
+import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -69,7 +71,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Mock private lateinit var root: View
@Mock private lateinit var viewRootImpl: ViewRootImpl
@Mock private lateinit var windowToken: IBinder
- @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var shadeAnimation: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
@Mock private lateinit var listener: NotificationShadeDepthController.DepthListener
@@ -89,10 +90,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
`when`(root.isAttachedToWindow).thenReturn(true)
`when`(statusBarStateController.state).then { statusBarState }
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
- (answer.arguments[0] as Float * maxBlur).toInt()
+ answer.arguments[0] as Float * maxBlur.toFloat()
}
- `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer ->
- answer.arguments[0] as Int / maxBlur.toFloat()
+ `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer ->
+ answer.arguments[0] as Float / maxBlur.toFloat()
}
`when`(blurUtils.supportsBlursOnWindows()).thenReturn(true)
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
@@ -102,7 +103,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
statusBarStateController, blurUtils, biometricUnlockController,
keyguardStateController, choreographer, wallpaperManager,
notificationShadeWindowController, dozeParameters, dumpManager)
- notificationShadeDepthController.shadeSpring = shadeSpring
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
notificationShadeDepthController.root = root
@@ -123,7 +123,6 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
fun onPanelExpansionChanged_apliesBlur_ifShade() {
notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
false /* tracking */)
- verify(shadeSpring).animateTo(eq(maxBlur), any())
verify(shadeAnimation).animateTo(eq(maxBlur), any())
}
@@ -170,23 +169,48 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ fun onPanelExpansionChanged_respectsMinPanelPullDownFraction() {
+ notificationShadeDepthController.panelPullDownMinFraction = 0.5f
+ notificationShadeDepthController.onPanelExpansionChanged(0.5f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0f)
+
+ notificationShadeDepthController.onPanelExpansionChanged(0.75f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(0.5f)
+
+ notificationShadeDepthController.onPanelExpansionChanged(1f /* expansion */,
+ true /* tracking */)
+ assertThat(notificationShadeDepthController.shadeExpansion).isEqualTo(1f)
+ }
+
+ @Test
fun onStateChanged_reevalutesBlurs_ifSameRadiusAndNewState() {
onPanelExpansionChanged_apliesBlur_ifShade()
- clearInvocations(shadeSpring)
- clearInvocations(shadeAnimation)
+ clearInvocations(choreographer)
statusBarState = StatusBarState.KEYGUARD
statusBarStateListener.onStateChanged(statusBarState)
- verify(shadeSpring).animateTo(eq(0), any())
verify(shadeAnimation).animateTo(eq(0), any())
}
@Test
fun setQsPanelExpansion_appliesBlur() {
+ statusBarState = StatusBarState.KEYGUARD
notificationShadeDepthController.qsPanelExpansion = 1f
- notificationShadeDepthController.onPanelExpansionChanged(0.5f, tracking = false)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
+ }
+
+ @Test
+ fun setQsPanelExpansion_easing() {
+ statusBarState = StatusBarState.KEYGUARD
+ notificationShadeDepthController.qsPanelExpansion = 0.25f
+ notificationShadeDepthController.onPanelExpansionChanged(1f, tracking = false)
notificationShadeDepthController.updateBlurCallback.doFrame(0)
- verify(blurUtils).applyBlur(any(), eq(maxBlur / 2), eq(false))
+ verify(wallpaperManager).setWallpaperZoomOut(any(),
+ eq(Interpolators.getNotificationScrimAlpha(0.25f, false /* notifications */)))
}
@Test
@@ -207,10 +231,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
fun setFullShadeTransition_appliesBlur_onlyIfSupported() {
reset(blurUtils)
`when`(blurUtils.blurRadiusOfRatio(anyFloat())).then { answer ->
- (answer.arguments[0] as Float * maxBlur).toInt()
+ answer.arguments[0] as Float * maxBlur
}
- `when`(blurUtils.ratioOfBlurRadius(anyInt())).then { answer ->
- answer.arguments[0] as Int / maxBlur.toFloat()
+ `when`(blurUtils.ratioOfBlurRadius(anyFloat())).then { answer ->
+ answer.arguments[0] as Float / maxBlur.toFloat()
}
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur)
@@ -239,16 +263,16 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun updateBlurCallback_setsBlur_whenExpanded() {
- `when`(shadeSpring.radius).thenReturn(maxBlur)
- `when`(shadeAnimation.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(maxBlur), eq(false))
}
@Test
fun updateBlurCallback_ignoreShadeBlurUntilHidden_overridesZoom() {
- `when`(shadeSpring.radius).thenReturn(maxBlur)
- `when`(shadeAnimation.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.blursDisabledForAppLaunch = true
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(blurUtils).applyBlur(any(), eq(0), eq(false))
@@ -293,8 +317,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
// Brightness mirror is fully visible
`when`(brightnessSpring.ratio).thenReturn(1f)
// And shade is blurred
- `when`(shadeSpring.radius).thenReturn(maxBlur)
- `when`(shadeAnimation.radius).thenReturn(maxBlur)
+ notificationShadeDepthController.onPanelExpansionChanged(1f, false)
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
notificationShadeDepthController.updateBlurCallback.doFrame(0)
verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
@@ -304,10 +328,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@Test
fun ignoreShadeBlurUntilHidden_whennNull_ignoresIfShadeHasNoBlur() {
- `when`(shadeSpring.radius).thenReturn(0)
- `when`(shadeAnimation.radius).thenReturn(0)
+ `when`(shadeAnimation.radius).thenReturn(0f)
notificationShadeDepthController.blursDisabledForAppLaunch = true
- verify(shadeSpring, never()).animateTo(anyInt(), any())
verify(shadeAnimation, never()).animateTo(anyInt(), any())
}
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 116f807a888d..9e103d68dd1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -44,6 +44,8 @@ import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener
import com.android.systemui.util.concurrency.FakeExecution
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -90,6 +92,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
+ private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock
private lateinit var handler: Handler
@Mock
@@ -107,12 +111,15 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+ @Captor
+ private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
private lateinit var sessionListener: OnTargetsAvailableListener
private lateinit var userListener: UserTracker.Callback
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
+ private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
@@ -144,6 +151,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
`when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
@@ -161,11 +170,15 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
contentResolver,
configurationController,
statusBarStateController,
+ deviceProvisionedController,
execution,
executor,
handler,
Optional.of(plugin)
)
+
+ verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
+ deviceProvisionedListener = deviceProvisionedCaptor.value
}
@Test(expected = RuntimeException::class)
@@ -180,6 +193,27 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
}
@Test
+ fun connectOnlyAfterDeviceIsProvisioned() {
+ // GIVEN an unprovisioned device and an attempt to connect
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false)
+
+ // WHEN a connection attempt is made
+ controller.buildAndConnectView(fakeParent)
+
+ // THEN no session is created
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+
+ // WHEN it does become provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onUserSetupChanged()
+
+ // THEN the session is created
+ verify(smartspaceManager).createSmartspaceSession(any())
+ }
+
+ @Test
fun testListenersAreRegistered() {
// GIVEN a listener is added after a session is created
connectSession()
@@ -424,6 +458,20 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
assertEquals(fakeSmartspaceView, controller.view)
}
+ @Test
+ fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
+ // GIVEN an uninitalized smartspaceView
+ // WHEN the device is provisioned
+ `when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true)
+ `when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true)
+ deviceProvisionedListener.onDeviceProvisionedChanged()
+
+ // THEN no calls to createSmartspaceSession should occur
+ verify(smartspaceManager, never()).createSmartspaceSession(any())
+ // THEN no listeners should be registered
+ verify(configurationController, never()).addCallback(any())
+ }
+
private fun connectSession() {
controller.buildAndConnectView(fakeParent)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index b03df880f0ba..4151ab2044f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -290,6 +290,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testUpdateFooter_noNotifications() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
+
FooterView view = mock(FooterView.class);
mStackScroller.setFooterView(view);
mStackScroller.updateFooter();
@@ -299,6 +301,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testUpdateFooter_remoteInput() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
when(row.canViewBeDismissed()).thenReturn(true);
@@ -318,6 +321,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testUpdateFooter_oneClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
@@ -331,8 +335,25 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
+ setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(false);
+
+ when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
+ when(mStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL))
+ .thenReturn(true);
+ when(mStackScrollLayoutController.hasActiveNotifications()).thenReturn(true);
+
+ FooterView view = mock(FooterView.class);
+ mStackScroller.setFooterView(view);
+ mStackScroller.updateFooter();
+ verify(mStackScroller).updateFooterView(false, true, true);
+ }
+
+ @Test
public void testUpdateFooter_oneNonClearableNotification() {
setBarStateForTest(StatusBarState.SHADE);
+ mStackScroller.setCurrentUserSetup(true);
ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
when(row.canViewBeDismissed()).thenReturn(false);
@@ -351,6 +372,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
public void testUpdateFooter_atEnd() {
+ mStackScroller.setCurrentUserSetup(true);
+
// add footer
mStackScroller.inflateFooterView();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
index f376e88b2cb1..42f38891b1bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollerControllerTest.java
@@ -74,6 +74,7 @@ import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
@@ -98,6 +99,7 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private TunerService mTunerService;
+ @Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
@Mock private ConfigurationController mConfigurationController;
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@@ -153,6 +155,7 @@ public class NotificationStackScrollerControllerTest extends SysuiTestCase {
mHeadsUpManager,
mNotificationRoundnessManager,
mTunerService,
+ mDeviceProvisionedController,
mDynamicPrivacyController,
mConfigurationController,
mSysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 60f0b68acac3..4276f7ce7b12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -274,6 +274,26 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
+ public void onBiometricAuthenticated_onLockScreen() {
+ // GIVEN not dozing
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+
+ // WHEN we want to unlock collapse
+ mBiometricUnlockController.startWakeAndUnlock(
+ BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
+
+ // THEN we collpase the panels and notify authenticated
+ verify(mShadeController).animateCollapsePanels(
+ /* flags */ anyInt(),
+ /* force */ eq(true),
+ /* delayed */ eq(false),
+ /* speedUpFactor */ anyFloat()
+ );
+ verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(
+ /* strongAuth */ eq(false));
+ }
+
+ @Test
public void onBiometricAuthenticated_whenFace_noBypass_encrypted_doNothing() {
reset(mUpdateMonitor);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index 690b8415762d..1043faa8b1bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -16,24 +16,37 @@
package com.android.systemui.statusbar.phone;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.AdditionalAnswers.returnsFirstArg;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.util.BurnInHelperKt;
+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 org.mockito.MockitoSession;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final int SCREEN_HEIGHT = 2000;
- private static final int EMPTY_MARGIN = 0;
private static final int EMPTY_HEIGHT = 0;
private static final float ZERO_DRAG = 0.f;
private static final float OPAQUE = 1.f;
@@ -41,10 +54,15 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private static final boolean HAS_CUSTOM_CLOCK = false;
private static final boolean HAS_VISIBLE_NOTIFS = false;
+ @Mock
+ private Resources mResources;
+
private KeyguardClockPositionAlgorithm mClockPositionAlgorithm;
private KeyguardClockPositionAlgorithm.Result mClockPosition;
+ private MockitoSession mStaticMockSession;
private int mNotificationStackHeight;
private float mPanelExpansion;
+ private int mKeyguardStatusBarHeaderHeight;
private int mKeyguardStatusHeight;
private float mDark;
private boolean mHasCustomClock;
@@ -52,16 +70,32 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private float mQsExpansion;
private int mCutoutTopInset = 0; // in pixels
private boolean mIsSplitShade = false;
+ private float mUdfpsTop = -1;
+ private float mClockBottom = SCREEN_HEIGHT / 2;
+ private boolean mClockTopAligned;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mStaticMockSession = mockitoSession()
+ .mockStatic(BurnInHelperKt.class)
+ .startMocking();
+
mClockPositionAlgorithm = new KeyguardClockPositionAlgorithm();
+ when(mResources.getDimensionPixelSize(anyInt())).thenReturn(0);
+ mClockPositionAlgorithm.loadDimens(mResources);
+
mClockPosition = new KeyguardClockPositionAlgorithm.Result();
mHasCustomClock = HAS_CUSTOM_CLOCK;
mHasVisibleNotifs = HAS_VISIBLE_NOTIFS;
}
+ @After
+ public void tearDown() {
+ mStaticMockSession.finishMocking();
+ }
+
@Test
public void clockPositionTopOfScreenOnAOD() {
// GIVEN on AOD and both stack scroll and clock have 0 height
@@ -338,6 +372,155 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
+ @Test
+ public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN the clock + udfps are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the worst-case clock Y position is shifted only by 100 (not the full 200),
+ // so that it's at the same location as mUdfpsTop
+ assertThat(mClockPosition.clockY).isEqualTo(100);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesSpaceAboveClock() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD)
+ mKeyguardStatusBarHeaderHeight = 150;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in since the burn in offset > space above clock
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset (0 in this case)
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ @Test
+ public void clockPositionShiftsToAvoidUdfpsOnAOD_usesMaxBurnInOffset() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+
+ // GIVEN the bottom of the clock is beyond the top of UDFPS
+ mClockBottom = SCREEN_HEIGHT - 300;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+
+ // GIVEN it's AOD and the burn-in y value is only 25px (less than space above clock)
+ givenAOD();
+ int maxYBurnInOffset = 25;
+ givenMaxBurnInOffset(maxYBurnInOffset);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use the area above the clock for
+ // burn-in
+ assertThat(mClockPosition.clockY).isEqualTo(mKeyguardStatusBarHeaderHeight);
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts above mKeyguardStatusBarHeaderHeight
+ assertThat(mClockPosition.clockY).isEqualTo(
+ mKeyguardStatusBarHeaderHeight - 2 * maxYBurnInOffset);
+ }
+
+ @Test
+ public void clockPositionShiftsToMaximizeUdfpsBurnInMovement() {
+ // GIVEN a center aligned clock
+ mClockTopAligned = false;
+
+ // GIVEN there's 200px space at the top of the screen on LS (that's available to be used for
+ // burn-in on AOD) but 50px are taken up by the cutout
+ mKeyguardStatusBarHeaderHeight = 200;
+ mCutoutTopInset = 50;
+ int upperSpaceAvailable = mKeyguardStatusBarHeaderHeight - mCutoutTopInset;
+
+ // GIVEN the bottom of the clock and the top of UDFPS are 100px apart
+ mClockBottom = SCREEN_HEIGHT - 500;
+ mUdfpsTop = SCREEN_HEIGHT - 400;
+ float lowerSpaceAvailable = mUdfpsTop - mClockBottom;
+
+ // GIVEN it's AOD and the burn-in y value is 200
+ givenAOD();
+ givenMaxBurnInOffset(200);
+
+ // WHEN the clock position algorithm is run with the highest burn in offset
+ givenHighestBurnInOffset();
+ positionClock();
+
+ // THEN the algo should shift the clock up and use both the area above
+ // the clock and below the clock (vertically centered in its allowed area)
+ assertThat(mClockPosition.clockY).isEqualTo(
+ (int) (mCutoutTopInset + upperSpaceAvailable + lowerSpaceAvailable));
+
+ // WHEN the clock position algorithm is run with the lowest burn in offset
+ givenLowestBurnInOffset();
+ positionClock();
+
+ // THEN lowest case starts at mCutoutTopInset
+ assertThat(mClockPosition.clockY).isEqualTo(mCutoutTopInset);
+ }
+
+ private void givenHighestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).then(returnsFirstArg());
+ }
+
+ private void givenLowestBurnInOffset() {
+ when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(0);
+ }
+
+ private void givenMaxBurnInOffset(int offset) {
+ when(mResources.getDimensionPixelSize(R.dimen.burn_in_prevention_offset_y_large_clock))
+ .thenReturn(offset);
+ mClockPositionAlgorithm.loadDimens(mResources);
+ }
+
private void givenAOD() {
mPanelExpansion = 1.f;
mDark = 1.f;
@@ -348,13 +531,33 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
mDark = 0.f;
}
+ /**
+ * Setup and run the clock position algorithm.
+ *
+ * mClockPosition.clockY will contain the top y-coordinate for the clock position
+ */
private void positionClock() {
- mClockPositionAlgorithm.setup(EMPTY_MARGIN, SCREEN_HEIGHT, mNotificationStackHeight,
- mPanelExpansion, SCREEN_HEIGHT, mKeyguardStatusHeight,
- 0 /* userSwitchHeight */, 0 /* userSwitchPreferredY */,
- mHasCustomClock, mHasVisibleNotifs, mDark, ZERO_DRAG, false /* bypassEnabled */,
- 0 /* unlockedStackScrollerPadding */, mQsExpansion,
- mCutoutTopInset, mIsSplitShade);
+ mClockPositionAlgorithm.setup(
+ mKeyguardStatusBarHeaderHeight,
+ SCREEN_HEIGHT,
+ mNotificationStackHeight,
+ mPanelExpansion,
+ SCREEN_HEIGHT,
+ mKeyguardStatusHeight,
+ 0 /* userSwitchHeight */,
+ 0 /* userSwitchPreferredY */,
+ mHasCustomClock,
+ mHasVisibleNotifs,
+ mDark,
+ ZERO_DRAG,
+ false /* bypassEnabled */,
+ 0 /* unlockedStackScrollerPadding */,
+ mQsExpansion,
+ mCutoutTopInset,
+ mIsSplitShade,
+ mUdfpsTop,
+ mClockBottom,
+ mClockTopAligned);
mClockPositionAlgorithm.run(mClockPosition);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index d7661be21752..f2477886eb68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -141,7 +141,7 @@ import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotificationPanelViewTest extends SysuiTestCase {
+public class NotificationPanelViewControllerTest extends SysuiTestCase {
private static final int NOTIFICATION_SCRIM_TOP_PADDING_IN_SPLIT_SHADE = 50;
@@ -470,6 +470,12 @@ public class NotificationPanelViewTest extends SysuiTestCase {
}
@Test
+ public void testSetMinFraction() {
+ mNotificationPanelViewController.setMinFraction(0.5f);
+ verify(mNotificationShadeDepthController).setPanelPullDownMinFraction(eq(0.5f));
+ }
+
+ @Test
public void testSetDozing_notifiesNsslAndStateController() {
mNotificationPanelViewController.setDozing(true /* dozing */, false /* animate */,
null /* touch */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
index ddd78541d113..90b8a74d88be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
@@ -146,7 +146,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
mNotificationShadeWindowController.attach();
clearInvocations(mWindowManager);
- mNotificationShadeWindowController.setLightRevealScrimAmount(0f);
+ mNotificationShadeWindowController.setLightRevealScrimOpaque(true);
verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture());
assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) == 0).isTrue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index 6c1a3c90d83d..aafaebd959f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -16,6 +16,11 @@
package com.android.systemui.statusbar.phone;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,6 +32,7 @@ import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
+import com.android.keyguard.LockIconViewController;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
@@ -55,6 +61,8 @@ import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -90,6 +98,11 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+ @Mock private LockIconViewController mLockIconViewController;
+
+ @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
+ mInteractionEventHandlerCaptor;
+ private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
@Before
public void setUp() {
@@ -131,7 +144,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mNotificationPanelViewController,
mStatusBarViewFactory,
mNotificationStackScrollLayoutController,
- mStatusBarKeyguardViewManager);
+ mStatusBarKeyguardViewManager,
+ mLockIconViewController);
mController.setupExpandedStatusBar();
mController.setService(mStatusBar, mNotificationShadeWindowController);
mController.setDragDownHelper(mDragDownHelper);
@@ -147,4 +161,49 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
verify(mDragDownHelper).onTouchEvent(ev);
ev.recycle();
}
+
+ @Test
+ public void testInterceptTouchWhenShowingAltAuth() {
+ captureInteractionEventHandler();
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we should intercept touch
+ assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+ }
+
+ @Test
+ public void testNoInterceptTouch() {
+ captureInteractionEventHandler();
+
+ // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we shouldn't intercept touch
+ assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
+ }
+
+ @Test
+ public void testHandleTouchEventWhenShowingAltAuth() {
+ captureInteractionEventHandler();
+
+ // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+ when(mStatusBarStateController.isDozing()).thenReturn(false);
+ when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+ when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
+
+ // THEN we should handle the touch
+ assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
+ }
+
+ private void captureInteractionEventHandler() {
+ verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
+ mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
+
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 678b193073c2..47c8806be6d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -16,10 +16,13 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.ScrimController.KEYGUARD_SCRIM_ALPHA;
import static com.android.systemui.statusbar.phone.ScrimController.OPAQUE;
import static com.android.systemui.statusbar.phone.ScrimController.SEMI_TRANSPARENT;
import static com.android.systemui.statusbar.phone.ScrimController.TRANSPARENT;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -621,7 +624,7 @@ public class ScrimControllerTest extends SysuiTestCase {
assertScrimTinted(Map.of(
mScrimInFront, false,
- mScrimBehind, false,
+ mScrimBehind, true,
mScrimForBubble, true
));
@@ -702,7 +705,7 @@ public class ScrimControllerTest extends SysuiTestCase {
public void qsExpansion_half_clippingQs() {
reset(mScrimBehind);
mScrimController.setClipsQsScrim(true);
- mScrimController.setQsPosition(0.5f, 999 /* value doesn't matter */);
+ mScrimController.setQsPosition(0.25f, 999 /* value doesn't matter */);
finishAnimationsImmediately();
assertScrimAlpha(Map.of(
@@ -734,20 +737,13 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
- public void transitionToUnlockedFromAod() {
- // Simulate unlock with fingerprint
- mScrimController.transitionTo(ScrimState.AOD);
+ public void transitionToUnlockedFromOff() {
+ // Simulate unlock with fingerprint without AOD
+ mScrimController.transitionTo(ScrimState.OFF);
mScrimController.setPanelExpansion(0f);
finishAnimationsImmediately();
mScrimController.transitionTo(ScrimState.UNLOCKED);
- // Immediately tinted black after the transition starts
- assertScrimTinted(Map.of(
- mScrimInFront, true,
- mScrimBehind, true,
- mScrimForBubble, true
- ));
-
finishAnimationsImmediately();
// All scrims should be transparent at the end of fade transition.
@@ -765,6 +761,28 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void transitionToUnlockedFromAod() {
+ // Simulate unlock with fingerprint
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.setPanelExpansion(0f);
+ finishAnimationsImmediately();
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+
+ finishAnimationsImmediately();
+
+ // All scrims should be transparent at the end of fade transition.
+ assertScrimAlpha(Map.of(
+ mScrimInFront, TRANSPARENT,
+ mScrimBehind, TRANSPARENT));
+
+ // Make sure at the very end of the animation, we're reset to transparent
+ assertScrimTinted(Map.of(
+ mScrimInFront, false,
+ mScrimBehind, true
+ ));
+ }
+
+ @Test
public void scrimBlanksBeforeLeavingAod() {
// Simulate unlock with fingerprint
mScrimController.transitionTo(ScrimState.AOD);
@@ -1081,6 +1099,26 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void testDoesntAnimate_whenUnlocking() {
+ // LightRevealScrim will animate the transition, we should only hide the keyguard scrims.
+ ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ ScrimState.UNLOCKED.prepare(ScrimState.PULSING);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse();
+
+ ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ ScrimState.UNLOCKED.prepare(ScrimState.AOD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isFalse();
+
+ // LightRevealScrim doesn't animate when AOD is disabled. We need to use the legacy anim.
+ ScrimState.UNLOCKED.prepare(ScrimState.KEYGUARD);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ ScrimState.UNLOCKED.prepare(ScrimState.OFF);
+ assertThat(ScrimState.UNLOCKED.getAnimateChange()).isTrue();
+ }
+
+ @Test
public void testScrimsVisible_whenShadeVisible_clippingQs() {
mScrimController.setClipsQsScrim(true);
mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -1098,7 +1136,7 @@ public class ScrimControllerTest extends SysuiTestCase {
@Test
public void testScrimsVisible_whenShadeVisibleOnLockscreen() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
- mScrimController.setQsPosition(0.5f, 300);
+ mScrimController.setQsPosition(0.25f, 300);
assertScrimAlpha(Map.of(
mScrimBehind, SEMI_TRANSPARENT,
@@ -1138,6 +1176,21 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void testNotificationTransparency_unnocclusion() {
+ mScrimController.transitionTo(ScrimState.KEYGUARD);
+ mScrimController.setUnocclusionAnimationRunning(true);
+
+ assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA,
+ /* expansion */ 0.0f);
+ assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ KEYGUARD_SCRIM_ALPHA,
+ /* expansion */ 1.0f);
+
+ // Verify normal behavior after
+ mScrimController.setUnocclusionAnimationRunning(false);
+ assertAlphaAfterExpansion(mNotificationsScrim, /* alpha */ 0.2f, /* expansion */ 0.4f);
+ }
+
+ @Test
public void testNotificationTransparency_inKeyguardState() {
mScrimController.transitionTo(ScrimState.KEYGUARD);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index c39a9061f95d..2b569f31e916 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -299,18 +299,4 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
verify(mBouncer).updateKeyguardPosition(1.0f);
}
-
- @Test
- public void testNavBarHiddenWhenSleepAnimationStarts() {
- mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- assertTrue(mStatusBarKeyguardViewManager.isNavBarVisible());
-
- // Verify that the nav bar is hidden when the screen off animation starts
- doReturn(true).when(mUnlockedScreenOffAnimationController).isScreenOffAnimationPlaying();
- mWakefulnessLifecycle.dispatchFinishedGoingToSleep();
- assertFalse(mStatusBarKeyguardViewManager.isNavBarVisible());
-
- mWakefulnessLifecycle.dispatchFinishedWakingUp();
- assertTrue(mStatusBarKeyguardViewManager.isNavBarVisible());
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 2c2833a864b9..6051c5615f8d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -812,6 +812,58 @@ public class StatusBarTest extends SysuiTestCase {
}
@Test
+ public void testTransitionLaunch_goesToUnlocked() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ mStatusBar.showKeyguardImpl();
+
+ // Starting a pulse should change the scrim controller to the pulsing state
+ when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
+ when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
+ }
+
+ @Test
+ public void testSetExpansionAffectsAlpha_onlyWhenHidingKeyguard() {
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
+
+ clearInvocations(mScrimController);
+ when(mBiometricUnlockController.isBiometricUnlock()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(true));
+
+ clearInvocations(mScrimController);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ reset(mKeyguardStateController);
+ when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+
+ clearInvocations(mScrimController);
+ reset(mKeyguardStateController);
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).setExpansionAffectsAlpha(eq(false));
+ }
+
+ @Test
+ public void testTransitionLaunch_noPreview_doesntGoUnlocked() {
+ mStatusBar.setBarStateForTest(StatusBarState.KEYGUARD);
+ mStatusBar.showKeyguardImpl();
+
+ // Starting a pulse should change the scrim controller to the pulsing state
+ when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
+ when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
+ mStatusBar.updateScrimController();
+ verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
+ }
+
+ @Test
public void testSetOccluded_propagatesToScrimController() {
mStatusBar.setOccluded(true);
verify(mScrimController).setKeyguardOccluded(eq(true));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index d26db4c69ece..b7c4d0a7d223 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -221,6 +221,18 @@ class OngoingCallControllerTest : SysuiTestCase() {
verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
}
+ /** Regression test for b/201097913. */
+ @Test
+ fun onEntryCleanUp_callNotifAddedThenRemoved_listenerNotified() {
+ val ongoingCallNotifEntry = createOngoingCallNotifEntry()
+ notifCollectionListener.onEntryAdded(ongoingCallNotifEntry)
+ reset(mockOngoingCallListener)
+
+ notifCollectionListener.onEntryCleanUp(ongoingCallNotifEntry)
+
+ verify(mockOngoingCallListener).onOngoingCallStateChanged(anyBoolean())
+ }
+
/** Regression test for b/188491504. */
@Test
fun onEntryRemoved_removedNotifHasSameKeyAsAddedNotif_listenerNotified() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 57198dbe18bb..4a5770d12239 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -238,7 +238,7 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
mNetworkController.setNoNetworksAvailable(false);
setWifiStateForVcn(true, testSsid);
setWifiLevelForVcn(0);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
+ verifyLastMobileDataIndicatorsForVcn(true, 0, TelephonyIcons.ICON_CWF, false);
mNetworkController.setNoNetworksAvailable(true);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
@@ -246,11 +246,11 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, true, true, mVcnTransportInfo);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
+ verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, true);
setConnectivityViaCallbackInNetworkControllerForVcn(
NetworkCapabilities.TRANSPORT_CELLULAR, false, true, mVcnTransportInfo);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ verifyLastMobileDataIndicatorsForVcn(true, testLevel, TelephonyIcons.ICON_CWF, false);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
new file mode 100644
index 000000000000..871a48c503be
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Date
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class VariableDateViewControllerTest : SysuiTestCase() {
+
+ companion object {
+ private const val TIME_STAMP = 1_500_000_000_000
+ private const val LONG_PATTERN = "EEEMMMd"
+ private const val SHORT_PATTERN = "MMMd"
+ private const val CHAR_WIDTH = 10f
+ }
+
+ @Mock
+ private lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var view: VariableDateView
+ @Captor
+ private lateinit var onMeasureListenerCaptor: ArgumentCaptor<VariableDateView.OnMeasureListener>
+
+ private var lastText: String? = null
+
+ private lateinit var systemClock: FakeSystemClock
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var testableHandler: Handler
+ private lateinit var controller: VariableDateViewController
+
+ private lateinit var longText: String
+ private lateinit var shortText: String
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ testableHandler = Handler(testableLooper.looper)
+
+ systemClock = FakeSystemClock()
+ systemClock.setCurrentTimeMillis(TIME_STAMP)
+
+ `when`(view.longerPattern).thenReturn(LONG_PATTERN)
+ `when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
+ `when`(view.handler).thenReturn(testableHandler)
+
+ `when`(view.setText(anyString())).thenAnswer {
+ lastText = it.arguments[0] as? String
+ Unit
+ }
+ `when`(view.isAttachedToWindow).thenReturn(true)
+
+ val date = Date(TIME_STAMP)
+ longText = getTextForFormat(date, getFormatFromPattern(LONG_PATTERN))
+ shortText = getTextForFormat(date, getFormatFromPattern(SHORT_PATTERN))
+
+ // Assume some sizes for the text, the controller doesn't need to know if these sizes are
+ // the true ones
+ `when`(view.getDesiredWidthForText(any())).thenAnswer {
+ getTextLength(it.arguments[0] as CharSequence)
+ }
+
+ controller = VariableDateViewController(
+ systemClock,
+ broadcastDispatcher,
+ testableHandler,
+ view
+ )
+
+ controller.init()
+ testableLooper.processAllMessages()
+
+ verify(view).onAttach(capture(onMeasureListenerCaptor))
+ }
+
+ @Test
+ fun testViewStartsWithLongText() {
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testListenerNotNull() {
+ assertThat(onMeasureListenerCaptor.value).isNotNull()
+ }
+
+ @Test
+ fun testLotsOfSpaceUseLongText() {
+ onMeasureListenerCaptor.value.onMeasureAction(10000)
+
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testSmallSpaceUseEmpty() {
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEmpty()
+ }
+
+ @Test
+ fun testSpaceInBetweenUseShortText() {
+ val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+
+ onMeasureListenerCaptor.value.onMeasureAction(average)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEqualTo(shortText)
+ }
+
+ @Test
+ fun testSwitchBackToLonger() {
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+
+ onMeasureListenerCaptor.value.onMeasureAction(10000)
+ testableLooper.processAllMessages()
+
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ @Test
+ fun testNoSwitchingWhenFrozen() {
+ `when`(view.freezeSwitching).thenReturn(true)
+
+ val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
+ onMeasureListenerCaptor.value.onMeasureAction(average)
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+
+ onMeasureListenerCaptor.value.onMeasureAction(1)
+ testableLooper.processAllMessages()
+ assertThat(lastText).isEqualTo(longText)
+ }
+
+ private fun getTextLength(text: CharSequence): Float {
+ return text.length * CHAR_WIDTH
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
index 9c47f19b20c8..2c461ae1b598 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java
@@ -103,7 +103,8 @@ public class ThemeOverlayApplierTest extends SysuiTestCase {
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- mManager = new ThemeOverlayApplier(mOverlayManager, MoreExecutors.directExecutor(),
+ mManager = new ThemeOverlayApplier(mOverlayManager,
+ MoreExecutors.directExecutor(), MoreExecutors.directExecutor(),
LAUNCHER_PACKAGE, THEMEPICKER_PACKAGE, mDumpManager) {
@Override
protected OverlayManagerTransaction.Builder getTransactionBuilder() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
new file mode 100644
index 000000000000..eebcbe63d004
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.usb
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.hardware.usb.IUsbSerialReader
+import android.hardware.usb.UsbAccessory
+import android.hardware.usb.UsbManager
+import android.testing.AndroidTestingRunner
+import android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
+import androidx.test.filters.SmallTest
+import androidx.test.rule.ActivityTestRule
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.lang.Exception
+
+/**
+ * UsbPermissionActivityTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UsbPermissionActivityTest : SysuiTestCase() {
+
+ class UsbPermissionActivityTestable : UsbPermissionActivity()
+
+ @Rule
+ @JvmField
+ var activityRule = ActivityTestRule<UsbPermissionActivityTestable>(
+ UsbPermissionActivityTestable::class.java, false, false)
+
+ private val activityIntent = Intent(mContext, UsbPermissionActivityTestable::class.java)
+ .apply {
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ putExtra(UsbManager.EXTRA_PACKAGE, "com.android.systemui")
+ putExtra(Intent.EXTRA_INTENT, PendingIntent.getBroadcast(
+ mContext,
+ 334,
+ Intent("NO_ACTION"),
+ PendingIntent.FLAG_MUTABLE))
+ putExtra(UsbManager.EXTRA_ACCESSORY, UsbAccessory(
+ "manufacturer",
+ "model",
+ "description",
+ "version",
+ "uri",
+ object : IUsbSerialReader.Stub() {
+ override fun getSerial(packageName: String): String {
+ return "serial"
+ }
+ }))
+ }
+
+ @Before
+ fun setUp() {
+ activityRule.launchActivity(activityIntent)
+ }
+
+ @After
+ fun tearDown() {
+ activityRule.finishActivity()
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun testHideNonSystemOverlay() {
+ assertThat(activityRule.activity.window.attributes.privateFlags and
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ .isEqualTo(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
index a34c5986f36c..0e9d96c61e54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/sensors/ProximitySensorDualTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.util.sensors;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -338,30 +340,25 @@ public class ProximitySensorDualTest extends SysuiTestCase {
@Test
public void testSecondaryCancelsSecondary() {
TestableListener listener = new TestableListener();
- ThresholdSensor.Listener cancelingListener = new ThresholdSensor.Listener() {
- @Override
- public void onThresholdCrossed(ThresholdSensor.ThresholdSensorEvent event) {
- mProximitySensor.pause();
- }
- };
+ ThresholdSensor.Listener cancelingListener = event -> mProximitySensor.pause();
mProximitySensor.register(listener);
mProximitySensor.register(cancelingListener);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorPrimary.triggerEvent(true, 0);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(true, 0);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
// The proximity sensor should now be canceled. Advancing the clock should do nothing.
- assertEquals(0, mFakeExecutor.numPending());
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(false, 1);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
mProximitySensor.unregister(listener);
}
@@ -372,33 +369,66 @@ public class ProximitySensorDualTest extends SysuiTestCase {
TestableListener listener = new TestableListener();
- // WE immediately register the secondary sensor.
+ // We immediately register the secondary sensor.
mProximitySensor.register(listener);
- assertFalse(mThresholdSensorPrimary.isPaused());
- assertFalse(mThresholdSensorSecondary.isPaused());
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorPrimary.triggerEvent(true, 0);
- assertNull(listener.mLastEvent);
- assertEquals(0, listener.mCallCount);
+ assertThat(listener.mLastEvent).isNull();
+ assertThat(listener.mCallCount).isEqualTo(0);
mThresholdSensorSecondary.triggerEvent(true, 0);
- assertTrue(listener.mLastEvent.getBelow());
- assertEquals(1, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isTrue();
+ assertThat(listener.mCallCount).isEqualTo(1);
// The secondary sensor should now remain resumed indefinitely.
- assertFalse(mThresholdSensorSecondary.isPaused());
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
mThresholdSensorSecondary.triggerEvent(false, 1);
- assertFalse(listener.mLastEvent.getBelow());
- assertEquals(2, listener.mCallCount);
+ assertThat(listener.mLastEvent.getBelow()).isFalse();
+ assertThat(listener.mCallCount).isEqualTo(2);
// The secondary is still running, and not polling with the executor.
- assertFalse(mThresholdSensorSecondary.isPaused());
- assertEquals(0, mFakeExecutor.numPending());
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
mProximitySensor.unregister(listener);
}
+ @Test
+ public void testSecondaryPausesPrimary() {
+ TestableListener listener = new TestableListener();
+
+ mProximitySensor.register(listener);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+ assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+ mProximitySensor.setSecondarySafe(true);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+ }
+
+ @Test
+ public void testSecondaryResumesPrimary() {
+ mProximitySensor.setSecondarySafe(true);
+
+ TestableListener listener = new TestableListener();
+ mProximitySensor.register(listener);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isTrue();
+ assertThat(mThresholdSensorSecondary.isPaused()).isFalse();
+
+ mProximitySensor.setSecondarySafe(false);
+
+ assertThat(mThresholdSensorPrimary.isPaused()).isFalse();
+ assertThat(mThresholdSensorSecondary.isPaused()).isTrue();
+
+
+ }
+
private static class TestableListener implements ThresholdSensor.Listener {
ThresholdSensor.ThresholdSensorEvent mLastEvent;
int mCallCount = 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
index 69764227040b..7bb26748a9d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettings.java
@@ -31,6 +31,7 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti
private final Map<SettingsKey, String> mValues = new HashMap<>();
private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
new HashMap<>();
+ private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
@@ -55,9 +56,15 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti
@Override
public void registerContentObserverForUser(Uri uri, boolean notifyDescendents,
ContentObserver settingsObserver, int userHandle) {
- SettingsKey key = new SettingsKey(userHandle, uri.toString());
- mContentObservers.putIfAbsent(key, new ArrayList<>());
- List<ContentObserver> observers = mContentObservers.get(key);
+ List<ContentObserver> observers;
+ if (userHandle == UserHandle.USER_ALL) {
+ mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
+ observers = mContentObserversAllUsers.get(uri.toString());
+ } else {
+ SettingsKey key = new SettingsKey(userHandle, uri.toString());
+ mContentObservers.putIfAbsent(key, new ArrayList<>());
+ observers = mContentObservers.get(key);
+ }
observers.add(settingsObserver);
}
@@ -67,6 +74,10 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti
List<ContentObserver> observers = mContentObservers.get(key);
observers.remove(settingsObserver);
}
+ for (String key : mContentObserversAllUsers.keySet()) {
+ List<ContentObserver> observers = mContentObserversAllUsers.get(key);
+ observers.remove(settingsObserver);
+ }
}
@Override
@@ -114,6 +125,10 @@ public class FakeSettings implements SecureSettings, GlobalSettings, SystemSetti
for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
observer.dispatchChange(false, List.of(uri), userHandle);
}
+ for (ContentObserver observer :
+ mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
+ observer.dispatchChange(false, List.of(uri), userHandle);
+ }
return true;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
index 0d560f237a07..34cae58d30e1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java
@@ -25,6 +25,7 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.database.ContentObserver;
+import android.os.UserHandle;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
@@ -89,6 +90,16 @@ public class FakeSettingsTest extends SysuiTestCase {
}
@Test
+ public void testRegisterContentObserverAllUsers() {
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+
+ mFakeSettings.putString("cat", "hat");
+
+ verify(mContentObserver).dispatchChange(anyBoolean(), any(Collection.class), anyInt());
+ }
+
+ @Test
public void testUnregisterContentObserver() {
mFakeSettings.registerContentObserver("cat", mContentObserver);
mFakeSettings.unregisterContentObserver(mContentObserver);
@@ -98,4 +109,16 @@ public class FakeSettingsTest extends SysuiTestCase {
verify(mContentObserver, never()).dispatchChange(
anyBoolean(), any(Collection.class), anyInt());
}
+
+ @Test
+ public void testUnregisterContentObserverAllUsers() {
+ mFakeSettings.registerContentObserverForUser(
+ mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL);
+ mFakeSettings.unregisterContentObserver(mContentObserver);
+
+ mFakeSettings.putString("cat", "hat");
+
+ verify(mContentObserver, never()).dispatchChange(
+ anyBoolean(), any(Collection.class), anyInt());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index be110242a3eb..4f9cb35db1a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -51,6 +51,11 @@ public class FakeRotationLockController extends BaseLeakChecker<RotationLockCont
}
@Override
+ public boolean isCameraRotationEnabled() {
+ return false;
+ }
+
+ @Override
public void setRotationLockedAtAngle(boolean locked, int rotation) {
}
diff --git a/packages/services/CameraExtensionsProxy/AndroidManifest.xml b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
index d35689413749..ef1d581d717e 100644
--- a/packages/services/CameraExtensionsProxy/AndroidManifest.xml
+++ b/packages/services/CameraExtensionsProxy/AndroidManifest.xml
@@ -8,6 +8,7 @@
android:directBootAware="true">
<service android:name=".CameraExtensionsProxyService"
+ android:visibleToInstantApps="true"
android:exported="true">
</service>
<uses-library android:name="androidx.camera.extensions.impl" android:required="false" />
diff --git a/proto/src/metrics_constants/OWNERS b/proto/src/metrics_constants/OWNERS
index 7009282b66e1..274d0d6323cf 100644
--- a/proto/src/metrics_constants/OWNERS
+++ b/proto/src/metrics_constants/OWNERS
@@ -1,4 +1,3 @@
cwren@android.com
yanglu@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/OWNERS b/services/OWNERS
index a08331996556..b3bf5dcd2292 100644
--- a/services/OWNERS
+++ b/services/OWNERS
@@ -3,5 +3,5 @@ per-file Android.bp = file:platform/build/soong:/OWNERS
# art-team@ manages the system server profile
per-file art-profile* = calin@google.com, ngeoffray@google.com, vmarko@google.com
-per-file java/com/android/server/* = toddke@google.com,patb@google.com
+per-file java/com/android/server/* = patb@google.com
per-file tests/servicestests/src/com/android/server/systemconfig/* = patb@google.com
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 469f465d7c9a..48f5b51a8404 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -488,7 +488,7 @@ public class BootReceiver extends BroadcastReceiver {
} else {
final String headers = getBootHeadersToLogAndUpdate();
addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
- TAG_TOMBSTONE);
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 483250ad2257..68fd0c12bea8 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -92,6 +92,8 @@ option java_package com.android.server
27533 notification_autogrouped (key|3)
# notification was removed from an autogroup
275534 notification_unautogrouped (key|3)
+# when a notification is adjusted via assistant
+27535 notification_adjusted (key|3),(adjustment_type|3),(new_value|3)
# ---------------------------
# Watchdog.java
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c3543e7ba368..c1c9fbb121a8 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -327,18 +327,23 @@ public class RescueParty {
}
}
- private static int getMaxRescueLevel() {
- return SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)
- ? LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS : LEVEL_FACTORY_RESET;
+ private static int getMaxRescueLevel(boolean mayPerformFactoryReset) {
+ if (!mayPerformFactoryReset
+ || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
+ return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
+ }
+ return LEVEL_FACTORY_RESET;
}
/**
* Get the rescue level to perform if this is the n-th attempt at mitigating failure.
*
* @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
+ * @param mayPerformFactoryReset: whether or not a factory reset may be performed for the given
+ * failure.
* @return the rescue level for the n-th mitigation attempt.
*/
- private static int getRescueLevel(int mitigationCount) {
+ private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
if (mitigationCount == 1) {
return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
} else if (mitigationCount == 2) {
@@ -346,9 +351,9 @@ public class RescueParty {
} else if (mitigationCount == 3) {
return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
} else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(), LEVEL_WARM_REBOOT);
+ return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
} else if (mitigationCount >= 5) {
- return Math.min(getMaxRescueLevel(), LEVEL_FACTORY_RESET);
+ return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
} else {
Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
return LEVEL_NONE;
@@ -614,7 +619,8 @@ public class RescueParty {
@FailureReasons int failureReason, int mitigationCount) {
if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
+ mayPerformFactoryReset(failedPackage)));
} else {
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
@@ -628,7 +634,8 @@ public class RescueParty {
}
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
- final int level = getRescueLevel(mitigationCount);
+ final int level = getRescueLevel(mitigationCount,
+ mayPerformFactoryReset(failedPackage));
executeRescueLevel(mContext,
failedPackage == null ? null : failedPackage.getPackageName(), level);
return true;
@@ -653,12 +660,7 @@ public class RescueParty {
} catch (PackageManager.NameNotFoundException ignore) {
}
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
+ return isPersistentSystemApp(packageName);
}
@Override
@@ -666,7 +668,7 @@ public class RescueParty {
if (isDisabled()) {
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
+ return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
}
@Override
@@ -674,7 +676,8 @@ public class RescueParty {
if (isDisabled()) {
return false;
}
- executeRescueLevel(mContext, /*failedPackage=*/ null, getRescueLevel(mitigationCount));
+ executeRescueLevel(mContext, /*failedPackage=*/ null,
+ getRescueLevel(mitigationCount, true));
return true;
}
@@ -683,6 +686,29 @@ public class RescueParty {
return NAME;
}
+ /**
+ * Returns {@code true} if the failing package is non-null and performing a reboot or
+ * prompting a factory reset is an acceptable mitigation strategy for the package's
+ * failure, {@code false} otherwise.
+ */
+ private boolean mayPerformFactoryReset(@Nullable VersionedPackage failingPackage) {
+ if (failingPackage == null) {
+ return false;
+ }
+
+ return isPersistentSystemApp(failingPackage.getPackageName());
+ }
+
+ private boolean isPersistentSystemApp(@NonNull String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
private synchronized void recordDeviceConfigAccess(@NonNull String callingPackage,
@NonNull String namespace) {
// Record it in calling packages to namespace map
diff --git a/services/core/java/com/android/server/SensorPrivacyService.java b/services/core/java/com/android/server/SensorPrivacyService.java
index 91b2440f71fe..06a78c864cac 100644
--- a/services/core/java/com/android/server/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/SensorPrivacyService.java
@@ -39,6 +39,7 @@ import static android.hardware.SensorPrivacyManager.Sources.OTHER;
import static android.hardware.SensorPrivacyManager.Sources.QS_TILE;
import static android.hardware.SensorPrivacyManager.Sources.SETTINGS;
import static android.hardware.SensorPrivacyManager.Sources.SHELL;
+import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.SensorPrivacyIndividualEnabledSensorProto.UNKNOWN;
@@ -195,7 +196,7 @@ public final class SensorPrivacyService extends SystemService {
private EmergencyCallHelper mEmergencyCallHelper;
private KeyguardManager mKeyguardManager;
- private int mCurrentUser = -1;
+ private int mCurrentUser = USER_NULL;
public SensorPrivacyService(Context context) {
super(context);
@@ -228,9 +229,9 @@ public final class SensorPrivacyService extends SystemService {
@Override
public void onUserStarting(TargetUser user) {
- if (mCurrentUser == -1) {
+ if (mCurrentUser == USER_NULL) {
mCurrentUser = user.getUserIdentifier();
- mSensorPrivacyServiceImpl.userSwitching(-1, user.getUserIdentifier());
+ mSensorPrivacyServiceImpl.userSwitching(USER_NULL, user.getUserIdentifier());
}
}
@@ -420,9 +421,12 @@ public final class SensorPrivacyService extends SystemService {
}
synchronized (mLock) {
- if (mSuppressReminders.containsKey(new Pair<>(sensor, user))) {
+ UserHandle parentUser = UserHandle.of(mUserManagerInternal
+ .getProfileParentId(user.getIdentifier()));
+ if (mSuppressReminders.containsKey(new Pair<>(sensor, parentUser))) {
Log.d(TAG,
- "Suppressed sensor privacy reminder for " + packageName + "/" + user);
+ "Suppressed sensor privacy reminder for " + packageName + "/"
+ + parentUser);
return;
}
}
@@ -708,6 +712,16 @@ public final class SensorPrivacyService extends SystemService {
@Override
public void setIndividualSensorPrivacy(@UserIdInt int userId,
@SensorPrivacyManager.Sources.Source int source, int sensor, boolean enable) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " setIndividualSensorPrivacy("
+ + "userId=" + userId
+ + " source=" + source
+ + " sensor=" + sensor
+ + " enable=" + enable
+ + ")");
+ }
enforceManageSensorPrivacyPermission();
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
@@ -892,6 +906,14 @@ public final class SensorPrivacyService extends SystemService {
@Override
public boolean isIndividualSensorPrivacyEnabled(@UserIdInt int userId, int sensor) {
+ if (DEBUG) {
+ Log.d(TAG, "callingUid=" + Binder.getCallingUid()
+ + " callingPid=" + Binder.getCallingPid()
+ + " isIndividualSensorPrivacyEnabled("
+ + "userId=" + userId
+ + " sensor=" + sensor
+ + ")");
+ }
enforceObserveSensorPrivacyPermission();
if (userId == UserHandle.USER_CURRENT) {
userId = mCurrentUser;
@@ -1273,13 +1295,13 @@ public final class SensorPrivacyService extends SystemService {
micState = isIndividualSensorPrivacyEnabledLocked(to, MICROPHONE);
camState = isIndividualSensorPrivacyEnabledLocked(to, CAMERA);
}
- if (prevMicState != micState) {
+ if (from == USER_NULL || prevMicState != micState) {
mHandler.onUserGlobalSensorPrivacyChanged(MICROPHONE, micState);
setGlobalRestriction(MICROPHONE, micState);
}
- if (prevCamState != camState) {
+ if (from == USER_NULL || prevCamState != camState) {
mHandler.onUserGlobalSensorPrivacyChanged(CAMERA, camState);
- setGlobalRestriction(CAMERA, micState);
+ setGlobalRestriction(CAMERA, camState);
}
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 69c29269b7a9..8727932a87f7 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -221,6 +221,9 @@ class StorageManagerService extends IStorageManager.Stub
@GuardedBy("mLock")
private final Set<Integer> mFuseMountedUser = new ArraySet<>();
+ @GuardedBy("mLock")
+ private final Set<Integer> mCeStoragePreparedUsers = new ArraySet<>();
+
public static class Lifecycle extends SystemService {
private StorageManagerService mStorageManagerService;
@@ -4864,5 +4867,19 @@ class StorageManagerService extends IStorageManager.Stub
}
return primaryVolumeIds;
}
+
+ @Override
+ public void markCeStoragePrepared(int userId) {
+ synchronized (mLock) {
+ mCeStoragePreparedUsers.add(userId);
+ }
+ }
+
+ @Override
+ public boolean isCeStoragePrepared(int userId) {
+ synchronized (mLock) {
+ return mCeStoragePreparedUsers.contains(userId);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 5ed6c86bdc18..4775127389d3 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1856,11 +1856,12 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
try {
if (!mConfigurationProvider.isDisplayInfoNrAdvancedSupported(
r.callingPackage, Binder.getCallingUserHandle())) {
- telephonyDisplayInfo =
+ r.callback.onDisplayInfoChanged(
getBackwardCompatibleTelephonyDisplayInfo(
- telephonyDisplayInfo);
+ telephonyDisplayInfo));
+ } else {
+ r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
}
- r.callback.onDisplayInfoChanged(telephonyDisplayInfo);
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3f816c88ae51..c9112578705e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7743,6 +7743,17 @@ public class ActivityManagerService extends IActivityManager.Stub
} else {
killUid(UserHandle.getAppId(uid), UserHandle.getUserId(uid),
"Too many Binders sent to SYSTEM");
+ // We need to run a GC here, because killing the processes involved
+ // actually isn't guaranteed to free up the proxies; in fact, if the
+ // GC doesn't run for a long time, we may even exceed the global
+ // proxy limit for a process (20000), resulting in system_server itself
+ // being killed.
+ // Note that the GC here might not actually clean up all the proxies,
+ // because the binder reference decrements will come in asynchronously;
+ // but if new processes belonging to the UID keep adding proxies, we
+ // will get another callback here, and run the GC again - this time
+ // cleaning up the old proxies.
+ VMRuntime.getRuntime().requestConcurrentGC();
}
}, mHandler);
t.traceEnd(); // setBinderProxies
@@ -15413,12 +15424,14 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void updateDeviceIdleTempAllowlist(int[] appids, int changingUid, boolean adding,
- long durationMs, @TempAllowListType int type, @ReasonCode int reasonCode,
- @Nullable String reason, int callingUid) {
+ public void updateDeviceIdleTempAllowlist(@Nullable int[] appids, int changingUid,
+ boolean adding, long durationMs, @TempAllowListType int type,
+ @ReasonCode int reasonCode, @Nullable String reason, int callingUid) {
synchronized (ActivityManagerService.this) {
synchronized (mProcLock) {
- mDeviceIdleTempAllowlist = appids;
+ if (appids != null) {
+ mDeviceIdleTempAllowlist = appids;
+ }
if (adding) {
if (type == TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED) {
// Note, the device idle temp-allowlist are by app-ids, but here
@@ -15428,12 +15441,7 @@ public class ActivityManagerService extends IActivityManager.Stub
callingUid));
}
} else {
- // Note in the removing case, we need to remove all the UIDs matching
- // the appId, because DeviceIdle's temp-allowlist are based on AppIds,
- // not UIDs.
- // For eacmple, "cmd deviceidle tempallowlist -r PACKAGE" will
- // not only remove this app for user 0, but for all users.
- mFgsStartTempAllowList.removeAppId(UserHandle.getAppId(changingUid));
+ mFgsStartTempAllowList.removeUid(changingUid);
}
setAppIdTempAllowlistStateLSP(changingUid, adding);
}
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index 0c633cacda92..7ba032f683b8 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -478,7 +478,7 @@ class BatteryExternalStatsWorker implements BatteryStatsImpl.ExternalStatsSync {
for (int uid : uidsToRemove) {
FrameworkStatsLog.write(FrameworkStatsLog.ISOLATED_UID_CHANGED, -1, uid,
FrameworkStatsLog.ISOLATED_UID_CHANGED__EVENT__REMOVED);
- mStats.removeIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
+ mStats.maybeRemoveIsolatedUidLocked(uid, SystemClock.elapsedRealtime(),
SystemClock.uptimeMillis());
}
mStats.clearPendingRemovedUids();
diff --git a/services/core/java/com/android/server/am/CacheOomRanker.java b/services/core/java/com/android/server/am/CacheOomRanker.java
index 50278fd81bb2..e6ffcfcd3510 100644
--- a/services/core/java/com/android/server/am/CacheOomRanker.java
+++ b/services/core/java/com/android/server/am/CacheOomRanker.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import android.os.Process;
+import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.util.Slog;
@@ -38,21 +40,40 @@ public class CacheOomRanker {
private static final boolean DEFAULT_USE_OOM_RE_RANKING = false;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK = "oom_re_ranking_number_to_re_rank";
- @VisibleForTesting static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final int DEFAULT_OOM_RE_RANKING_NUMBER_TO_RE_RANK = 8;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS =
+ "oom_re_ranking_preserve_top_n_apps";
+ @VisibleForTesting
+ static final int DEFAULT_PRESERVE_TOP_N_APPS = 3;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_USE_FREQUENT_RSS = "oom_re_ranking_rss_use_frequent_rss";
+ @VisibleForTesting
+ static final boolean DEFAULT_USE_FREQUENT_RSS = true;
+ @VisibleForTesting
+ static final String KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS = "oom_re_ranking_rss_update_rate_ms";
+ @VisibleForTesting
+ static final long DEFAULT_RSS_UPDATE_RATE_MS = 10_000; // 10 seconds
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_LRU_WEIGHT = "oom_re_ranking_lru_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_LRU_WEIGHT = 0.35f;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_USES_WEIGHT = "oom_re_ranking_uses_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_USES_WEIGHT = 0.5f;
@VisibleForTesting
static final String KEY_OOM_RE_RANKING_RSS_WEIGHT = "oom_re_ranking_rss_weight";
- @VisibleForTesting static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
+ @VisibleForTesting
+ static final float DEFAULT_OOM_RE_RANKING_RSS_WEIGHT = 0.15f;
private static final Comparator<RankedProcessRecord> SCORED_PROCESS_RECORD_COMPARATOR =
new ScoreComparator();
private static final Comparator<RankedProcessRecord> CACHE_USE_COMPARATOR =
new CacheUseComparator();
+ private static final Comparator<RankedProcessRecord> RSS_COMPARATOR =
+ new RssComparator();
private static final Comparator<RankedProcessRecord> LAST_RSS_COMPARATOR =
new LastRssComparator();
private static final Comparator<RankedProcessRecord> LAST_ACTIVITY_TIME_COMPARATOR =
@@ -61,20 +82,33 @@ public class CacheOomRanker {
private final Object mPhenotypeFlagLock = new Object();
private final ActivityManagerService mService;
+ private final ProcessDependencies mProcessDependencies;
private final ActivityManagerGlobalLock mProcLock;
private final Object mProfilerLock;
@GuardedBy("mPhenotypeFlagLock")
private boolean mUseOomReRanking = DEFAULT_USE_OOM_RE_RANKING;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ int mPreserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ boolean mUseFrequentRss = DEFAULT_USE_FREQUENT_RSS;
+ @GuardedBy("mPhenotypeFlagLock")
+ @VisibleForTesting
+ long mRssUpdateRateMs = DEFAULT_RSS_UPDATE_RATE_MS;
// Weight to apply to the LRU ordering.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
+ @VisibleForTesting
+ float mLruWeight = DEFAULT_OOM_RE_RANKING_LRU_WEIGHT;
// Weight to apply to the ordering by number of times the process has been added to the cache.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
+ @VisibleForTesting
+ float mUsesWeight = DEFAULT_OOM_RE_RANKING_USES_WEIGHT;
// Weight to apply to the ordering by RSS used by the processes.
@GuardedBy("mPhenotypeFlagLock")
- @VisibleForTesting float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
+ @VisibleForTesting
+ float mRssWeight = DEFAULT_OOM_RE_RANKING_RSS_WEIGHT;
// Positions to replace in the lru list.
@GuardedBy("mPhenotypeFlagLock")
@@ -93,6 +127,12 @@ public class CacheOomRanker {
updateUseOomReranking();
} else if (KEY_OOM_RE_RANKING_NUMBER_TO_RE_RANK.equals(name)) {
updateNumberToReRank();
+ } else if (KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS.equals(name)) {
+ updatePreserveTopNApps();
+ } else if (KEY_OOM_RE_RANKING_USE_FREQUENT_RSS.equals(name)) {
+ updateUseFrequentRss();
+ } else if (KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS.equals(name)) {
+ updateRssUpdateRateMs();
} else if (KEY_OOM_RE_RANKING_LRU_WEIGHT.equals(name)) {
updateLruWeight();
} else if (KEY_OOM_RE_RANKING_USES_WEIGHT.equals(name)) {
@@ -106,9 +146,15 @@ public class CacheOomRanker {
};
CacheOomRanker(final ActivityManagerService service) {
+ this(service, new ProcessDependenciesImpl());
+ }
+
+ @VisibleForTesting
+ CacheOomRanker(final ActivityManagerService service, ProcessDependencies processDependencies) {
mService = service;
mProcLock = service.mProcLock;
mProfilerLock = service.mAppProfiler.mProfilerLock;
+ mProcessDependencies = processDependencies;
}
/** Load settings from device config and register a listener for changes. */
@@ -160,6 +206,31 @@ public class CacheOomRanker {
}
@GuardedBy("mPhenotypeFlagLock")
+ private void updatePreserveTopNApps() {
+ int preserveTopNApps = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS, DEFAULT_PRESERVE_TOP_N_APPS);
+ if (preserveTopNApps < 0) {
+ Slog.w(OomAdjuster.TAG,
+ "Found negative value for preserveTopNApps, setting to default: "
+ + preserveTopNApps);
+ preserveTopNApps = DEFAULT_PRESERVE_TOP_N_APPS;
+ }
+ mPreserveTopNApps = preserveTopNApps;
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateRssUpdateRateMs() {
+ mRssUpdateRateMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS, DEFAULT_RSS_UPDATE_RATE_MS);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
+ private void updateUseFrequentRss() {
+ mUseFrequentRss = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_OOM_RE_RANKING_USE_FREQUENT_RSS, DEFAULT_USE_FREQUENT_RSS);
+ }
+
+ @GuardedBy("mPhenotypeFlagLock")
private void updateLruWeight() {
mLruWeight = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_OOM_RE_RANKING_LRU_WEIGHT, DEFAULT_OOM_RE_RANKING_LRU_WEIGHT);
@@ -183,9 +254,39 @@ public class CacheOomRanker {
*/
@GuardedBy({"mService", "mProcLock"})
void reRankLruCachedAppsLSP(ArrayList<ProcessRecord> lruList, int lruProcessServiceStart) {
+ // The lruList is a list of processes ordered by how recently they were used. The
+ // least-recently-used apps are at the beginning of the list. We keep track of two
+ // indices in the lruList:
+ //
+ // getNumberToReRank=5, preserveTopNApps=3, lruProcessServiceStart=7,
+ // lruList=
+ // 0: app A ^
+ // 1: app B | These apps are re-ranked, as they are the first five apps (see
+ // 2: app C | getNumberToReRank), excluding...
+ // 3: app D v
+ // 4: app E ^
+ // 5: app F | The three most-recently-used apps in the cache (see preserveTopNApps).
+ // 6: app G v
+ // 7: service A ^
+ // 8: service B | Everything beyond lruProcessServiceStart is ignored, as these aren't
+ // 9: service C | apps.
+ // 10: activity A |
+ // ... |
+ //
+ // `numProcessesEvaluated` moves across the apps (indices 0-6) or until we've found enough
+ // apps to re-rank, and made sure none of them are in the top `preserveTopNApps` apps.
+ // Re-ranked apps are copied into `scoredProcessRecords`, where the re-ranking calculation
+ // happens.
+ //
+ // Note that some apps in the `lruList` can be skipped, if they don't pass
+ //`appCanBeReRanked`.
+
float lruWeight;
float usesWeight;
float rssWeight;
+ int preserveTopNApps;
+ boolean useFrequentRss;
+ long rssUpdateRateMs;
int[] lruPositions;
RankedProcessRecord[] scoredProcessRecords;
@@ -193,6 +294,9 @@ public class CacheOomRanker {
lruWeight = mLruWeight;
usesWeight = mUsesWeight;
rssWeight = mRssWeight;
+ preserveTopNApps = mPreserveTopNApps;
+ useFrequentRss = mUseFrequentRss;
+ rssUpdateRateMs = mRssUpdateRateMs;
lruPositions = mLruPositions;
scoredProcessRecords = mScoredProcessRecords;
}
@@ -202,52 +306,98 @@ public class CacheOomRanker {
return;
}
+ int numProcessesEvaluated = 0;
// Collect the least recently used processes to re-rank, only rank cached
// processes further down the list than mLruProcessServiceStart.
- int cachedProcessPos = 0;
- for (int i = 0; i < lruProcessServiceStart
- && cachedProcessPos < scoredProcessRecords.length; ++i) {
- ProcessRecord app = lruList.get(i);
+ int numProcessesReRanked = 0;
+ while (numProcessesEvaluated < lruProcessServiceStart
+ && numProcessesReRanked < scoredProcessRecords.length) {
+ ProcessRecord process = lruList.get(numProcessesEvaluated);
// Processes that will be assigned a cached oom adj score.
- if (!app.isKilledByAm() && app.getThread() != null && app.mState.getCurAdj()
- >= ProcessList.UNKNOWN_ADJ) {
- scoredProcessRecords[cachedProcessPos].proc = app;
- scoredProcessRecords[cachedProcessPos].score = 0.0f;
- lruPositions[cachedProcessPos] = i;
- ++cachedProcessPos;
+ if (appCanBeReRanked(process)) {
+ scoredProcessRecords[numProcessesReRanked].proc = process;
+ scoredProcessRecords[numProcessesReRanked].score = 0.0f;
+ lruPositions[numProcessesReRanked] = numProcessesEvaluated;
+ ++numProcessesReRanked;
}
+ ++numProcessesEvaluated;
}
- // TODO maybe ensure a certain number above this in the cache before re-ranking.
- if (cachedProcessPos < scoredProcessRecords.length) {
- // Ignore we don't have enough processes to worry about re-ranking.
- return;
+ // Count how many apps we're not re-ranking (up to preserveTopNApps).
+ int numProcessesNotReRanked = 0;
+ while (numProcessesEvaluated < lruProcessServiceStart
+ && numProcessesNotReRanked < preserveTopNApps) {
+ ProcessRecord process = lruList.get(numProcessesEvaluated);
+ if (appCanBeReRanked(process)) {
+ numProcessesNotReRanked++;
+ }
+ numProcessesEvaluated++;
+ }
+ // Exclude the top `preserveTopNApps` apps from re-ranking.
+ if (numProcessesNotReRanked < preserveTopNApps) {
+ numProcessesReRanked -= preserveTopNApps - numProcessesNotReRanked;
+ if (numProcessesReRanked < 0) {
+ numProcessesReRanked = 0;
+ }
+ }
+
+ if (useFrequentRss) {
+ // Update RSS values for re-ranked apps.
+ long nowMs = SystemClock.elapsedRealtime();
+ for (int i = 0; i < numProcessesReRanked; ++i) {
+ RankedProcessRecord scoredProcessRecord = scoredProcessRecords[i];
+ long sinceUpdateMs =
+ nowMs - scoredProcessRecord.proc.mState.getCacheOomRankerRssTimeMs();
+ if (scoredProcessRecord.proc.mState.getCacheOomRankerRss() != 0
+ && sinceUpdateMs < rssUpdateRateMs) {
+ continue;
+ }
+
+ long[] rss = mProcessDependencies.getRss(scoredProcessRecord.proc.getPid());
+ if (rss == null || rss.length == 0) {
+ Slog.e(
+ OomAdjuster.TAG,
+ "Process.getRss returned bad value, not re-ranking: "
+ + Arrays.toString(rss));
+ return;
+ }
+ // First element is total RSS:
+ // frameworks/base/core/jni/android_util_Process.cpp:1192
+ scoredProcessRecord.proc.mState.setCacheOomRankerRss(rss[0], nowMs);
+ scoredProcessRecord.proc.mProfile.setLastRss(rss[0]);
+ }
}
// Add scores for each of the weighted features we want to rank based on.
if (lruWeight > 0.0f) {
// This doesn't use the LRU list ordering as after the first re-ranking
// that will no longer be lru.
- Arrays.sort(scoredProcessRecords, LAST_ACTIVITY_TIME_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
+ LAST_ACTIVITY_TIME_COMPARATOR);
addToScore(scoredProcessRecords, lruWeight);
}
if (rssWeight > 0.0f) {
- synchronized (mService.mAppProfiler.mProfilerLock) {
- Arrays.sort(scoredProcessRecords, LAST_RSS_COMPARATOR);
+ if (useFrequentRss) {
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, RSS_COMPARATOR);
+ } else {
+ synchronized (mService.mAppProfiler.mProfilerLock) {
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, LAST_RSS_COMPARATOR);
+ }
}
addToScore(scoredProcessRecords, rssWeight);
}
if (usesWeight > 0.0f) {
- Arrays.sort(scoredProcessRecords, CACHE_USE_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked, CACHE_USE_COMPARATOR);
addToScore(scoredProcessRecords, usesWeight);
}
// Re-rank by the new combined score.
- Arrays.sort(scoredProcessRecords, SCORED_PROCESS_RECORD_COMPARATOR);
+ Arrays.sort(scoredProcessRecords, 0, numProcessesReRanked,
+ SCORED_PROCESS_RECORD_COMPARATOR);
if (ActivityManagerDebugConfig.DEBUG_OOM_ADJ) {
boolean printedHeader = false;
- for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ for (int i = 0; i < numProcessesReRanked; ++i) {
if (scoredProcessRecords[i].proc.getPid()
!= lruList.get(lruPositions[i]).getPid()) {
if (!printedHeader) {
@@ -260,12 +410,18 @@ public class CacheOomRanker {
}
}
- for (int i = 0; i < scoredProcessRecords.length; ++i) {
+ for (int i = 0; i < numProcessesReRanked; ++i) {
lruList.set(lruPositions[i], scoredProcessRecords[i].proc);
scoredProcessRecords[i].proc = null;
}
}
+ private static boolean appCanBeReRanked(ProcessRecord process) {
+ return !process.isKilledByAm()
+ && process.getThread() != null
+ && process.mState.getCurAdj() >= ProcessList.UNKNOWN_ADJ;
+ }
+
private static void addToScore(RankedProcessRecord[] scores, float weight) {
for (int i = 1; i < scores.length; ++i) {
scores[i].score += i * weight;
@@ -305,6 +461,16 @@ public class CacheOomRanker {
}
}
+ private static class RssComparator implements Comparator<RankedProcessRecord> {
+ @Override
+ public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
+ // High RSS first to match least recently used.
+ return Long.compare(
+ o2.proc.mState.getCacheOomRankerRss(),
+ o1.proc.mState.getCacheOomRankerRss());
+ }
+ }
+
private static class LastRssComparator implements Comparator<RankedProcessRecord> {
@Override
public int compare(RankedProcessRecord o1, RankedProcessRecord o2) {
@@ -317,4 +483,18 @@ public class CacheOomRanker {
public ProcessRecord proc;
public float score;
}
+
+ /**
+ * Interface for mocking {@link Process} static methods.
+ */
+ interface ProcessDependencies {
+ long[] getRss(int pid);
+ }
+
+ private static class ProcessDependenciesImpl implements ProcessDependencies {
+ @Override
+ public long[] getRss(int pid) {
+ return Process.getRss(pid);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index f32aa2295cb8..6230919d8d2d 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -157,6 +157,9 @@ public final class CachedAppOptimizer {
static final int SYNC_RECEIVED_WHILE_FROZEN = 1;
static final int ASYNC_RECEIVED_WHILE_FROZEN = 2;
+ // Bitfield values for sync transactions received by frozen binder threads
+ static final int TXNS_PENDING_WHILE_FROZEN = 4;
+
/**
* This thread must be moved to the system background cpuset.
* If that doesn't happen, it's probably going to draw a lot of power.
@@ -611,8 +614,9 @@ public final class CachedAppOptimizer {
* binder for the specificed pid.
*
* @throws RuntimeException in case a flush/freeze operation could not complete successfully.
+ * @return 0 if success, or -EAGAIN indicating there's pending transaction.
*/
- private static native void freezeBinder(int pid, boolean freeze);
+ private static native int freezeBinder(int pid, boolean freeze);
/**
* Retrieves binder freeze info about a process.
@@ -641,21 +645,25 @@ public final class CachedAppOptimizer {
char state = (char) fr.read();
if (state == '1' || state == '0') {
+ // Also check freezer binder ioctl
+ getBinderFreezeInfo(Process.myPid());
supported = true;
} else {
Slog.e(TAG_AM, "unexpected value in cgroup.freeze");
}
} catch (java.io.FileNotFoundException e) {
- Slog.d(TAG_AM, "cgroup.freeze not present");
+ Slog.w(TAG_AM, "cgroup.freeze not present");
+ } catch (RuntimeException e) {
+ Slog.w(TAG_AM, "unable to read freezer info");
} catch (Exception e) {
- Slog.d(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
+ Slog.w(TAG_AM, "unable to read cgroup.freeze: " + e.toString());
}
if (fr != null) {
try {
fr.close();
} catch (java.io.IOException e) {
- Slog.e(TAG_AM, "Exception closing freezer.killable: " + e.toString());
+ Slog.e(TAG_AM, "Exception closing cgroup.freeze: " + e.toString());
}
}
@@ -948,7 +956,7 @@ public final class CachedAppOptimizer {
int freezeInfo = getBinderFreezeInfo(pid);
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
- Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+ Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received sync transactions while frozen, killing");
app.killLocked("Sync transaction while in frozen state",
ApplicationExitInfo.REASON_OTHER,
@@ -956,8 +964,8 @@ public final class CachedAppOptimizer {
processKilled = true;
}
- if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
- Slog.d(TAG_AM, "pid " + pid + " " + app.processName + " "
+ if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0 && DEBUG_FREEZER) {
+ Slog.d(TAG_AM, "pid " + pid + " " + app.processName
+ " received async transactions while frozen");
}
} catch (Exception e) {
@@ -1292,7 +1300,9 @@ public final class CachedAppOptimizer {
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_FROZEN_PROCESS_MSG:
- freezeProcess((ProcessRecord) msg.obj);
+ synchronized (mAm) {
+ freezeProcess((ProcessRecord) msg.obj);
+ }
break;
case REPORT_UNFREEZE_MSG:
int pid = msg.arg1;
@@ -1306,6 +1316,15 @@ public final class CachedAppOptimizer {
}
}
+ @GuardedBy({"mAm", "mProcLock"})
+ private void rescheduleFreeze(final ProcessRecord proc, final String reason) {
+ Slog.d(TAG_AM, "Reschedule freeze for process " + proc.getPid()
+ + " " + proc.processName + " (" + reason + ")");
+ unfreezeAppLSP(proc);
+ freezeAppAsyncLSP(proc);
+ }
+
+ @GuardedBy({"mAm"})
private void freezeProcess(final ProcessRecord proc) {
int pid = proc.getPid(); // Unlocked intentionally
final String name = proc.processName;
@@ -1355,10 +1374,15 @@ public final class CachedAppOptimizer {
return;
}
+ Slog.d(TAG_AM, "freezing " + pid + " " + name);
+
// Freeze binder interface before the process, to flush any
// transactions that might be pending.
try {
- freezeBinder(pid, true);
+ if (freezeBinder(pid, true) != 0) {
+ rescheduleFreeze(proc, "outstanding txns");
+ return;
+ }
} catch (RuntimeException e) {
Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
mFreezeHandler.post(() -> {
@@ -1404,24 +1428,36 @@ public final class CachedAppOptimizer {
try {
// post-check to prevent races
+ int freezeInfo = getBinderFreezeInfo(pid);
+
+ if ((freezeInfo & TXNS_PENDING_WHILE_FROZEN) != 0) {
+ synchronized (mProcLock) {
+ rescheduleFreeze(proc, "new pending txns");
+ }
+ return;
+ }
+ } catch (RuntimeException e) {
+ Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
+ mFreezeHandler.post(() -> {
+ synchronized (mAm) {
+ proc.killLocked("Unable to freeze binder interface",
+ ApplicationExitInfo.REASON_OTHER,
+ ApplicationExitInfo.SUBREASON_FREEZER_BINDER_IOCTL, true);
+ }
+ });
+ }
+
+ try {
+ // post-check to prevent races
if (mProcLocksReader.hasFileLocks(pid)) {
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, reverting freeze");
}
-
- synchronized (mAm) {
- synchronized (mProcLock) {
- unfreezeAppLSP(proc);
- }
- }
+ unfreezeAppLSP(proc);
}
} catch (Exception e) {
Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
- synchronized (mAm) {
- synchronized (mProcLock) {
- unfreezeAppLSP(proc);
- }
- }
+ unfreezeAppLSP(proc);
}
}
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 5c9d38515e49..2e3e635c1157 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -94,8 +94,6 @@ final class CoreSettingsObserver extends ContentObserver {
sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES, String.class);
sGlobalSettingToTypeMap.put(
- Settings.Global.ANGLE_ALLOWLIST, String.class);
- sGlobalSettingToTypeMap.put(
Settings.Global.ANGLE_EGL_FEATURES, String.class);
sGlobalSettingToTypeMap.put(
Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 5c19ceb7067a..ff480d1f614c 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -72,6 +72,7 @@ import static com.android.server.am.AppProfiler.TAG_PSS;
import static com.android.server.am.ProcessList.TAG_PROCESS_OBSERVERS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityThread;
@@ -114,6 +115,8 @@ 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.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -171,6 +174,27 @@ public class OomAdjuster {
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S)
static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
+ static final int CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY = 0;
+ static final int CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY = 1;
+ static final int CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME = 2;
+
+ @IntDef(prefix = { "CACHED_COMPAT_CHANGE_" }, value = {
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY,
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY,
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ static @interface CachedCompatChangeId{}
+
+ /**
+ * Mapping from CACHED_COMPAT_CHANGE_* to the actual compat change id.
+ */
+ static final long[] CACHED_COMPAT_CHANGE_IDS_MAPPING = new long[] {
+ PROCESS_CAPABILITY_CHANGE_ID,
+ CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID,
+ USE_SHORT_FGS_USAGE_INTERACTION_TIME,
+ };
+
/**
* For some direct access we need to power manager.
*/
@@ -368,6 +392,12 @@ public class OomAdjuster {
return mPlatformCompatCache;
}
+ boolean isChangeEnabled(@CachedCompatChangeId int cachedCompatChangeId, ApplicationInfo app,
+ boolean defaultValue) {
+ return getPlatformCompatCache().isChangeEnabled(
+ CACHED_COMPAT_CHANGE_IDS_MAPPING[cachedCompatChangeId], app, defaultValue);
+ }
+
OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
this(service, processList, activeUids, createAdjusterThread());
}
@@ -1929,12 +1959,8 @@ public class OomAdjuster {
(fgsType & FOREGROUND_SERVICE_TYPE_LOCATION)
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- CAMERA_MICROPHONE_CAPABILITY_CHANGE_ID, s.appInfo);
- } catch (RemoteException e) {
- }
+ final boolean enabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY);
if (enabled) {
capabilityFromFGS |=
(fgsType & FOREGROUND_SERVICE_TYPE_CAMERA)
@@ -2151,12 +2177,8 @@ public class OomAdjuster {
// to client's state.
clientProcState = PROCESS_STATE_BOUND_TOP;
state.bumpAllowStartFgsState(PROCESS_STATE_BOUND_TOP);
- boolean enabled = false;
- try {
- enabled = getPlatformCompatCache().isChangeEnabled(
- PROCESS_CAPABILITY_CHANGE_ID, client.info);
- } catch (RemoteException e) {
- }
+ final boolean enabled = cstate.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY);
if (enabled) {
if (cr.hasFlag(Context.BIND_INCLUDE_CAPABILITIES)) {
// TOP process passes all capabilities to the service.
@@ -2800,8 +2822,8 @@ public class OomAdjuster {
state.setProcStateChanged(true);
}
} else if (state.hasReportedInteraction()) {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.USAGE_STATS_INTERACTION_INTERVAL_POST_S
: mConstants.USAGE_STATS_INTERACTION_INTERVAL_PRE_S;
@@ -2811,8 +2833,8 @@ public class OomAdjuster {
maybeUpdateUsageStatsLSP(app, nowElapsed);
}
} else {
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
final long interactionThreshold = fgsInteractionChangeEnabled
? mConstants.SERVICE_USAGE_INTERACTION_TIME_POST_S
: mConstants.SERVICE_USAGE_INTERACTION_TIME_PRE_S;
@@ -2901,8 +2923,8 @@ public class OomAdjuster {
if (mService.mUsageStatsService == null) {
return;
}
- final boolean fgsInteractionChangeEnabled = getPlatformCompatCache().isChangeEnabled(
- USE_SHORT_FGS_USAGE_INTERACTION_TIME, app.info, false);
+ final boolean fgsInteractionChangeEnabled = state.getCachedCompatChange(
+ CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME);
boolean isInteraction;
// To avoid some abuse patterns, we are going to be careful about what we consider
// to be an app interaction. Being the top activity doesn't count while the display
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index dc6bcd8ab9de..206dd889fb70 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -21,6 +21,7 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.OomAdjuster.CachedCompatChangeId;
import static com.android.server.am.ProcessRecord.TAG;
import android.annotation.ElapsedRealtimeLong;
@@ -342,6 +343,20 @@ final class ProcessStateRecord {
private int mCacheOomRankerUseCount;
/**
+ * Process memory usage (RSS).
+ *
+ * Periodically populated by {@code CacheOomRanker}, stored in this object to cache the values.
+ */
+ @GuardedBy("mService")
+ private long mCacheOomRankerRss;
+
+ /**
+ * The last time, in milliseconds since boot, since {@link #mCacheOomRankerRss} was updated.
+ */
+ @GuardedBy("mService")
+ private long mCacheOomRankerRssTimeMs;
+
+ /**
* Whether or not this process is reachable from given process.
*/
@GuardedBy("mService")
@@ -377,6 +392,16 @@ final class ProcessStateRecord {
@GuardedBy("mService")
private int mCachedIsReceivingBroadcast = VALUE_INVALID;
+ /**
+ * Cache the return value of PlatformCompat.isChangeEnabled().
+ */
+ @GuardedBy("mService")
+ private int[] mCachedCompatChanges = new int[] {
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_PROCESS_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_CAMERA_MICROPHONE_CAPABILITY
+ VALUE_INVALID, // CACHED_COMPAT_CHANGE_USE_SHORT_FGS_USAGE_INTERACTION_TIME
+ };
+
@GuardedBy("mService")
private int mCachedAdj = ProcessList.INVALID_ADJ;
@GuardedBy("mService")
@@ -566,6 +591,10 @@ final class ProcessStateRecord {
@GuardedBy({"mService", "mProcLock"})
void setSetProcState(int setProcState) {
+ if (ActivityManager.isProcStateCached(mSetProcState)
+ && !ActivityManager.isProcStateCached(setProcState)) {
+ mCacheOomRankerUseCount++;
+ }
mSetProcState = setProcState;
}
@@ -829,12 +858,7 @@ final class ProcessStateRecord {
@GuardedBy("mService")
void setCached(boolean cached) {
- if (mCached != cached) {
- mCached = cached;
- if (cached) {
- ++mCacheOomRankerUseCount;
- }
- }
+ mCached = cached;
}
@GuardedBy("mService")
@@ -1009,6 +1033,16 @@ final class ProcessStateRecord {
}
@GuardedBy("mService")
+ boolean getCachedCompatChange(@CachedCompatChangeId int cachedCompatChangeId) {
+ if (mCachedCompatChanges[cachedCompatChangeId] == VALUE_INVALID) {
+ mCachedCompatChanges[cachedCompatChangeId] = mService.mOomAdjuster
+ .isChangeEnabled(cachedCompatChangeId, mApp.info, false /* default */)
+ ? VALUE_TRUE : VALUE_FALSE;
+ }
+ return mCachedCompatChanges[cachedCompatChangeId] == VALUE_TRUE;
+ }
+
+ @GuardedBy("mService")
void computeOomAdjFromActivitiesIfNecessary(OomAdjuster.ComputeOomAdjWindowCallback callback,
int adj, boolean foregroundActivities, boolean hasVisibleActivities, int procState,
int schedGroup, int appUid, int logUid, int processCurTop) {
@@ -1088,6 +1122,9 @@ final class ProcessStateRecord {
mCurSchedGroup = mSetSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
mCurProcState = mCurRawProcState = mSetProcState = mAllowStartFgsState =
PROCESS_STATE_NONEXISTENT;
+ for (int i = 0; i < mCachedCompatChanges.length; i++) {
+ mCachedCompatChanges[i] = VALUE_INVALID;
+ }
}
@GuardedBy("mService")
@@ -1127,6 +1164,21 @@ final class ProcessStateRecord {
return mLastInvisibleTime;
}
+ public void setCacheOomRankerRss(long rss, long rssTimeMs) {
+ mCacheOomRankerRss = rss;
+ mCacheOomRankerRssTimeMs = rssTimeMs;
+ }
+
+ @GuardedBy("mService")
+ public long getCacheOomRankerRss() {
+ return mCacheOomRankerRss;
+ }
+
+ @GuardedBy("mService")
+ public long getCacheOomRankerRssTimeMs() {
+ return mCacheOomRankerRssTimeMs;
+ }
+
@GuardedBy({"mService", "mProcLock"})
void dump(PrintWriter pw, String prefix, long nowUptime) {
if (mReportedInteraction || mFgInteractionTime != 0) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 804e442bc8de..17930ea9c93c 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -927,9 +927,9 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
}
public void postNotification() {
- final int appUid = appInfo.uid;
- final int appPid = app.getPid();
- if (isForeground && foregroundNoti != null) {
+ if (isForeground && foregroundNoti != null && app != null) {
+ final int appUid = appInfo.uid;
+ final int appPid = app.getPid();
// Do asynchronous communication with notification manager to
// avoid deadlocks.
final String localPackageName = packageName;
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index af8d7a6a282b..3003c520254b 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -272,6 +272,13 @@ public final class GameManagerService extends IGameManagerService.Stub {
"com.android.graphics.intervention.wm.allowDownscale";
/**
+ * Metadata that can be included in the app manifest to allow/disallow any ANGLE
+ * interventions. Default value is TRUE.
+ */
+ public static final String METADATA_ANGLE_ALLOW_ANGLE =
+ "com.android.graphics.intervention.angle.allowAngle";
+
+ /**
* Metadata that needs to be included in the app manifest to OPT-IN to PERFORMANCE mode.
* This means the app will assume full responsibility for the experience provided by this
* mode and the system will enable no window manager downscaling.
@@ -294,6 +301,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
private boolean mPerfModeOptedIn;
private boolean mBatteryModeOptedIn;
private boolean mAllowDownscale;
+ private boolean mAllowAngle;
GamePackageConfiguration(String packageName, int userId) {
mPackageName = packageName;
@@ -305,10 +313,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
+ mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
} else {
mPerfModeOptedIn = false;
mBatteryModeOptedIn = false;
mAllowDownscale = true;
+ mAllowAngle = true;
}
} catch (PackageManager.NameNotFoundException e) {
// Not all packages are installed, hence ignore those that are not installed yet.
@@ -340,14 +350,26 @@ public final class GameManagerService extends IGameManagerService.Stub {
public static final String MODE_KEY = "mode";
public static final String SCALING_KEY = "downscaleFactor";
public static final String DEFAULT_SCALING = "1.0";
+ public static final String ANGLE_KEY = "useAngle";
private final @GameMode int mGameMode;
private final String mScaling;
+ private final boolean mUseAngle;
GameModeConfiguration(KeyValueListParser parser) {
mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
- mScaling = !mAllowDownscale || isGameModeOptedIn(mGameMode)
+ // isGameModeOptedIn() returns if an app will handle all of the changes necessary
+ // for a particular game mode. If so, the Android framework (i.e.
+ // GameManagerService) will not do anything for the app (like window scaling or
+ // using ANGLE).
+ mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
? DEFAULT_SCALING : parser.getString(SCALING_KEY, DEFAULT_SCALING);
+ // We only want to use ANGLE if:
+ // - We're allowed to use ANGLE (the app hasn't opted out via the manifest) AND
+ // - The app has not opted in to performing the work itself AND
+ // - The Phenotype config has enabled it.
+ mUseAngle = mAllowAngle && !willGamePerformOptimizations(mGameMode)
+ && parser.getBoolean(ANGLE_KEY, false);
}
public int getGameMode() {
@@ -358,6 +380,10 @@ public final class GameManagerService extends IGameManagerService.Stub {
return mScaling;
}
+ public boolean getUseAngle() {
+ return mUseAngle;
+ }
+
public boolean isValid() {
return (mGameMode == GameManager.GAME_MODE_PERFORMANCE
|| mGameMode == GameManager.GAME_MODE_BATTERY)
@@ -368,7 +394,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
* @hide
*/
public String toString() {
- return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + "]";
+ return "[Game Mode:" + mGameMode + ",Scaling:" + mScaling + ",Use Angle:"
+ + mUseAngle + "]";
}
/**
@@ -384,13 +411,14 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
/**
- * Gets whether a package has opted into a game mode via its manifest.
+ * Returns if the app will assume full responsibility for the experience provided by this
+ * mode. If True, the system will not perform any interventions for the app.
*
* @return True if the app package has specified in its metadata either:
* "com.android.app.gamemode.performance.enabled" or
* "com.android.app.gamemode.battery.enabled" with a value of "true"
*/
- public boolean isGameModeOptedIn(@GameMode int gameMode) {
+ public boolean willGamePerformOptimizations(@GameMode int gameMode) {
return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
|| (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
}
@@ -631,7 +659,34 @@ public final class GameManagerService extends IGameManagerService.Stub {
mHandler.sendMessageDelayed(msg, WRITE_SETTINGS_DELAY);
}
}
- updateCompatModeDownscale(packageName, gameMode);
+ updateInterventions(packageName, gameMode);
+ }
+
+ /**
+ * Get if ANGLE is enabled for the package for the currently enabled game mode.
+ * Checks that the caller has {@link android.Manifest.permission#MANAGE_GAME_MODE}.
+ */
+ @Override
+ @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE)
+ public @GameMode boolean getAngleEnabled(String packageName, int userId)
+ throws SecurityException {
+ final int gameMode = getGameMode(packageName, userId);
+ if (gameMode == GameManager.GAME_MODE_UNSUPPORTED) {
+ return false;
+ }
+
+ synchronized (mDeviceConfigLock) {
+ final GamePackageConfiguration config = mConfigs.get(packageName);
+ if (config == null) {
+ return false;
+ }
+ GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+ config.getGameModeConfiguration(gameMode);
+ if (gameModeConfiguration == null) {
+ return false;
+ }
+ return gameModeConfiguration.getUseAngle();
+ }
}
/**
@@ -753,7 +808,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (DEBUG) {
Slog.v(TAG, dumpDeviceConfigs());
}
- if (packageConfig.isGameModeOptedIn(gameMode)) {
+ if (packageConfig.willGamePerformOptimizations(gameMode)) {
disableCompatScale(packageName);
return;
}
@@ -782,6 +837,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
return (bitField & modeToBitmask(gameMode)) != 0;
}
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ private void updateUseAngle(String packageName, @GameMode int gameMode) {
+ // TODO (b/188475576): Nothing to do yet. Remove if it's still empty when we're ready to
+ // ship.
+ }
+
+ private void updateInterventions(String packageName, @GameMode int gameMode) {
+ updateCompatModeDownscale(packageName, gameMode);
+ updateUseAngle(packageName, gameMode);
+ }
+
/**
* @hide
*/
@@ -839,11 +905,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
if (newGameMode != gameMode) {
setGameMode(packageName, newGameMode, userId);
}
- updateCompatModeDownscale(packageName, gameMode);
+ updateInterventions(packageName, gameMode);
}
}
} catch (Exception e) {
- Slog.e(TAG, "Failed to update compat modes for user: " + userId);
+ Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
}
}
@@ -851,7 +917,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
final List<PackageInfo> packages =
mPackageManager.getInstalledPackagesAsUser(0, userId);
return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
- == ApplicationInfo.CATEGORY_GAME)
+ == ApplicationInfo.CATEGORY_GAME)
.map(e -> e.packageName)
.toArray(String[]::new);
}
diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
index db2ecc54dbfe..19dcee4828dd 100644
--- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java
+++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java
@@ -696,7 +696,7 @@ public final class AppHibernationService extends SystemService {
idpw.print("User Level Hibernation States, ");
idpw.printPair("user", userId);
idpw.println();
- Map<String, UserLevelState> stateMap = mUserStates.get(i);
+ Map<String, UserLevelState> stateMap = mUserStates.get(userId);
idpw.increaseIndent();
for (UserLevelState state : stateMap.values()) {
idpw.print(state);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 0b2311bc563a..4fc661472b5a 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1640,10 +1640,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (client == null) {
return;
}
- Log.w(TAG, "Speaker client died");
+ Log.w(TAG, "Communication client died");
setCommunicationRouteForClient(
- client.getBinder(), client.getPid(), null,
- BtHelper.SCO_MODE_UNDEFINED, "onCommunicationRouteClientDied");
+ client.getBinder(), client.getPid(), null, BtHelper.SCO_MODE_UNDEFINED,
+ "onCommunicationRouteClientDied");
}
/**
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 51784bbce2e0..df5d60c3a74d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -2601,18 +2601,19 @@ public class AudioService extends IAudioService.Stub
case KeyEvent.KEYCODE_VOLUME_UP:
adjustSuggestedStreamVolume(AudioManager.ADJUST_RAISE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, keyEventMode);
+ Binder.getCallingUid(), Binder.getCallingPid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
adjustSuggestedStreamVolume(AudioManager.ADJUST_LOWER,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, keyEventMode);
+ Binder.getCallingUid(), Binder.getCallingPid(), true, keyEventMode);
break;
case KeyEvent.KEYCODE_VOLUME_MUTE:
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
- Binder.getCallingUid(), true, VOL_ADJUST_NORMAL);
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ true, VOL_ADJUST_NORMAL);
}
break;
default:
@@ -2625,8 +2626,8 @@ public class AudioService extends IAudioService.Stub
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
String callingPackage, String caller) {
adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
- caller, Binder.getCallingUid(), callingHasAudioSettingsPermission(),
- VOL_ADJUST_NORMAL);
+ caller, Binder.getCallingUid(), Binder.getCallingPid(),
+ callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
}
public void setNavigationRepeatSoundEffectsEnabled(boolean enabled) {
@@ -2652,7 +2653,7 @@ public class AudioService extends IAudioService.Stub
}
private void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
- String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
+ String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
if (DEBUG_VOL) Log.d(TAG, "adjustSuggestedStreamVolume() stream=" + suggestedStreamType
+ ", flags=" + flags + ", caller=" + caller
@@ -2725,7 +2726,7 @@ public class AudioService extends IAudioService.Stub
if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
}
- adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid,
+ adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, pid,
hasModifyAudioSettings, keyEventMode);
}
@@ -2757,12 +2758,12 @@ public class AudioService extends IAudioService.Stub
sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
direction/*val1*/, flags/*val2*/, callingPackage));
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
- Binder.getCallingUid(), callingHasAudioSettingsPermission(),
- VOL_ADJUST_NORMAL);
+ Binder.getCallingUid(), Binder.getCallingPid(),
+ callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
}
protected void adjustStreamVolume(int streamType, int direction, int flags,
- String callingPackage, String caller, int uid, boolean hasModifyAudioSettings,
+ String callingPackage, String caller, int uid, int pid, boolean hasModifyAudioSettings,
int keyEventMode) {
if (mUseFixedVolume) {
return;
@@ -2784,8 +2785,7 @@ public class AudioService extends IAudioService.Stub
if (isMuteAdjust &&
(streamType == AudioSystem.STREAM_VOICE_CALL ||
streamType == AudioSystem.STREAM_BLUETOOTH_SCO) &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_PHONE_STATE)
+ mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -2795,8 +2795,8 @@ public class AudioService extends IAudioService.Stub
// If the stream is STREAM_ASSISTANT,
// make sure that the calling app have the MODIFY_AUDIO_ROUTING permission.
if (streamType == AudioSystem.STREAM_ASSISTANT &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ mContext.checkPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid="
+ Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
@@ -2828,8 +2828,8 @@ public class AudioService extends IAudioService.Stub
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ // validate calling package and app op
+ if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
return;
}
@@ -3562,8 +3562,7 @@ public class AudioService extends IAudioService.Stub
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(getCurrentUserId(), UserHandle.getAppId(uid));
}
- if (mAppOps.noteOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!checkNoteAppOp(STREAM_VOLUME_OPS[streamTypeAlias], uid, callingPackage)) {
return;
}
@@ -4001,20 +4000,19 @@ public class AudioService extends IAudioService.Stub
}
private void setMasterMuteInternal(boolean mute, int flags, String callingPackage, int uid,
- int userId) {
+ int userId, int pid) {
// If we are being called by the system check for user we are going to change
// so we handle user restrictions correctly.
if (uid == android.os.Process.SYSTEM_UID) {
uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
}
// If OP_AUDIO_MASTER_VOLUME is set, disallow unmuting.
- if (!mute && mAppOps.noteOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!mute && !checkNoteAppOp(AppOpsManager.OP_AUDIO_MASTER_VOLUME, uid, callingPackage)) {
return;
}
if (userId != UserHandle.getCallingUserId() &&
- mContext.checkCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ pid, uid)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
@@ -4054,7 +4052,7 @@ public class AudioService extends IAudioService.Stub
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId) {
enforceModifyAudioRoutingPermission();
setMasterMuteInternal(mute, flags, callingPackage, Binder.getCallingUid(),
- userId);
+ userId, Binder.getCallingPid());
}
/** @see AudioManager#getStreamVolume(int) */
@@ -4140,8 +4138,7 @@ public class AudioService extends IAudioService.Stub
? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE);
// If OP_MUTE_MICROPHONE is set, disallow unmuting.
- if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
- != AppOpsManager.MODE_ALLOWED) {
+ if (!on && !checkNoteAppOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)) {
mmi.set(MediaMetrics.Property.EARLY_RETURN, "disallow unmuting").record();
return;
}
@@ -4956,8 +4953,8 @@ public class AudioService extends IAudioService.Stub
// direction and stream type swap here because the public
// adjustSuggested has a different order than the other methods.
- adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName, uid,
- hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
+ adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName,
+ uid, pid, hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
}
/** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */
@@ -4976,7 +4973,7 @@ public class AudioService extends IAudioService.Stub
.toString()));
}
- adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid,
+ adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, pid,
hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
}
@@ -5320,6 +5317,10 @@ public class AudioService extends IAudioService.Stub
// TODO investigate internal users due to deprecation of SDK API
/** @see AudioManager#setBluetoothA2dpOn(boolean) */
public void setBluetoothA2dpOn(boolean on) {
+ if (!checkAudioSettingsPermission("setBluetoothA2dpOn()")) {
+ return;
+ }
+
// for logging only
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
@@ -5345,6 +5346,10 @@ public class AudioService extends IAudioService.Stub
/** @see AudioManager#startBluetoothSco() */
public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+ if (!checkAudioSettingsPermission("startBluetoothSco()")) {
+ return;
+ }
+
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final int scoAudioMode =
@@ -5367,6 +5372,10 @@ public class AudioService extends IAudioService.Stub
/** @see AudioManager#startBluetoothScoVirtualCall() */
public void startBluetoothScoVirtualCall(IBinder cb) {
+ if (!checkAudioSettingsPermission("startBluetoothScoVirtualCall()")) {
+ return;
+ }
+
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
@@ -10534,4 +10543,31 @@ public class AudioService extends IAudioService.Stub
}
mFullVolumeDevices.remove(audioSystemDeviceOut);
}
+
+ //====================
+ // Helper functions for app ops
+ //====================
+ /**
+ * Validates, and notes an app op for a given uid and package name.
+ * Validation comes from exception catching: a security exception indicates the package
+ * doesn't exist, an IAE indicates the uid and package don't match. The code only checks
+ * if exception was thrown for robustness to code changes in op validation
+ * @param op the app op to check
+ * @param uid the uid of the caller
+ * @param packageName the package to check
+ * @return true if the origin of the call is valid (no uid / package mismatch) and the caller
+ * is allowed to perform the operation
+ */
+ private boolean checkNoteAppOp(int op, int uid, String packageName) {
+ try {
+ if (mAppOps.noteOp(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ return false;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error noting op:" + op + " on uid:" + uid + " for package:"
+ + packageName, e);
+ return false;
+ }
+ return true;
+ }
}
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index bb627e5a21fb..00cb280236d7 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -36,7 +36,16 @@ public final class FadeOutManager {
public static final String TAG = "AudioService.FadeOutManager";
+ /** duration of the fade out curve */
/*package*/ static final long FADE_OUT_DURATION_MS = 2000;
+ /**
+ * delay after which a faded out player will be faded back in. This will be heard by the user
+ * only in the case of unmuting players that didn't respect audio focus and didn't stop/pause
+ * when their app lost focus.
+ * This is the amount of time between the app being notified of
+ * the focus loss (when its muted by the fade out), and the time fade in (to unmute) starts
+ */
+ /*package*/ static final long DELAY_FADE_IN_OFFENDERS_MS = 2000;
private static final boolean DEBUG = PlaybackActivityMonitor.DEBUG;
@@ -148,6 +157,11 @@ public final class FadeOutManager {
}
}
+ /**
+ * Remove the app for the given UID from the list of faded out apps, unfade out its players
+ * @param uid the uid for the app to unfade out
+ * @param players map of current available players (so we can get an APC from piid)
+ */
synchronized void unfadeOutUid(int uid, HashMap<Integer, AudioPlaybackConfiguration> players) {
Log.i(TAG, "unfadeOutUid() uid:" + uid);
final FadedOutApp fa = mFadedApps.remove(uid);
@@ -157,12 +171,6 @@ public final class FadeOutManager {
fa.removeUnfadeAll(players);
}
- synchronized void forgetUid(int uid) {
- //Log.v(TAG, "forget() uid:" + uid);
- //mFadedApps.remove(uid);
- // TODO unfade all players later in case they are reused or the app continued to play
- }
-
// pre-condition: apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED
// see {@link PlaybackActivityMonitor#playerEvent}
synchronized void checkFade(@NonNull AudioPlaybackConfiguration apc) {
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index e6c4abfa2086..9548ada14b8e 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -131,6 +131,11 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
@Override
public void restoreVShapedPlayers(@NonNull FocusRequester winner) {
mFocusEnforcer.restoreVShapedPlayers(winner);
+ // remove scheduled events to unfade out offending players (if any) corresponding to
+ // this uid, as we're removing any effects of muting/ducking/fade out now
+ mFocusHandler.removeEqualMessages(MSL_L_FORGET_UID,
+ new ForgetFadeUidInfo(winner.getClientUid()));
+
}
@Override
@@ -1182,6 +1187,13 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
mFocusHandler.obtainMessage(MSG_L_FOCUS_LOSS_AFTER_FADE, focusLoser),
FadeOutManager.FADE_OUT_DURATION_MS);
}
+
+ private void postForgetUidLater(int uid) {
+ mFocusHandler.sendMessageDelayed(
+ mFocusHandler.obtainMessage(MSL_L_FORGET_UID, new ForgetFadeUidInfo(uid)),
+ FadeOutManager.DELAY_FADE_IN_OFFENDERS_MS);
+ }
+
//=================================================================
// Message handling
private Handler mFocusHandler;
@@ -1196,6 +1208,8 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
*/
private static final int MSG_L_FOCUS_LOSS_AFTER_FADE = 1;
+ private static final int MSL_L_FORGET_UID = 2;
+
private void initFocusThreading() {
mFocusThread = new HandlerThread(TAG);
mFocusThread.start();
@@ -1213,15 +1227,56 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
if (loser.isInFocusLossLimbo()) {
loser.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
loser.release();
- mFocusEnforcer.forgetUid(loser.getClientUid());
+ postForgetUidLater(loser.getClientUid());
}
}
break;
+
+ case MSL_L_FORGET_UID:
+ final int uid = ((ForgetFadeUidInfo) msg.obj).mUid;
+ if (DEBUG) {
+ Log.d(TAG, "MSL_L_FORGET_UID uid=" + uid);
+ }
+ mFocusEnforcer.forgetUid(uid);
+ break;
default:
break;
}
}
};
+ }
+ /**
+ * Class to associate a UID with a scheduled event to "forget" a UID for the fade out behavior.
+ * Having a class with an equals() override allows using Handler.removeEqualsMessage() to
+ * unschedule events when needed. Here we need to unschedule the "unfading out" == "forget uid"
+ * whenever a new, more recent, focus related event happens before this one is handled.
+ */
+ private static final class ForgetFadeUidInfo {
+ private final int mUid;
+
+ ForgetFadeUidInfo(int uid) {
+ mUid = uid;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final ForgetFadeUidInfo f = (ForgetFadeUidInfo) o;
+ if (f.mUid != mUid) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mUid;
+ }
}
}
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index a13b2eb344d9..b94cea4d5d40 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -747,7 +747,11 @@ public final class PlaybackActivityMonitor
@Override
public void forgetUid(int uid) {
- mFadingManager.forgetUid(uid);
+ final HashMap<Integer, AudioPlaybackConfiguration> players;
+ synchronized (mPlayerLock) {
+ players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone();
+ }
+ mFadingManager.unfadeOutUid(uid, players);
}
//=================================================================
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index c97ad55ceeec..b2c40d580946 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -210,7 +210,7 @@ public class AuthService extends SystemService {
}
@Override
- public void authenticate(IBinder token, long sessionId, int userId,
+ public long authenticate(IBinder token, long sessionId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo)
throws RemoteException {
// Only allow internal clients to authenticate with a different userId.
@@ -227,18 +227,18 @@ public class AuthService extends SystemService {
if (!checkAppOps(callingUid, opPackageName, "authenticate()")) {
authenticateFastFail("Denied by app ops: " + opPackageName, receiver);
- return;
+ return -1;
}
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
authenticateFastFail(
"Unable to authenticate, one or more null arguments", receiver);
- return;
+ return -1;
}
if (!Utils.isForeground(callingUid, callingPid)) {
authenticateFastFail("Caller is not foreground: " + opPackageName, receiver);
- return;
+ return -1;
}
if (promptInfo.containsTestConfigurations()) {
@@ -255,7 +255,7 @@ public class AuthService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
- mBiometricService.authenticate(
+ return mBiometricService.authenticate(
token, sessionId, userId, receiver, opPackageName, promptInfo);
} finally {
Binder.restoreCallingIdentity(identity);
@@ -274,7 +274,7 @@ public class AuthService extends SystemService {
}
@Override
- public void cancelAuthentication(IBinder token, String opPackageName)
+ public void cancelAuthentication(IBinder token, String opPackageName, long requestId)
throws RemoteException {
checkPermission();
@@ -285,7 +285,7 @@ public class AuthService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
- mBiometricService.cancelAuthentication(token, opPackageName);
+ mBiometricService.cancelAuthentication(token, opPackageName, requestId);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index bdde980f5cae..0da6a1ba3109 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -128,6 +128,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
@VisibleForTesting final IBinder mToken;
// Info to be shown on BiometricDialog when all cookies are returned.
@VisibleForTesting final PromptInfo mPromptInfo;
+ private final long mRequestId;
private final long mOperationId;
private final int mUserId;
private final IBiometricSensorReceiver mSensorReceiver;
@@ -142,6 +143,8 @@ public final class AuthSession implements IBinder.DeathRecipient {
private @BiometricMultiSensorMode int mMultiSensorMode;
private @MultiSensorState int mMultiSensorState;
private int[] mSensors;
+ // TODO(b/197265902): merge into state
+ private boolean mCancelled;
// For explicit confirmation, do not send to keystore until the user has confirmed
// the authentication.
private byte[] mTokenEscrow;
@@ -162,6 +165,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
@NonNull ClientDeathReceiver clientDeathReceiver,
@NonNull PreAuthInfo preAuthInfo,
@NonNull IBinder token,
+ long requestId,
long operationId,
int userId,
@NonNull IBiometricSensorReceiver sensorReceiver,
@@ -179,6 +183,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mClientDeathReceiver = clientDeathReceiver;
mPreAuthInfo = preAuthInfo;
mToken = token;
+ mRequestId = requestId;
mOperationId = operationId;
mUserId = userId;
mSensorReceiver = sensorReceiver;
@@ -187,6 +192,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
mPromptInfo = promptInfo;
mDebugEnabled = debugEnabled;
mFingerprintSensorProperties = fingerprintSensorProperties;
+ mCancelled = false;
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -233,7 +239,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
Slog.v(TAG, "waiting for cooking for sensor: " + sensor.id);
}
sensor.goToStateWaitingForCookie(requireConfirmation, mToken, mOperationId,
- mUserId, mSensorReceiver, mOpPackageName, cookie,
+ mUserId, mSensorReceiver, mOpPackageName, mRequestId, cookie,
mPromptInfo.isAllowBackgroundAuthentication());
}
}
@@ -255,8 +261,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
} else if (!mPreAuthInfo.eligibleSensors.isEmpty()) {
// Some combination of biometric or biometric|credential is requested
@@ -270,6 +277,11 @@ public final class AuthSession implements IBinder.DeathRecipient {
}
void onCookieReceived(int cookie) {
+ if (mCancelled) {
+ Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie);
+ return;
+ }
+
for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) {
sensor.goToStateCookieReturnedIfCookieMatches(cookie);
}
@@ -301,8 +313,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
mPreAuthInfo.shouldShowCredential(),
requireConfirmation,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
mState = STATE_AUTH_STARTED;
} catch (RemoteException e) {
@@ -369,7 +382,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
final boolean shouldCancel = filter.apply(sensor);
Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel);
if (shouldCancel) {
- sensor.goToStateCancelling(mToken, mOpPackageName);
+ sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to cancel authentication");
@@ -425,8 +438,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
true /* credentialAllowed */,
false /* requireConfirmation */,
mUserId,
- mOpPackageName,
mOperationId,
+ mOpPackageName,
+ mRequestId,
mMultiSensorMode);
} else {
mClientReceiver.onError(modality, error, vendorCode);
@@ -775,6 +789,8 @@ public final class AuthSession implements IBinder.DeathRecipient {
* @return true if this AuthSession is finished, e.g. should be set to null
*/
boolean onCancelAuthSession(boolean force) {
+ mCancelled = true;
+
final boolean authStarted = mState == STATE_AUTH_CALLED
|| mState == STATE_AUTH_STARTED
|| mState == STATE_AUTH_STARTED_UI_SHOWING;
@@ -820,6 +836,7 @@ public final class AuthSession implements IBinder.DeathRecipient {
return Utils.isCredentialRequested(mPromptInfo);
}
+ @VisibleForTesting
boolean allCookiesReceived() {
final int remainingCookies = mPreAuthInfo.numSensorsWaitingForCookie();
Slog.d(TAG, "Remaining cookies: " + remainingCookies);
@@ -839,6 +856,10 @@ public final class AuthSession implements IBinder.DeathRecipient {
return mState;
}
+ long getRequestId() {
+ return mRequestId;
+ }
+
private int statsModality() {
int modality = 0;
@@ -901,7 +922,9 @@ public final class AuthSession implements IBinder.DeathRecipient {
@Override
public String toString() {
return "State: " + mState
+ + ", cancelled: " + mCancelled
+ ", isCrypto: " + isCrypto()
- + ", PreAuthInfo: " + mPreAuthInfo;
+ + ", PreAuthInfo: " + mPreAuthInfo
+ + ", requestId: " + mRequestId;
}
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java
index 8a842b53d8e8..0333c3e247c0 100644
--- a/services/core/java/com/android/server/biometrics/BiometricSensor.java
+++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java
@@ -108,11 +108,11 @@ public abstract class BiometricSensor {
void goToStateWaitingForCookie(boolean requireConfirmation, IBinder token, long sessionId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication)
+ long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mCookie = cookie;
impl.prepareForAuthentication(requireConfirmation, token,
- sessionId, userId, sensorReceiver, opPackageName, mCookie,
+ sessionId, userId, sensorReceiver, opPackageName, requestId, mCookie,
allowBackgroundAuthentication);
mSensorState = STATE_WAITING_FOR_COOKIE;
}
@@ -129,8 +129,9 @@ public abstract class BiometricSensor {
mSensorState = STATE_AUTHENTICATING;
}
- void goToStateCancelling(IBinder token, String opPackageName) throws RemoteException {
- impl.cancelAuthenticationFromService(token, opPackageName);
+ void goToStateCancelling(IBinder token, String opPackageName, long requestId)
+ throws RemoteException {
+ impl.cancelAuthenticationFromService(token, opPackageName, requestId);
mSensorState = STATE_CANCELING;
}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index b1d300cd838e..e0775d48b42f 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -83,6 +83,7 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
/**
* System service that arbitrates the modality for BiometricPrompt to use.
@@ -115,6 +116,7 @@ public class BiometricService extends SystemService {
final SettingObserver mSettingObserver;
private final List<EnabledOnKeyguardCallback> mEnabledOnKeyguardCallbacks;
private final Random mRandom = new Random();
+ @NonNull private final AtomicLong mRequestCounter;
@VisibleForTesting
IStatusBarService mStatusBarService;
@@ -194,6 +196,7 @@ public class BiometricService extends SystemService {
SomeArgs args = (SomeArgs) msg.obj;
handleAuthenticate(
(IBinder) args.arg1 /* token */,
+ (long) args.arg6 /* requestId */,
(long) args.arg2 /* operationId */,
args.argi1 /* userid */,
(IBiometricServiceReceiver) args.arg3 /* receiver */,
@@ -204,7 +207,9 @@ public class BiometricService extends SystemService {
}
case MSG_CANCEL_AUTHENTICATION: {
- handleCancelAuthentication();
+ SomeArgs args = (SomeArgs) msg.obj;
+ handleCancelAuthentication((long) args.arg3 /* requestId */);
+ args.recycle();
break;
}
@@ -683,13 +688,13 @@ public class BiometricService extends SystemService {
}
@Override // Binder call
- public void authenticate(IBinder token, long operationId, int userId,
+ public long authenticate(IBinder token, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
checkInternalPermission();
if (token == null || receiver == null || opPackageName == null || promptInfo == null) {
Slog.e(TAG, "Unable to authenticate, one or more null arguments");
- return;
+ return -1;
}
if (!Utils.isValidAuthenticatorConfig(promptInfo)) {
@@ -706,6 +711,8 @@ public class BiometricService extends SystemService {
}
}
+ final long requestId = mRequestCounter.incrementAndGet();
+
SomeArgs args = SomeArgs.obtain();
args.arg1 = token;
args.arg2 = operationId;
@@ -713,15 +720,23 @@ public class BiometricService extends SystemService {
args.arg3 = receiver;
args.arg4 = opPackageName;
args.arg5 = promptInfo;
+ args.arg6 = requestId;
mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
+
+ return requestId;
}
@Override // Binder call
- public void cancelAuthentication(IBinder token, String opPackageName) {
+ public void cancelAuthentication(IBinder token, String opPackageName, long requestId) {
checkInternalPermission();
- mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION).sendToTarget();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = token;
+ args.arg2 = opPackageName;
+ args.arg3 = requestId;
+
+ mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget();
}
@Override // Binder call
@@ -1111,6 +1126,10 @@ public class BiometricService extends SystemService {
return Settings.Secure.getInt(context.getContentResolver(),
CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0;
}
+
+ public AtomicLong getRequestGenerator() {
+ return new AtomicLong(0);
+ }
}
/**
@@ -1136,6 +1155,7 @@ public class BiometricService extends SystemService {
mEnabledOnKeyguardCallbacks = new ArrayList<>();
mSettingObserver = mInjector.getSettingObserver(context, mHandler,
mEnabledOnKeyguardCallbacks);
+ mRequestCounter = mInjector.getRequestGenerator();
// TODO(b/193089985) This logic lives here (outside of CoexCoordinator) so that it doesn't
// need to depend on context. We can remove this code once the advanced logic is enabled
@@ -1349,7 +1369,7 @@ public class BiometricService extends SystemService {
mCurrentAuthSession.onCookieReceived(cookie);
}
- private void handleAuthenticate(IBinder token, long operationId, int userId,
+ private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo) {
mHandler.post(() -> {
try {
@@ -1360,7 +1380,8 @@ public class BiometricService extends SystemService {
final Pair<Integer, Integer> preAuthStatus = preAuthInfo.getPreAuthenticateStatus();
Slog.d(TAG, "handleAuthenticate: modality(" + preAuthStatus.first
- + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo);
+ + "), status(" + preAuthStatus.second + "), preAuthInfo: " + preAuthInfo
+ + " requestId: " + requestId);
if (preAuthStatus.second == BiometricConstants.BIOMETRIC_SUCCESS) {
// If BIOMETRIC_WEAK or BIOMETRIC_STRONG are allowed, but not enrolled, but
@@ -1372,8 +1393,8 @@ public class BiometricService extends SystemService {
promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
}
- authenticateInternal(token, operationId, userId, receiver, opPackageName,
- promptInfo, preAuthInfo);
+ authenticateInternal(token, requestId, operationId, userId, receiver,
+ opPackageName, promptInfo, preAuthInfo);
} else {
receiver.onError(preAuthStatus.first /* modality */,
preAuthStatus.second /* errorCode */,
@@ -1394,7 +1415,7 @@ public class BiometricService extends SystemService {
* Note that this path is NOT invoked when the BiometricPrompt "Try again" button is pressed.
* In that case, see {@link #handleOnTryAgainPressed()}.
*/
- private void authenticateInternal(IBinder token, long operationId, int userId,
+ private void authenticateInternal(IBinder token, long requestId, long operationId, int userId,
IBiometricServiceReceiver receiver, String opPackageName, PromptInfo promptInfo,
PreAuthInfo preAuthInfo) {
Slog.d(TAG, "Creating authSession with authRequest: " + preAuthInfo);
@@ -1412,9 +1433,9 @@ public class BiometricService extends SystemService {
final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId);
mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver,
- mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, operationId, userId,
- mBiometricSensorReceiver, receiver, opPackageName, promptInfo, debugEnabled,
- mInjector.getFingerprintSensorProperties(getContext()));
+ mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId,
+ operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo,
+ debugEnabled, mInjector.getFingerprintSensorProperties(getContext()));
try {
mCurrentAuthSession.goToInitialState();
} catch (RemoteException e) {
@@ -1422,11 +1443,21 @@ public class BiometricService extends SystemService {
}
}
- private void handleCancelAuthentication() {
+ private void handleCancelAuthentication(long requestId) {
if (mCurrentAuthSession == null) {
Slog.e(TAG, "handleCancelAuthentication: AuthSession is null");
return;
}
+ if (mCurrentAuthSession.getRequestId() != requestId) {
+ // TODO: actually cancel the operation
+ // This can happen if the operation has been queued, but is cancelled before
+ // it reaches the head of the scheduler. Consider it a programming error for now
+ // and ignore it.
+ Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: "
+ + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId
+ + " (ignoring cancellation)");
+ return;
+ }
final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */);
if (finished) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index e5e1385fa605..9764a167fbbf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -26,6 +26,8 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.ArrayList;
+import java.util.List;
import java.util.NoSuchElementException;
/**
@@ -70,26 +72,32 @@ public abstract class BaseClientMonitor extends LoggableMonitor
}
/** Holder for wrapping multiple handlers into a single Callback. */
- protected static class CompositeCallback implements Callback {
+ public static class CompositeCallback implements Callback {
@NonNull
- private final Callback[] mCallbacks;
+ private final List<Callback> mCallbacks;
public CompositeCallback(@NonNull Callback... callbacks) {
- mCallbacks = callbacks;
+ mCallbacks = new ArrayList<>();
+
+ for (Callback callback : callbacks) {
+ if (callback != null) {
+ mCallbacks.add(callback);
+ }
+ }
}
@Override
public final void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- for (int i = 0; i < mCallbacks.length; i++) {
- mCallbacks[i].onClientStarted(clientMonitor);
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onClientStarted(clientMonitor);
}
}
@Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- for (int i = mCallbacks.length - 1; i >= 0; i--) {
- mCallbacks[i].onClientFinished(clientMonitor, success);
+ for (int i = mCallbacks.size() - 1; i >= 0; i--) {
+ mCallbacks.get(i).onClientFinished(clientMonitor, success);
}
}
}
@@ -101,6 +109,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
private final int mSensorId; // sensorId as configured by the framework
@Nullable private IBinder mToken;
+ private long mRequestId;
@Nullable private ClientMonitorCallbackConverter mListener;
// Currently only used for authentication client. The cookie generated by BiometricService
// is never 0.
@@ -154,6 +163,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
mSequentialId = sCount++;
mContext = context;
mToken = token;
+ mRequestId = -1;
mListener = listener;
mTargetUserId = userId;
mOwner = owner;
@@ -206,7 +216,6 @@ public abstract class BaseClientMonitor extends LoggableMonitor
}
mToken = null;
}
- mListener = null;
}
@Override
@@ -255,10 +264,33 @@ public abstract class BaseClientMonitor extends LoggableMonitor
return mToken;
}
- public final int getSensorId() {
+ public int getSensorId() {
return mSensorId;
}
+ /** Unique request id. */
+ public final long getRequestId() {
+ return mRequestId;
+ }
+
+ /** If a unique id has been set via {@link #setRequestId(long)} */
+ public final boolean hasRequestId() {
+ return mRequestId > 0;
+ }
+
+ /**
+ * A unique identifier used to tie this operation to a request (i.e an API invocation).
+ *
+ * Subclasses should not call this method if this operation does not have a direct
+ * correspondence to a request and {@link #hasRequestId()} will return false.
+ */
+ protected final void setRequestId(long id) {
+ if (id <= 0) {
+ throw new IllegalArgumentException("request id must be positive");
+ }
+ mRequestId = id;
+ }
+
@VisibleForTesting
public Callback getCallback() {
return mCallback;
@@ -271,6 +303,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor
+ ", proto=" + getProtoEnum()
+ ", owner=" + getOwnerString()
+ ", cookie=" + getCookie()
+ + ", requestId=" + getRequestId()
+ ", userId=" + getTargetUserId() + "}";
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
index b20316e4c6df..361ec40f2877 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java
@@ -295,6 +295,7 @@ public class BiometricScheduler {
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) {
mHandler.post(() -> {
+ clientMonitor.destroy();
if (mCurrentOperation == null) {
Slog.e(getTag(), "[Finishing] " + clientMonitor
+ " but current operation is null, success: " + success
@@ -642,22 +643,18 @@ public class BiometricScheduler {
/**
* Requests to cancel authentication or detection.
* @param token from the caller, should match the token passed in when requesting authentication
+ * @param requestId the id returned when requesting authentication
*/
- public void cancelAuthenticationOrDetection(IBinder token) {
- if (mCurrentOperation == null) {
- Slog.e(getTag(), "Unable to cancel authentication, null operation");
- return;
- }
- final boolean isCorrectClient = isAuthenticationOrDetectionOperation(mCurrentOperation);
- final boolean tokenMatches = mCurrentOperation.mClientMonitor.getToken() == token;
-
- Slog.d(getTag(), "cancelAuthenticationOrDetection, isCorrectClient: " + isCorrectClient
- + ", tokenMatches: " + tokenMatches);
+ public void cancelAuthenticationOrDetection(IBinder token, long requestId) {
+ Slog.d(getTag(), "cancelAuthenticationOrDetection, requestId: " + requestId
+ + " current: " + mCurrentOperation
+ + " stack size: " + mPendingOperations.size());
- if (isCorrectClient && tokenMatches) {
+ if (mCurrentOperation != null
+ && canCancelAuthOperation(mCurrentOperation, token, requestId)) {
Slog.d(getTag(), "Cancelling: " + mCurrentOperation);
cancelInternal(mCurrentOperation);
- } else if (!isCorrectClient) {
+ } else {
// Look through the current queue for all authentication clients for the specified
// token, and mark them as STATE_WAITING_IN_QUEUE_CANCELING. Note that we're marking
// all of them, instead of just the first one, since the API surface currently doesn't
@@ -665,8 +662,7 @@ public class BiometricScheduler {
// process. However, this generally does not happen anyway, and would be a class of
// bugs on its own.
for (Operation operation : mPendingOperations) {
- if (isAuthenticationOrDetectionOperation(operation)
- && operation.mClientMonitor.getToken() == token) {
+ if (canCancelAuthOperation(operation, token, requestId)) {
Slog.d(getTag(), "Marking " + operation
+ " as STATE_WAITING_IN_QUEUE_CANCELING");
operation.mState = Operation.STATE_WAITING_IN_QUEUE_CANCELING;
@@ -675,10 +671,26 @@ public class BiometricScheduler {
}
}
- private boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
- final boolean isAuthentication = operation.mClientMonitor
- instanceof AuthenticationConsumer;
- final boolean isDetection = operation.mClientMonitor instanceof DetectionConsumer;
+ private static boolean canCancelAuthOperation(Operation operation, IBinder token,
+ long requestId) {
+ // TODO: restrict callers that can cancel without requestId (negative value)?
+ return isAuthenticationOrDetectionOperation(operation)
+ && operation.mClientMonitor.getToken() == token
+ && isMatchingRequestId(operation, requestId);
+ }
+
+ // By default, monitors are not associated with a request id to retain the original
+ // behavior (i.e. if no requestId is explicitly set then assume it matches)
+ private static boolean isMatchingRequestId(Operation operation, long requestId) {
+ return !operation.mClientMonitor.hasRequestId()
+ || operation.mClientMonitor.getRequestId() == requestId;
+ }
+
+ private static boolean isAuthenticationOrDetectionOperation(@NonNull Operation operation) {
+ final boolean isAuthentication =
+ operation.mClientMonitor instanceof AuthenticationConsumer;
+ final boolean isDetection =
+ operation.mClientMonitor instanceof DetectionConsumer;
return isAuthentication || isDetection;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index a15e14b79e30..9191b8b55989 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -31,7 +31,7 @@ import java.util.Arrays;
/**
* A class to keep track of the enrollment state for a given client.
*/
-public abstract class EnrollClient<T> extends AcquisitionClient<T> {
+public abstract class EnrollClient<T> extends AcquisitionClient<T> implements EnrollmentModifier {
private static final String TAG = "Biometrics/EnrollClient";
@@ -40,6 +40,7 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> {
protected final BiometricUtils mBiometricUtils;
private long mEnrollmentStartTimeMs;
+ private final boolean mHasEnrollmentsBeforeStarting;
/**
* @return true if the user has already enrolled the maximum number of templates.
@@ -56,6 +57,18 @@ public abstract class EnrollClient<T> extends AcquisitionClient<T> {
mBiometricUtils = utils;
mHardwareAuthToken = Arrays.copyOf(hardwareAuthToken, hardwareAuthToken.length);
mTimeoutSec = timeoutSec;
+ mHasEnrollmentsBeforeStarting = hasEnrollments();
+ }
+
+ @Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = hasEnrollments();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
}
public void onEnrollResult(BiometricAuthenticator.Identifier identifier, int remaining) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
new file mode 100644
index 000000000000..c2f909b08bb6
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollmentModifier.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+/**
+ * Interface for {@link BaseClientMonitor} subclasses that affect the state of enrollment.
+ */
+public interface EnrollmentModifier {
+
+ /**
+ * Callers should typically check this after
+ * {@link BaseClientMonitor.Callback#onClientFinished(BaseClientMonitor, boolean)}
+ *
+ * @return true if the user has gone from:
+ * 1) none-enrolled --> enrolled
+ * 2) enrolled --> none-enrolled
+ * but NOT any-enrolled --> more-enrolled
+ */
+ boolean hasEnrollmentStateChanged();
+
+ /**
+ * @return true if the user has any enrollments
+ */
+ boolean hasEnrollments();
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 282261e6556f..579dfd69ec66 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -40,7 +40,8 @@ import java.util.Map;
* {@link #onRemoved(BiometricAuthenticator.Identifier, int)} returns true/
*/
public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer {
+ extends HalClientMonitor<T> implements EnumerateConsumer, RemovalConsumer,
+ EnrollmentModifier {
private static final String TAG = "Biometrics/InternalCleanupClient";
@@ -61,6 +62,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
private final List<S> mEnrolledList;
+ private final boolean mHasEnrollmentsBeforeStarting;
private BaseClientMonitor mCurrentTask;
private final Callback mEnumerateCallback = new Callback() {
@@ -115,6 +117,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
mEnrolledList = enrolledList;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
private void startCleanupUnknownHalTemplates() {
@@ -166,6 +169,18 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public void onEnumerationResult(BiometricAuthenticator.Identifier identifier,
int remaining) {
if (!(mCurrentTask instanceof InternalEnumerateClient)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
index 383efce5bb2a..2a6677e55d60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/RemovalClient.java
@@ -33,12 +33,13 @@ import java.util.Map;
* A class to keep track of the remove state for a given client.
*/
public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier, T>
- extends HalClientMonitor<T> implements RemovalConsumer {
+ extends HalClientMonitor<T> implements RemovalConsumer, EnrollmentModifier {
private static final String TAG = "Biometrics/RemovalClient";
private final BiometricUtils<S> mBiometricUtils;
private final Map<Integer, Long> mAuthenticatorIds;
+ private final boolean mHasEnrollmentsBeforeStarting;
public RemovalClient(@NonNull Context context, @NonNull LazyDaemon<T> lazyDaemon,
@NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener,
@@ -49,6 +50,7 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
BiometricsProtoEnums.CLIENT_UNKNOWN);
mBiometricUtils = utils;
mAuthenticatorIds = authenticatorIds;
+ mHasEnrollmentsBeforeStarting = !utils.getBiometricsForUser(context, userId).isEmpty();
}
@Override
@@ -91,6 +93,18 @@ public abstract class RemovalClient<S extends BiometricAuthenticator.Identifier,
}
@Override
+ public boolean hasEnrollmentStateChanged() {
+ final boolean hasEnrollmentsNow = !mBiometricUtils
+ .getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ return hasEnrollmentsNow != mHasEnrollmentsBeforeStarting;
+ }
+
+ @Override
+ public boolean hasEnrollments() {
+ return !mBiometricUtils.getBiometricsForUser(getContext(), getTargetUserId()).isEmpty();
+ }
+
+ @Override
public int getProtoEnum() {
return BiometricsProto.CM_REMOVE;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
index 0bc4f1b54d59..b2fd46d1475d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceAuthenticator.java
@@ -61,10 +61,11 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mFaceService.prepareForAuthentication(mSensorId, requireConfirmation, token, operationId,
- userId, sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+ userId, sensorReceiver, opPackageName, requestId, cookie,
+ allowBackgroundAuthentication);
}
@Override
@@ -73,9 +74,9 @@ public final class FaceAuthenticator extends IBiometricAuthenticator.Stub {
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+ mFaceService.cancelAuthenticationFromService(mSensorId, token, opPackageName, requestId);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 12d6b08b8bcf..675ee545a14f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -250,7 +250,7 @@ public class FaceService extends SystemService {
}
@Override // Binder call
- public void authenticate(final IBinder token, final long operationId, int userId,
+ public long authenticate(final IBinder token, final long operationId, int userId,
final IFaceServiceReceiver receiver, final String opPackageName,
boolean isKeyguardBypassEnabled) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -270,38 +270,38 @@ public class FaceService extends SystemService {
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
- return;
+ return -1;
}
- provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
0 /* cookie */,
new ClientMonitorCallbackConverter(receiver), opPackageName, restricted,
statsClient, isKeyguard, isKeyguardBypassEnabled);
}
@Override // Binder call
- public void detectFace(final IBinder token, final int userId,
+ public long detectFace(final IBinder token, final int userId,
final IFaceServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFace called from non-sysui package: " + opPackageName);
- return;
+ return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFace invoked when user is not encrypted or lockdown");
- return;
+ return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFace");
- return;
+ return -1;
}
- provider.second.scheduleFaceDetect(provider.first, token, userId,
+ return provider.second.scheduleFaceDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@@ -309,8 +309,8 @@ public class FaceService extends SystemService {
@Override // Binder call
public void prepareForAuthentication(int sensorId, boolean requireConfirmation,
IBinder token, long operationId, int userId,
- IBiometricSensorReceiver sensorReceiver, String opPackageName, int cookie,
- boolean allowBackgroundAuthentication) {
+ IBiometricSensorReceiver sensorReceiver, String opPackageName, long requestId,
+ int cookie, boolean allowBackgroundAuthentication) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -322,9 +322,9 @@ public class FaceService extends SystemService {
final boolean isKeyguardBypassEnabled = false; // only valid for keyguard clients
final boolean restricted = true; // BiometricPrompt is always restricted
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
- isKeyguardBypassEnabled);
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
}
@Override // Binder call
@@ -341,7 +341,8 @@ public class FaceService extends SystemService {
}
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ public void cancelAuthentication(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
@@ -350,11 +351,12 @@ public class FaceService extends SystemService {
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
- public void cancelFaceDetect(final IBinder token, final String opPackageName) {
+ public void cancelFaceDetect(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFaceDetect called from non-sysui package: "
@@ -368,12 +370,12 @@ public class FaceService extends SystemService {
return;
}
- provider.second.cancelFaceDetect(provider.first, token);
+ provider.second.cancelFaceDetect(provider.first, token, requestId);
}
@Override // Binder call
public void cancelAuthenticationFromService(int sensorId, final IBinder token,
- final String opPackageName) {
+ final String opPackageName, final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -382,7 +384,7 @@ public class FaceService extends SystemService {
return;
}
- provider.cancelAuthentication(sensorId, token);
+ provider.cancelAuthentication(sensorId, token, requestId);
}
@Override // Binder call
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
index 93ab1b65edff..e099ba372b05 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/ServiceProvider.java
@@ -101,18 +101,23 @@ public interface ServiceProvider {
void cancelEnrollment(int sensorId, @NonNull IBinder token);
- void scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
+ long scheduleFaceDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
int statsClient);
- void cancelFaceDetect(int sensorId, @NonNull IBinder token);
+ void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId);
- void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
- void cancelAuthentication(int sensorId, @NonNull IBinder token);
+ void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled);
+
+ void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
void scheduleRemove(int sensorId, @NonNull IBinder token, int faceId, int userId,
@NonNull IFaceServiceReceiver receiver, @NonNull String opPackageName);
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 d66a27920f49..cbceba6cc959 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
@@ -65,7 +65,8 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
@FaceManager.FaceAcquired private int mLastAcquire = FaceManager.FACE_ACQUIRED_UNKNOWN;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient, @NonNull UsageStats usageStats,
@@ -76,6 +77,7 @@ class FaceAuthenticationClient extends AuthenticationClient<ISession> implements
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
+ setRequestId(requestId);
mUsageStats = usageStats;
mLockoutCache = lockoutCache;
mNotificationManager = context.getSystemService(NotificationManager.class);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
index 1e73ac528f08..2ef0911658b1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceDetectClient.java
@@ -43,11 +43,13 @@ public class FaceDetectClient extends AcquisitionClient<ISession> implements Det
@Nullable private ICancellationSignal mCancellationSignal;
public FaceDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId, boolean isStrongBiometric, int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FACE,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
}
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 718b9da968f5..4bae7756abe0 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
@@ -65,6 +65,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Provider for a single instance of the {@link IFace} HAL.
@@ -83,6 +84,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
@NonNull private final UsageStats mUsageStats;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@NonNull private final BiometricTaskStackListener mTaskStackListener;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@Nullable private IFace mDaemon;
@@ -110,8 +113,8 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mSensors.valueAt(i).getScheduler()
- .cancelAuthenticationOrDetection(client.getToken());
+ mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
}
@@ -356,34 +359,39 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
@Override
- public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+ public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
int userId, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
+
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceDetectClient client = new FaceDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId, opPackageName,
+ mSensors.get(sensorId).getLazySession(),
+ token, id, callback, userId, opPackageName,
sensorId, isStrongBiometric, statsClient);
scheduleForSensor(sensorId, client);
});
+
+ return id;
}
@Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
+ mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+ userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mUsageStats, mSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, isKeyguardBypassEnabled);
@@ -392,9 +400,23 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+ opPackageName, id, restricted, statsClient,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index d05333db7532..f4dcbbba21d7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -87,6 +87,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Supports a single instance of the {@link android.hardware.biometrics.face.V1_0} or its extended
@@ -115,6 +116,8 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@NonNull private final Map<Integer, Long> mAuthenticatorIds;
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
private final int mSensorId;
private final List<Long> mGeneratedChallengeCount = new ArrayList<>();
@@ -605,7 +608,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
@Override
- public void scheduleFaceDetect(int sensorId, @NonNull IBinder token,
+ public long scheduleFaceDetect(int sensorId, @NonNull IBinder token,
int userId, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, int statsClient) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
@@ -613,7 +616,7 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
}
@Override
- public void cancelFaceDetect(int sensorId, @NonNull IBinder token) {
+ public void cancelFaceDetect(int sensorId, @NonNull IBinder token, long requestId) {
throw new IllegalStateException("Face detect not supported by IBiometricsFace@1.0. Did you"
+ "forget to check the supportsFaceDetection flag?");
}
@@ -621,26 +624,38 @@ public class Face10 implements IHwBinder.DeathRecipient, ServiceProvider {
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
- @NonNull String opPackageName, boolean restricted, int statsClient,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorId);
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
- mLazyDaemon, token, receiver, userId, operationId, restricted, opPackageName,
- cookie, false /* requireConfirmation */, mSensorId, isStrongBiometric,
- statsClient, mLockoutTracker, mUsageStats, allowBackgroundAuthentication,
- isKeyguardBypassEnabled);
+ mLazyDaemon, token, requestId, receiver, userId, operationId, restricted,
+ opPackageName, cookie, false /* requireConfirmation */, mSensorId,
+ isStrongBiometric, statsClient, mLockoutTracker, mUsageStats,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
mScheduler.scheduleClientMonitor(client);
});
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
- mHandler.post(() -> {
- mScheduler.cancelAuthenticationOrDetection(token);
- });
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter receiver,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication, boolean isKeyguardBypassEnabled) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, receiver,
+ opPackageName, id, restricted, statsClient,
+ allowBackgroundAuthentication, isKeyguardBypassEnabled);
+
+ return id;
+ }
+
+ @Override
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
index 33950af2216f..40f2801541d3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/FaceAuthenticationClient.java
@@ -57,7 +57,8 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
private int mLastAcquire;
FaceAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFace> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFace> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, String owner, int cookie, boolean requireConfirmation, int sensorId,
boolean isStrongBiometric, int statsClient, @NonNull LockoutTracker lockoutTracker,
@@ -68,6 +69,7 @@ class FaceAuthenticationClient extends AuthenticationClient<IBiometricsFace> {
BiometricsProtoEnums.MODALITY_FACE, statsClient, null /* taskStackListener */,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
isKeyguardBypassEnabled);
+ setRequestId(requestId);
mUsageStats = usageStats;
final Resources resources = getContext().getResources();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
index 1e5942930b01..52d887a75216 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintAuthenticator.java
@@ -61,10 +61,10 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long operationId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
mFingerprintService.prepareForAuthentication(mSensorId, token, operationId, userId,
- sensorReceiver, opPackageName, cookie, allowBackgroundAuthentication);
+ sensorReceiver, opPackageName, requestId, cookie, allowBackgroundAuthentication);
}
@Override
@@ -73,9 +73,10 @@ public final class FingerprintAuthenticator extends IBiometricAuthenticator.Stub
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
- mFingerprintService.cancelAuthenticationFromService(mSensorId, token, opPackageName);
+ mFingerprintService.cancelAuthenticationFromService(
+ mSensorId, token, opPackageName, requestId);
}
@Override
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 183fabdc2a7b..f35bb7ffd26b 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
@@ -35,6 +35,7 @@ import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.BiometricsProtoEnums;
@@ -62,11 +63,13 @@ import android.os.Build;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.util.EventLog;
import android.util.Pair;
@@ -111,6 +114,7 @@ public class FingerprintService extends SystemService {
private final FingerprintServiceWrapper mServiceWrapper;
@NonNull private final List<ServiceProvider> mServiceProviders;
@NonNull private final FingerprintStateCallback mFingerprintStateCallback;
+ @NonNull private final Handler mHandler;
@GuardedBy("mLock")
@NonNull private final RemoteCallbackList<IFingerprintAuthenticatorsRegisteredCallback>
@@ -125,6 +129,37 @@ public class FingerprintService extends SystemService {
*/
public void registerFingerprintStateListener(@NonNull IFingerprintStateListener listener) {
mFingerprintStateCallback.registerFingerprintStateListener(listener);
+ broadcastCurrentEnrollmentState(listener);
+ }
+
+ /**
+ * @param listener if non-null, notifies only this listener. if null, notifies all listeners
+ * in {@link FingerprintStateCallback}. This is slightly ugly, but reduces
+ * redundant code.
+ */
+ private void broadcastCurrentEnrollmentState(@Nullable IFingerprintStateListener listener) {
+ final UserManager um = UserManager.get(getContext());
+ synchronized (mLock) {
+ // Update the new listener with current state of all sensors
+ for (FingerprintSensorPropertiesInternal prop : mSensorProps) {
+ final ServiceProvider provider = getProviderForSensor(prop.sensorId);
+ for (UserInfo userInfo : um.getAliveUsers()) {
+ final boolean enrolled = !provider
+ .getEnrolledFingerprints(prop.sensorId, userInfo.id).isEmpty();
+
+ // Defer this work and allow the loop to release the lock sooner
+ mHandler.post(() -> {
+ if (listener != null) {
+ mFingerprintStateCallback.notifyFingerprintEnrollmentStateChanged(
+ listener, userInfo.id, prop.sensorId, enrolled);
+ } else {
+ mFingerprintStateCallback.notifyAllFingerprintEnrollmentStateChanged(
+ userInfo.id, prop.sensorId, enrolled);
+ }
+ });
+ }
+ }
+ }
}
/**
@@ -143,8 +178,7 @@ public class FingerprintService extends SystemService {
return null;
}
- return provider.createTestSession(sensorId, callback, mFingerprintStateCallback,
- opPackageName);
+ return provider.createTestSession(sensorId, callback, opPackageName);
}
@Override
@@ -227,7 +261,7 @@ public class FingerprintService extends SystemService {
}
provider.second.scheduleEnroll(provider.first, token, hardwareAuthToken, userId,
- receiver, opPackageName, enrollReason, mFingerprintStateCallback);
+ receiver, opPackageName, enrollReason);
}
@Override // Binder call
@@ -245,7 +279,7 @@ public class FingerprintService extends SystemService {
@SuppressWarnings("deprecation")
@Override // Binder call
- public void authenticate(final IBinder token, final long operationId,
+ public long authenticate(final IBinder token, final long operationId,
final int sensorId, final int userId, final IFingerprintServiceReceiver receiver,
final String opPackageName) {
final int callingUid = Binder.getCallingUid();
@@ -255,7 +289,7 @@ public class FingerprintService extends SystemService {
if (!canUseFingerprint(opPackageName, true /* requireForeground */, callingUid,
callingPid, callingUserId)) {
Slog.w(TAG, "Authenticate rejecting package: " + opPackageName);
- return;
+ return -1;
}
// Keyguard check must be done on the caller's binder identity, since it also checks
@@ -270,7 +304,7 @@ public class FingerprintService extends SystemService {
// SafetyNet for b/79776455
EventLog.writeEvent(0x534e4554, "79776455");
Slog.e(TAG, "Authenticate invoked when user is encrypted or lockdown");
- return;
+ return -1;
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -290,7 +324,7 @@ public class FingerprintService extends SystemService {
}
if (provider == null) {
Slog.w(TAG, "Null provider for authenticate");
- return;
+ return -1;
}
final FingerprintSensorPropertiesInternal sensorProps =
@@ -299,18 +333,17 @@ public class FingerprintService extends SystemService {
&& sensorProps != null && sensorProps.isAnyUdfpsType()) {
identity = Binder.clearCallingIdentity();
try {
- authenticateWithPrompt(operationId, sensorProps, userId, receiver);
+ return authenticateWithPrompt(operationId, sensorProps, userId, receiver);
} finally {
Binder.restoreCallingIdentity(identity);
}
- } else {
- provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
- 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
- restricted, statsClient, isKeyguard, mFingerprintStateCallback);
}
+ return provider.second.scheduleAuthenticate(provider.first, token, operationId, userId,
+ 0 /* cookie */, new ClientMonitorCallbackConverter(receiver), opPackageName,
+ restricted, statsClient, isKeyguard);
}
- private void authenticateWithPrompt(
+ private long authenticateWithPrompt(
final long operationId,
@NonNull final FingerprintSensorPropertiesInternal props,
final int userId,
@@ -387,41 +420,41 @@ public class FingerprintService extends SystemService {
}
};
- biometricPrompt.authenticateUserForOperation(
+ return biometricPrompt.authenticateUserForOperation(
new CancellationSignal(), executor, promptCallback, userId, operationId);
}
@Override
- public void detectFingerprint(final IBinder token, final int userId,
+ public long detectFingerprint(final IBinder token, final int userId,
final IFingerprintServiceReceiver receiver, final String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "detectFingerprint called from non-sysui package: " + opPackageName);
- return;
+ return -1;
}
if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) {
// If this happens, something in KeyguardUpdateMonitor is wrong. This should only
// ever be invoked when the user is encrypted or lockdown.
Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown");
- return;
+ return -1;
}
final Pair<Integer, ServiceProvider> provider = getSingleProvider();
if (provider == null) {
Slog.w(TAG, "Null provider for detectFingerprint");
- return;
+ return -1;
}
- provider.second.scheduleFingerDetect(provider.first, token, userId,
+ return provider.second.scheduleFingerDetect(provider.first, token, userId,
new ClientMonitorCallbackConverter(receiver), opPackageName,
- BiometricsProtoEnums.CLIENT_KEYGUARD, mFingerprintStateCallback);
+ BiometricsProtoEnums.CLIENT_KEYGUARD);
}
@Override // Binder call
public void prepareForAuthentication(int sensorId, IBinder token, long operationId,
int userId, IBiometricSensorReceiver sensorReceiver, String opPackageName,
- int cookie, boolean allowBackgroundAuthentication) {
+ long requestId, int cookie, boolean allowBackgroundAuthentication) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
final ServiceProvider provider = getProviderForSensor(sensorId);
@@ -432,9 +465,9 @@ public class FingerprintService extends SystemService {
final boolean restricted = true; // BiometricPrompt is always restricted
provider.scheduleAuthenticate(sensorId, token, operationId, userId, cookie,
- new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, restricted,
- BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT, allowBackgroundAuthentication,
- mFingerprintStateCallback);
+ new ClientMonitorCallbackConverter(sensorReceiver), opPackageName, requestId,
+ restricted, BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
+ allowBackgroundAuthentication);
}
@Override // Binder call
@@ -452,7 +485,8 @@ public class FingerprintService extends SystemService {
@Override // Binder call
- public void cancelAuthentication(final IBinder token, final String opPackageName) {
+ public void cancelAuthentication(final IBinder token, final String opPackageName,
+ long requestId) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final int callingUserId = UserHandle.getCallingUserId();
@@ -469,11 +503,12 @@ public class FingerprintService extends SystemService {
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
- public void cancelFingerprintDetect(final IBinder token, final String opPackageName) {
+ public void cancelFingerprintDetect(final IBinder token, final String opPackageName,
+ final long requestId) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
if (!Utils.isKeyguard(getContext(), opPackageName)) {
Slog.w(TAG, "cancelFingerprintDetect called from non-sysui package: "
@@ -489,12 +524,12 @@ public class FingerprintService extends SystemService {
return;
}
- provider.second.cancelAuthentication(provider.first, token);
+ provider.second.cancelAuthentication(provider.first, token, requestId);
}
@Override // Binder call
public void cancelAuthenticationFromService(final int sensorId, final IBinder token,
- final String opPackageName) {
+ final String opPackageName, final long requestId) {
Utils.checkPermission(getContext(), MANAGE_BIOMETRIC);
@@ -506,7 +541,7 @@ public class FingerprintService extends SystemService {
return;
}
- provider.cancelAuthentication(sensorId, token);
+ provider.cancelAuthentication(sensorId, token, requestId);
}
@Override // Binder call
@@ -686,27 +721,6 @@ public class FingerprintService extends SystemService {
.isEmpty();
}
- @Override // Binder call
- public boolean hasEnrolledTemplatesForAnySensor(int userId,
- @NonNull List<FingerprintSensorPropertiesInternal> sensors,
- @NonNull String opPackageName) {
- Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
-
- for (FingerprintSensorPropertiesInternal prop : sensors) {
- final ServiceProvider provider = getProviderForSensor(prop.sensorId);
- if (provider == null) {
- Slog.w(TAG, "Null provider for sensorId: " + prop.sensorId
- + ", caller: " + opPackageName);
- continue;
- }
-
- if (!provider.getEnrolledFingerprints(prop.sensorId, userId).isEmpty()) {
- return true;
- }
- }
- return false;
- }
-
public boolean hasEnrolledFingerprints(int sensorId, int userId, String opPackageName) {
Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
@@ -796,10 +810,12 @@ public class FingerprintService extends SystemService {
&& Settings.Secure.getIntForUser(getContext().getContentResolver(),
Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
UserHandle.USER_CURRENT) != 0) {
- fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
} else {
- fingerprint21 = Fingerprint21.newInstance(getContext(), hidlSensor,
+ fingerprint21 = Fingerprint21.newInstance(getContext(),
+ mFingerprintStateCallback, hidlSensor,
mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
mServiceProviders.add(fingerprint21);
@@ -822,8 +838,9 @@ public class FingerprintService extends SystemService {
try {
final SensorProps[] props = fp.getSensorProps();
final FingerprintProvider provider =
- new FingerprintProvider(getContext(), props, instance,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ new FingerprintProvider(getContext(), mFingerprintStateCallback, props,
+ instance, mLockoutResetDispatcher,
+ mGestureAvailabilityDispatcher);
mServiceProviders.add(provider);
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
@@ -877,6 +894,7 @@ public class FingerprintService extends SystemService {
}
}
+ broadcastCurrentEnrollmentState(null); // broadcasts to all listeners
broadcastAllAuthenticatorsRegistered();
});
}
@@ -974,6 +992,7 @@ public class FingerprintService extends SystemService {
mFingerprintStateCallback = new FingerprintStateCallback();
mAuthenticatorsRegisteredCallbacks = new RemoteCallbackList<>();
mSensorProps = new ArrayList<>();
+ mHandler = new Handler(Looper.getMainLooper());
}
// Notifies the callbacks that all of the authenticators have been registered and removes the
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
index 5f998d81fb83..0050a895034f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallback.java
@@ -23,6 +23,7 @@ import static android.hardware.fingerprint.FingerprintStateListener.STATE_IDLE;
import static android.hardware.fingerprint.FingerprintStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
+import android.content.Context;
import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IFingerprintStateListener;
import android.os.RemoteException;
@@ -31,6 +32,9 @@ import android.util.Slog;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.sensors.AuthenticationClient;
import com.android.server.biometrics.sensors.BaseClientMonitor;
+import com.android.server.biometrics.sensors.EnrollClient;
+import com.android.server.biometrics.sensors.EnrollmentModifier;
+import com.android.server.biometrics.sensors.RemovalConsumer;
import com.android.server.biometrics.sensors.fingerprint.hidl.FingerprintEnrollClient;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -39,9 +43,11 @@ import java.util.concurrent.CopyOnWriteArrayList;
* A callback for receiving notifications about changes in fingerprint state.
*/
public class FingerprintStateCallback implements BaseClientMonitor.Callback {
- private @FingerprintStateListener.State int mFingerprintState;
+
@NonNull private final CopyOnWriteArrayList<IFingerprintStateListener>
- mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+ mFingerprintStateListeners = new CopyOnWriteArrayList<>();
+
+ private @FingerprintStateListener.State int mFingerprintState;
public FingerprintStateCallback() {
mFingerprintState = STATE_IDLE;
@@ -54,8 +60,9 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback {
@Override
public void onClientStarted(@NonNull BaseClientMonitor client) {
final int previousFingerprintState = mFingerprintState;
+
if (client instanceof AuthenticationClient) {
- AuthenticationClient authClient = (AuthenticationClient) client;
+ final AuthenticationClient<?> authClient = (AuthenticationClient<?>) client;
if (authClient.isKeyguard()) {
mFingerprintState = STATE_KEYGUARD_AUTH;
} else if (authClient.isBiometricPrompt()) {
@@ -70,6 +77,7 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback {
"Other authentication client: " + Utils.getClientName(client));
mFingerprintState = STATE_IDLE;
}
+
Slog.d(FingerprintService.TAG, "Fps state updated from " + previousFingerprintState
+ " to " + mFingerprintState + ", client " + client);
notifyFingerprintStateListeners(mFingerprintState);
@@ -81,6 +89,18 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback {
Slog.d(FingerprintService.TAG,
"Client finished, fps state updated to " + mFingerprintState + ", client "
+ client);
+
+ if (client instanceof EnrollmentModifier) {
+ EnrollmentModifier enrollmentModifier = (EnrollmentModifier) client;
+ final boolean enrollmentStateChanged = enrollmentModifier.hasEnrollmentStateChanged();
+ Slog.d(FingerprintService.TAG, "Enrollment state changed: " + enrollmentStateChanged);
+ if (enrollmentStateChanged) {
+ notifyAllFingerprintEnrollmentStateChanged(client.getTargetUserId(),
+ client.getSensorId(),
+ enrollmentModifier.hasEnrollments());
+ }
+ }
+
notifyFingerprintStateListeners(mFingerprintState);
}
@@ -95,6 +115,32 @@ public class FingerprintStateCallback implements BaseClientMonitor.Callback {
}
/**
+ * This should be invoked when:
+ * 1) Enrolled --> None-enrolled
+ * 2) None-enrolled --> enrolled
+ * 3) HAL becomes ready
+ * 4) Listener is registered
+ */
+ void notifyAllFingerprintEnrollmentStateChanged(int userId, int sensorId,
+ boolean hasEnrollments) {
+ for (IFingerprintStateListener listener : mFingerprintStateListeners) {
+ notifyFingerprintEnrollmentStateChanged(listener, userId, sensorId, hasEnrollments);
+ }
+ }
+
+ /**
+ * Notifies the listener of enrollment state changes.
+ */
+ void notifyFingerprintEnrollmentStateChanged(@NonNull IFingerprintStateListener listener,
+ int userId, int sensorId, boolean hasEnrollments) {
+ try {
+ listener.onEnrollmentsChanged(userId, sensorId, hasEnrollments);
+ } catch (RemoteException e) {
+ Slog.e(FingerprintService.TAG, "Remote exception", e);
+ }
+ }
+
+ /**
* Enables clients to register a FingerprintStateListener. Used by FingerprintService to forward
* updates in fingerprint sensor state to the SideFpNsEventHandler
* @param listener
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 706ac1013746..1772f814dd10 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -90,25 +90,27 @@ public interface ServiceProvider {
*/
void scheduleEnroll(int sensorId, @NonNull IBinder token, @NonNull byte[] hardwareAuthToken,
int userId, @NonNull IFingerprintServiceReceiver receiver,
- @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ @NonNull String opPackageName, @FingerprintManager.EnrollReason int enrollReason);
void cancelEnrollment(int sensorId, @NonNull IBinder token);
- void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ int statsClient);
void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication);
+
+ long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId, int userId,
+ int cookie, @NonNull ClientMonitorCallbackConverter callback,
@NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback);
+ boolean allowBackgroundAuthentication);
void startPreparedClient(int sensorId, int cookie);
- void cancelAuthentication(int sensorId, @NonNull IBinder token);
+ void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId);
void scheduleRemove(int sensorId, @NonNull IBinder token,
@NonNull IFingerprintServiceReceiver receiver, int fingerId, int userId,
@@ -163,6 +165,5 @@ public interface ServiceProvider {
@NonNull
ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
index 29f2f20b8a75..2b50b96c69a1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@ class BiometricTestSessionImpl extends ITestSession.Stub {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 37ee76adeece..b405be75bdf1 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -61,7 +61,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<ISession> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<ISession> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, int statsClient,
@@ -74,6 +75,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
+ setRequestId(requestId);
mLockoutCache = lockoutCache;
mUdfpsOverlayController = udfpsOverlayController;
mSensorProps = sensorProps;
@@ -164,13 +166,17 @@ class FingerprintAuthenticationClient extends AuthenticationClient<ISession> imp
@Override
protected void stopHalOperation() {
UdfpsHelper.hideUdfpsOverlay(getSensorId(), mUdfpsOverlayController);
- try {
- mCancellationSignal.cancel();
- } catch (RemoteException e) {
- Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
- 0 /* vendorCode */);
- mCallback.onClientFinished(this, false /* success */);
+ if (mCancellationSignal != null) {
+ try {
+ mCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception", e);
+ onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ 0 /* vendorCode */);
+ mCallback.onClientFinished(this, false /* success */);
+ }
+ } else {
+ Slog.e(TAG, "cancellation signal was null");
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index c5dc44988612..da91cdd981b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -47,13 +47,15 @@ class FingerprintDetectClient extends AcquisitionClient<ISession> implements Det
@Nullable private ICancellationSignal mCancellationSignal;
FingerprintDetectClient(@NonNull Context context, @NonNull LazyDaemon<ISession> lazyDaemon,
- @NonNull IBinder token, @NonNull ClientMonitorCallbackConverter listener, int userId,
+ @NonNull IBinder token, long requestId,
+ @NonNull ClientMonitorCallbackConverter listener, int userId,
@NonNull String owner, int sensorId,
@Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric,
int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mIsStrongBiometric = isStrongBiometric;
mUdfpsOverlayController = udfpsOverlayController;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 102b07493134..ca83dda3bc4e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -71,6 +71,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Provider for a single instance of the {@link IFingerprint} HAL.
@@ -81,6 +82,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
private boolean mTestHalEnabled;
@NonNull private final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
@NonNull private final String mHalInstanceName;
@NonNull @VisibleForTesting
final SparseArray<Sensor> mSensors; // Map of sensors that this HAL supports
@@ -88,6 +90,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@NonNull private final LockoutResetDispatcher mLockoutResetDispatcher;
@NonNull private final ActivityTaskManager mActivityTaskManager;
@NonNull private final BiometricTaskStackListener mTaskStackListener;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@Nullable private IFingerprint mDaemon;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@@ -118,8 +122,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
&& !client.isAlreadyDone()) {
Slog.e(getTag(), "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mSensors.valueAt(i).getScheduler()
- .cancelAuthenticationOrDetection(client.getToken());
+ mSensors.valueAt(i).getScheduler().cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
}
@@ -127,10 +131,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
}
- public FingerprintProvider(@NonNull Context context, @NonNull SensorProps[] props,
- @NonNull String halInstanceName, @NonNull LockoutResetDispatcher lockoutResetDispatcher,
+ public FingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
+ @NonNull SensorProps[] props, @NonNull String halInstanceName,
+ @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mHalInstanceName = halInstanceName;
mSensors = new SparseArray<>();
mHandler = new Handler(Looper.getMainLooper());
@@ -332,8 +339,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
final int maxTemplatesPerUser = mSensors.get(sensorId).getSensorProperties()
.maxEnrollmentsPerUser;
@@ -347,13 +353,13 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
scheduleLoadAuthenticatorIdsForUser(sensorId, userId);
scheduleInvalidationRequest(sensorId, userId);
@@ -369,48 +375,62 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
- public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter callback, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mSensors.get(sensorId).getLazySession(), token, callback, userId,
+ mSensors.get(sensorId).getLazySession(), token, id, callback, userId,
opPackageName, sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
+
+ return id;
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
final boolean isStrongBiometric = Utils.isStrongBiometric(sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mSensors.get(sensorId).getLazySession(), token, callback, userId,
- operationId, restricted, opPackageName, cookie,
+ mContext, mSensors.get(sensorId).getLazySession(), token, requestId, callback,
+ userId, operationId, restricted, opPackageName, cookie,
false /* requireConfirmation */, sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, allowBackgroundAuthentication,
mSensors.get(sensorId).getSensorProperties());
- scheduleForSensor(sensorId, client, fingerprintStateCallback);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@Override
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter callback,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, callback,
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler().startPreparedClient(cookie));
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
mHandler.post(() -> mSensors.get(sensorId).getScheduler()
- .cancelAuthenticationOrDetection(token));
+ .cancelAuthenticationOrDetection(token, requestId));
}
@Override
@@ -444,7 +464,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client);
+ scheduleForSensor(sensorId, client, mFingerprintStateCallback);
});
}
@@ -459,7 +479,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mContext.getOpPackageName(), sensorId, enrolledList,
FingerprintUtils.getInstance(sensorId),
mSensors.get(sensorId).getAuthenticatorIds());
- scheduleForSensor(sensorId, client, callback);
+ scheduleForSensor(sensorId, client, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
});
}
@@ -604,9 +625,8 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
- return mSensors.get(sensorId).createTestSession(callback, fingerprintStateCallback);
+ return mSensors.get(sensorId).createTestSession(callback, mFingerprintStateCallback);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
index c00daffb867f..79c6b1b30d5b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/BiometricTestSessionImpl.java
@@ -143,8 +143,7 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
Utils.checkPermission(mContext, TEST_BIOMETRIC);
mFingerprint21.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
- mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL,
- mFingerprintStateCallback);
+ mContext.getOpPackageName(), FingerprintManager.ENROLL_ENROLL);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 2f5b5c7b9727..d2882aa4094c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -88,6 +88,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Supports a single instance of the {@link android.hardware.biometrics.fingerprint.V2_1} or
@@ -101,6 +102,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
private boolean mTestHalEnabled;
final Context mContext;
+ @NonNull private final FingerprintStateCallback mFingerprintStateCallback;
private final ActivityTaskManager mActivityTaskManager;
@NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
private final BiometricScheduler mScheduler;
@@ -115,6 +117,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
@NonNull private final HalResultController mHalResultController;
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
+ // for requests that do not use biometric prompt
+ @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
private final boolean mIsUdfps;
private final int mSensorId;
@@ -142,7 +146,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
&& !client.isAlreadyDone()) {
Slog.e(TAG, "Stopping background authentication, top: "
+ topPackage + " currentClient: " + client);
- mScheduler.cancelAuthenticationOrDetection(client.getToken());
+ mScheduler.cancelAuthenticationOrDetection(
+ client.getToken(), client.getRequestId());
}
}
});
@@ -313,11 +318,13 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
Fingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
mContext = context;
+ mFingerprintStateCallback = fingerprintStateCallback;
mSensorProperties = sensorProps;
mSensorId = sensorProps.sensorId;
@@ -347,6 +354,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
public static Fingerprint21 newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -358,8 +366,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
final HalResultController controller = new HalResultController(sensorProps.sensorId,
context, handler,
scheduler);
- return new Fingerprint21(context, sensorProps, scheduler, handler, lockoutResetDispatcher,
- controller);
+ return new Fingerprint21(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
@@ -553,8 +561,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
public void scheduleEnroll(int sensorId, @NonNull IBinder token,
@NonNull byte[] hardwareAuthToken, int userId,
@NonNull IFingerprintServiceReceiver receiver, @NonNull String opPackageName,
- @FingerprintManager.EnrollReason int enrollReason,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @FingerprintManager.EnrollReason int enrollReason) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
@@ -566,13 +573,13 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mScheduler.scheduleClientMonitor(client, new BaseClientMonitor.Callback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
- fingerprintStateCallback.onClientStarted(clientMonitor);
+ mFingerprintStateCallback.onClientStarted(clientMonitor);
}
@Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
- fingerprintStateCallback.onClientFinished(clientMonitor, success);
+ mFingerprintStateCallback.onClientFinished(clientMonitor, success);
if (success) {
// Update authenticatorIds
scheduleUpdateActiveUserWithoutHandler(clientMonitor.getTargetUserId(),
@@ -591,51 +598,65 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
- public void scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
+ public long scheduleFingerDetect(int sensorId, @NonNull IBinder token, int userId,
@NonNull ClientMonitorCallbackConverter listener, @NonNull String opPackageName,
- int statsClient,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ int statsClient) {
+ final long id = mRequestCounter.incrementAndGet();
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
- mLazyDaemon, token, listener, userId, opPackageName,
+ mLazyDaemon, token, id, listener, userId, opPackageName,
mSensorProperties.sensorId, mUdfpsOverlayController, isStrongBiometric,
statsClient);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
+
+ return id;
}
@Override
public void scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
- @NonNull String opPackageName, boolean restricted, int statsClient,
- boolean allowBackgroundAuthentication,
- @NonNull FingerprintStateCallback fingerprintStateCallback) {
+ @NonNull String opPackageName, long requestId, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
mHandler.post(() -> {
scheduleUpdateActiveUserWithoutHandler(userId);
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
- mContext, mLazyDaemon, token, listener, userId, operationId, restricted,
- opPackageName, cookie, false /* requireConfirmation */,
+ mContext, mLazyDaemon, token, requestId, listener, userId, operationId,
+ restricted, opPackageName, cookie, false /* requireConfirmation */,
mSensorProperties.sensorId, isStrongBiometric, statsClient,
mTaskStackListener, mLockoutTracker, mUdfpsOverlayController,
allowBackgroundAuthentication, mSensorProperties);
- mScheduler.scheduleClientMonitor(client, fingerprintStateCallback);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@Override
+ public long scheduleAuthenticate(int sensorId, @NonNull IBinder token, long operationId,
+ int userId, int cookie, @NonNull ClientMonitorCallbackConverter listener,
+ @NonNull String opPackageName, boolean restricted, int statsClient,
+ boolean allowBackgroundAuthentication) {
+ final long id = mRequestCounter.incrementAndGet();
+
+ scheduleAuthenticate(sensorId, token, operationId, userId, cookie, listener,
+ opPackageName, id, restricted, statsClient, allowBackgroundAuthentication);
+
+ return id;
+ }
+
+ @Override
public void startPreparedClient(int sensorId, int cookie) {
mHandler.post(() -> mScheduler.startPreparedClient(cookie));
}
@Override
- public void cancelAuthentication(int sensorId, @NonNull IBinder token) {
+ public void cancelAuthentication(int sensorId, @NonNull IBinder token, long requestId) {
Slog.d(TAG, "cancelAuthentication, sensorId: " + sensorId);
- mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token));
+ mHandler.post(() -> mScheduler.cancelAuthenticationOrDetection(token, requestId));
}
@Override
@@ -649,7 +670,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), fingerId,
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -666,7 +687,7 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
0 /* fingerprintId */, userId, opPackageName,
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId, mAuthenticatorIds);
- mScheduler.scheduleClientMonitor(client);
+ mScheduler.scheduleClientMonitor(client, mFingerprintStateCallback);
});
}
@@ -688,7 +709,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
@Override
public void scheduleInternalCleanup(int sensorId, int userId,
@Nullable BaseClientMonitor.Callback callback) {
- scheduleInternalCleanup(userId, callback);
+ scheduleInternalCleanup(userId, new BaseClientMonitor.CompositeCallback(callback,
+ mFingerprintStateCallback));
}
@Override
@@ -896,9 +918,8 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
@NonNull
@Override
public ITestSession createTestSession(int sensorId, @NonNull ITestSessionCallback callback,
- @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull String opPackageName) {
return new BiometricTestSessionImpl(mContext, mSensorProperties.sensorId, callback,
- fingerprintStateCallback, this, mHalResultController);
+ mFingerprintStateCallback, this, mHalResultController);
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index 24ce8676235a..79ad8e1a5c70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -26,6 +26,7 @@ import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintStateListener;
import android.hardware.fingerprint.IUdfpsOverlayController;
import android.os.Handler;
import android.os.IBinder;
@@ -42,6 +43,7 @@ import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import java.util.ArrayList;
@@ -270,6 +272,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
}
public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
@@ -280,8 +283,8 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
new TestableBiometricScheduler(TAG, gestureAvailabilityDispatcher);
final MockHalResultController controller =
new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
- return new Fingerprint21UdfpsMock(context, sensorProps, scheduler, handler,
- lockoutResetDispatcher, controller);
+ return new Fingerprint21UdfpsMock(context, fingerprintStateCallback, sensorProps, scheduler,
+ handler, lockoutResetDispatcher, controller);
}
private static abstract class FakeFingerRunnable implements Runnable {
@@ -400,17 +403,19 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage
// internal preemption logic is not run.
mFingerprint21.scheduleAuthenticate(mFingerprint21.mSensorProperties.sensorId, token,
operationId, user, cookie, listener, opPackageName, restricted, statsClient,
- isKeyguard, null /* fingerprintStateCallback */);
+ isKeyguard);
}
}
private Fingerprint21UdfpsMock(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull TestableBiometricScheduler scheduler,
@NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull MockHalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
mScheduler = scheduler;
mScheduler.init(this);
mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 5060744bb33e..7d95ec098fee 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -59,7 +59,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
private boolean mIsPointerDown;
FingerprintAuthenticationClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
int sensorId, boolean isStrongBiometric, int statsClient,
@@ -73,6 +74,7 @@ class FingerprintAuthenticationClient extends AuthenticationClient<IBiometricsFi
BiometricsProtoEnums.MODALITY_FINGERPRINT, statsClient, taskStackListener,
lockoutTracker, allowBackgroundAuthentication, true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
+ setRequestId(requestId);
mLockoutFrameworkImpl = lockoutTracker;
mUdfpsOverlayController = udfpsOverlayController;
mSensorProps = sensorProps;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 8e73ee6b3e9f..147a20699b54 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -52,13 +52,15 @@ class FingerprintDetectClient extends AcquisitionClient<IBiometricsFingerprint>
private boolean mIsPointerDown;
public FingerprintDetectClient(@NonNull Context context,
- @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
+ @NonNull LazyDaemon<IBiometricsFingerprint> lazyDaemon,
+ @NonNull IBinder token, long requestId,
@NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
int sensorId, @Nullable IUdfpsOverlayController udfpsOverlayController,
boolean isStrongBiometric, int statsClient) {
super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
true /* shouldVibrate */, BiometricsProtoEnums.MODALITY_FINGERPRINT,
BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient);
+ setRequestId(requestId);
mUdfpsOverlayController = udfpsOverlayController;
mIsStrongBiometric = isStrongBiometric;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
index 491818520e3c..5c0c3626037a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
+++ b/services/core/java/com/android/server/biometrics/sensors/iris/IrisAuthenticator.java
@@ -59,7 +59,7 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
@Override
public void prepareForAuthentication(boolean requireConfirmation, IBinder token,
long sessionId, int userId, IBiometricSensorReceiver sensorReceiver,
- String opPackageName, int cookie, boolean allowBackgroundAuthentication)
+ String opPackageName, long requestId, int cookie, boolean allowBackgroundAuthentication)
throws RemoteException {
}
@@ -68,7 +68,7 @@ public final class IrisAuthenticator extends IBiometricAuthenticator.Stub {
}
@Override
- public void cancelAuthenticationFromService(IBinder token, String opPackageName)
+ public void cancelAuthenticationFromService(IBinder token, String opPackageName, long requestId)
throws RemoteException {
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 4c9d0f2691b3..2ae5cbbbf24b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -590,7 +590,7 @@ public class DisplayDeviceConfig {
newIndex = i - newStart;
final float newBacklightVal;
final float newNitsVal;
- isLastValue = mRawBacklight[i] > mBacklightMaximum
+ isLastValue = mRawBacklight[i] >= mBacklightMaximum
|| i >= mRawBacklight.length - 1;
// Clamp beginning and end to valid backlight values.
if (newIndex == 0) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index afd18894f531..c2200430e53b 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -192,7 +192,9 @@ public final class DisplayManagerService extends SystemService {
private static final String PROP_DEFAULT_DISPLAY_TOP_INSET = "persist.sys.displayinset.top";
private static final long WAIT_FOR_DEFAULT_DISPLAY_TIMEOUT = 10000;
- private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.1f;
+ // This value needs to be in sync with the threshold
+ // in RefreshRateConfigs::getFrameRateDivider.
+ private static final float THRESHOLD_FOR_REFRESH_RATES_DIVIDERS = 0.0009f;
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;
@@ -812,7 +814,7 @@ public final class DisplayManagerService extends SystemService {
// Override the refresh rate only if it is a divider of the current
// refresh rate. This calculation needs to be in sync with the native code
- // in RefreshRateConfigs::getRefreshRateDividerForUid
+ // in RefreshRateConfigs::getFrameRateDivider
Display.Mode currentMode = info.getMode();
float numPeriods = currentMode.getRefreshRate() / frameRateHz;
float numPeriodsRound = Math.round(numPeriods);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 329a69634f71..bc2a234ccc96 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -17,6 +17,7 @@
package com.android.server.display;
import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE;
+import static android.os.PowerManager.BRIGHTNESS_INVALID;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -36,9 +37,15 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.net.Uri;
import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.Temperature;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -48,12 +55,14 @@ import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayInfo;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
import com.android.server.display.utils.AmbientFilter;
@@ -72,7 +81,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Objects;
-
/**
* The DisplayModeDirector is responsible for determining what modes are allowed to be automatically
* picked by the system based on system-wide and display-specific configuration.
@@ -87,6 +95,8 @@ public class DisplayModeDirector {
private static final int MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED = 4;
private static final int MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED = 5;
private static final int MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED = 6;
+ private static final int MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED = 7;
+ private static final int MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED = 8;
// Special ID used to indicate that given vote is to be applied globally, rather than to a
// specific display.
@@ -108,6 +118,7 @@ public class DisplayModeDirector {
private final UdfpsObserver mUdfpsObserver;
private final SensorObserver mSensorObserver;
private final HbmObserver mHbmObserver;
+ private final SkinThermalStatusObserver mSkinThermalStatusObserver;
private final DeviceConfigInterface mDeviceConfig;
private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
@@ -147,7 +158,7 @@ public class DisplayModeDirector {
mAppRequestObserver = new AppRequestObserver();
mSettingsObserver = new SettingsObserver(context, handler);
mDisplayObserver = new DisplayObserver(context, handler);
- mBrightnessObserver = new BrightnessObserver(context, handler);
+ mBrightnessObserver = new BrightnessObserver(context, handler, injector);
mUdfpsObserver = new UdfpsObserver();
final BallotBox ballotBox = (displayId, priority, vote) -> {
synchronized (mLock) {
@@ -155,8 +166,10 @@ public class DisplayModeDirector {
}
};
mSensorObserver = new SensorObserver(context, ballotBox, injector);
- mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler());
+ mSkinThermalStatusObserver = new SkinThermalStatusObserver(injector, ballotBox);
mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
+ mHbmObserver = new HbmObserver(injector, ballotBox, BackgroundThread.getHandler(),
+ mDeviceConfigDisplaySettings);
mDeviceConfig = injector.getDeviceConfig();
mAlwaysRespectAppRequest = false;
}
@@ -174,6 +187,7 @@ public class DisplayModeDirector {
mBrightnessObserver.observe(sensorManager);
mSensorObserver.observe();
mHbmObserver.observe();
+ mSkinThermalStatusObserver.observe();
synchronized (mLock) {
// We may have a listener already registered before the call to start, so go ahead and
// notify them to pick up our newly initialized state.
@@ -609,6 +623,7 @@ public class DisplayModeDirector {
mUdfpsObserver.dumpLocked(pw);
mSensorObserver.dumpLocked(pw);
mHbmObserver.dumpLocked(pw);
+ mSkinThermalStatusObserver.dumpLocked(pw);
}
}
@@ -717,6 +732,10 @@ public class DisplayModeDirector {
return mUdfpsObserver;
}
+ @VisibleForTesting
+ HbmObserver getHbmObserver() {
+ return mHbmObserver;
+ }
@VisibleForTesting
DesiredDisplayModeSpecs getDesiredDisplayModeSpecsWithInjectedFpsSettings(
@@ -787,6 +806,19 @@ public class DisplayModeDirector {
(DesiredDisplayModeSpecsListener) msg.obj;
desiredDisplayModeSpecsListener.onDesiredDisplayModeSpecsChanged();
break;
+
+ case MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED: {
+ int refreshRateInHbmSunlight = msg.arg1;
+ mHbmObserver.onDeviceConfigRefreshRateInHbmSunlightChanged(
+ refreshRateInHbmSunlight);
+ break;
+ }
+
+ case MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED: {
+ int refreshRateInHbmHdr = msg.arg1;
+ mHbmObserver.onDeviceConfigRefreshRateInHbmHdrChanged(refreshRateInHbmHdr);
+ break;
+ }
}
}
}
@@ -913,16 +945,19 @@ public class DisplayModeDirector {
// result is a range.
public static final int PRIORITY_FLICKER_REFRESH_RATE = 1;
+ // High-brightness-mode may need a specific range of refresh-rates to function properly.
+ public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 2;
+
// SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
// It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
- public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 2;
+ public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 3;
// APP_REQUEST_REFRESH_RATE_RANGE is used to for internal apps to limit the refresh
// rate in certain cases, mostly to preserve power.
// @see android.view.WindowManager.LayoutParams#preferredMinRefreshRate
// @see android.view.WindowManager.LayoutParams#preferredMaxRefreshRate
// It votes to [preferredMinRefreshRate, preferredMaxRefreshRate].
- public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 3;
+ public static final int PRIORITY_APP_REQUEST_REFRESH_RATE_RANGE = 4;
// We split the app request into different priorities in case we can satisfy one desire
// without the other.
@@ -937,32 +972,32 @@ public class DisplayModeDirector {
// The preferred refresh rate is set on the main surface of the app outside of
// DisplayModeDirector.
// @see com.android.server.wm.WindowState#updateFrameRateSelectionPriorityIfNeeded
- public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 4;
- public static final int PRIORITY_APP_REQUEST_SIZE = 5;
+ public static final int PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE = 5;
+ public static final int PRIORITY_APP_REQUEST_SIZE = 6;
// SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
// of low priority voters. It votes [0, max(PEAK, MIN)]
- public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 6;
+ public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 7;
// LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
- public static final int PRIORITY_LOW_POWER_MODE = 7;
+ public static final int PRIORITY_LOW_POWER_MODE = 8;
// PRIORITY_FLICKER_REFRESH_RATE_SWITCH votes for disabling refresh rate switching. If the
// higher priority voters' result is a range, it will fix the rate to a single choice.
// It's used to avoid refresh rate switches in certain conditions which may result in the
// user seeing the display flickering when the switches occur.
- public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 8;
+ public static final int PRIORITY_FLICKER_REFRESH_RATE_SWITCH = 9;
- // High-brightness-mode may need a specific range of refresh-rates to function properly.
- public static final int PRIORITY_HIGH_BRIGHTNESS_MODE = 9;
+ // Force display to [0, 60HZ] if skin temperature is at or above CRITICAL.
+ public static final int PRIORITY_SKIN_TEMPERATURE = 10;
// The proximity sensor needs the refresh rate to be locked in order to function, so this is
// set to a high priority.
- public static final int PRIORITY_PROXIMITY = 10;
+ public static final int PRIORITY_PROXIMITY = 11;
// The Under-Display Fingerprint Sensor (UDFPS) needs the refresh rate to be locked in order
// to function, so this needs to be the highest priority of all votes.
- public static final int PRIORITY_UDFPS = 11;
+ public static final int PRIORITY_UDFPS = 12;
// Whenever a new priority is added, remember to update MIN_PRIORITY, MAX_PRIORITY, and
// APP_REQUEST_REFRESH_RATE_RANGE_PRIORITY_CUTOFF, as well as priorityToString.
@@ -1057,6 +1092,8 @@ public class DisplayModeDirector {
return "PRIORITY_PROXIMITY";
case PRIORITY_LOW_POWER_MODE:
return "PRIORITY_LOW_POWER_MODE";
+ case PRIORITY_SKIN_TEMPERATURE:
+ return "PRIORITY_SKIN_TEMPERATURE";
case PRIORITY_UDFPS:
return "PRIORITY_UDFPS";
case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
@@ -1396,8 +1433,6 @@ public class DisplayModeDirector {
@Override
public void onDisplayChanged(int displayId) {
updateDisplayModes(displayId);
- // TODO: Break the coupling between DisplayObserver and BrightnessObserver.
- mBrightnessObserver.onDisplayChanged(displayId);
}
private void updateDisplayModes(int displayId) {
@@ -1434,7 +1469,7 @@ public class DisplayModeDirector {
* {@link R.array#config_ambientThresholdsOfPeakRefreshRate}.
*/
@VisibleForTesting
- public class BrightnessObserver extends ContentObserver {
+ public class BrightnessObserver implements DisplayManager.DisplayListener {
private final static int LIGHT_SENSOR_RATE_MS = 250;
private int[] mLowDisplayBrightnessThresholds;
private int[] mLowAmbientBrightnessThresholds;
@@ -1457,6 +1492,8 @@ public class DisplayModeDirector {
private int mBrightness = -1;
private final Context mContext;
+ private final Injector mInjector;
+ private final Handler mHandler;
// Enable light sensor only when mShouldObserveAmbientLowChange is true or
// mShouldObserveAmbientHighChange is true, screen is on, peak refresh rate
@@ -1469,9 +1506,11 @@ public class DisplayModeDirector {
private int mRefreshRateInLowZone;
private int mRefreshRateInHighZone;
- BrightnessObserver(Context context, Handler handler) {
- super(handler);
+ BrightnessObserver(Context context, Handler handler, Injector injector) {
mContext = context;
+ mHandler = handler;
+ mInjector = injector;
+
mLowDisplayBrightnessThresholds = context.getResources().getIntArray(
R.array.config_brightnessThresholdsOfPeakRefreshRate);
mLowAmbientBrightnessThresholds = context.getResources().getIntArray(
@@ -1538,8 +1577,7 @@ public class DisplayModeDirector {
public void observe(SensorManager sensorManager) {
mSensorManager = sensorManager;
final ContentResolver cr = mContext.getContentResolver();
- mBrightness = Settings.System.getIntForUser(cr,
- Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
+ mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
// DeviceConfig is accessible after system ready.
int[] lowDisplayBrightnessThresholds =
@@ -1572,6 +1610,10 @@ public class DisplayModeDirector {
restartObserver();
mDeviceConfigDisplaySettings.startListening();
+
+ mInjector.registerDisplayListener(this, mHandler,
+ DisplayManager.EVENT_FLAG_DISPLAY_CHANGED |
+ DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
}
public void setLoggingEnabled(boolean loggingEnabled) {
@@ -1687,28 +1729,30 @@ public class DisplayModeDirector {
}
}
+ @Override
+ public void onDisplayAdded(int displayId) {}
+
+ @Override
+ public void onDisplayRemoved(int displayId) {}
+
+ @Override
public void onDisplayChanged(int displayId) {
if (displayId == Display.DEFAULT_DISPLAY) {
updateDefaultDisplayState();
- }
- }
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- synchronized (mLock) {
- final ContentResolver cr = mContext.getContentResolver();
- int brightness = Settings.System.getIntForUser(cr,
- Settings.System.SCREEN_BRIGHTNESS, -1 /*default*/, cr.getUserId());
- if (brightness != mBrightness) {
- mBrightness = brightness;
- onBrightnessChangedLocked();
+ // We don't support multiple display blocking zones yet, so only handle
+ // brightness changes for the default display for now.
+ int brightness = getBrightness(displayId);
+ synchronized (mLock) {
+ if (brightness != mBrightness) {
+ mBrightness = brightness;
+ onBrightnessChangedLocked();
+ }
}
}
}
private void restartObserver() {
- final ContentResolver cr = mContext.getContentResolver();
-
if (mRefreshRateInLowZone > 0) {
mShouldObserveDisplayLowChange = hasValidThreshold(
mLowDisplayBrightnessThresholds);
@@ -1729,15 +1773,6 @@ public class DisplayModeDirector {
mShouldObserveAmbientHighChange = false;
}
- if (mShouldObserveDisplayLowChange || mShouldObserveDisplayHighChange) {
- // Content Service does not check if an listener has already been registered.
- // To ensure only one listener is registered, force an unregistration first.
- mInjector.unregisterBrightnessObserver(cr, this);
- mInjector.registerBrightnessObserver(cr, this);
- } else {
- mInjector.unregisterBrightnessObserver(cr, this);
- }
-
if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
Resources resources = mContext.getResources();
String lightSensorType = resources.getString(
@@ -1937,6 +1972,15 @@ public class DisplayModeDirector {
return mDefaultDisplayState == Display.STATE_ON;
}
+ private int getBrightness(int displayId) {
+ final BrightnessInfo info = mInjector.getBrightnessInfo(displayId);
+ if (info != null) {
+ return BrightnessSynchronizer.brightnessFloatToInt(info.adjustedBrightness);
+ }
+
+ return BRIGHTNESS_INVALID;
+ }
+
private final class LightSensorEventListener implements SensorEventListener {
final private static int INJECT_EVENTS_INTERVAL_MS = LIGHT_SENSOR_RATE_MS;
private float mLastSensorData;
@@ -2248,33 +2292,80 @@ public class DisplayModeDirector {
* HBM that are associated with that display. Restrictions are retrieved from
* DisplayManagerInternal but originate in the display-device-config file.
*/
- private static class HbmObserver implements DisplayManager.DisplayListener {
+ public static class HbmObserver implements DisplayManager.DisplayListener {
private final BallotBox mBallotBox;
private final Handler mHandler;
- private final SparseBooleanArray mHbmEnabled = new SparseBooleanArray();
+ private final SparseIntArray mHbmMode = new SparseIntArray();
+ private final SparseBooleanArray mHbmActive = new SparseBooleanArray();
private final Injector mInjector;
+ private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
+ private int mRefreshRateInHbmSunlight;
+ private int mRefreshRateInHbmHdr;
private DisplayManagerInternal mDisplayManagerInternal;
- HbmObserver(Injector injector, BallotBox ballotBox, Handler handler) {
+ HbmObserver(Injector injector, BallotBox ballotBox, Handler handler,
+ DeviceConfigDisplaySettings displaySettings) {
mInjector = injector;
mBallotBox = ballotBox;
mHandler = handler;
+ mDeviceConfigDisplaySettings = displaySettings;
}
public void observe() {
+ mRefreshRateInHbmSunlight = mDeviceConfigDisplaySettings.getRefreshRateInHbmSunlight();
+ mRefreshRateInHbmHdr = mDeviceConfigDisplaySettings.getRefreshRateInHbmHdr();
+
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mInjector.registerDisplayListener(this, mHandler,
DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
| DisplayManager.EVENT_FLAG_DISPLAY_REMOVED);
}
+ /**
+ * @return the refresh to lock to when the device is in high brightness mode for Sunlight.
+ */
+ @VisibleForTesting
+ int getRefreshRateInHbmSunlight() {
+ return mRefreshRateInHbmSunlight;
+ }
+
+ /**
+ * @return the refresh to lock to when the device is in high brightness mode for HDR.
+ */
+ @VisibleForTesting
+ int getRefreshRateInHbmHdr() {
+ return mRefreshRateInHbmHdr;
+ }
+
+ /**
+ * Recalculates the HBM vote when the device config has been changed.
+ */
+ public void onDeviceConfigRefreshRateInHbmSunlightChanged(int refreshRate) {
+ if (refreshRate != mRefreshRateInHbmSunlight) {
+ mRefreshRateInHbmSunlight = refreshRate;
+ onDeviceConfigRefreshRateInHbmChanged();
+ }
+ }
+
+ /**
+ * Recalculates the HBM vote when the device config has been changed.
+ */
+ public void onDeviceConfigRefreshRateInHbmHdrChanged(int refreshRate) {
+ if (refreshRate != mRefreshRateInHbmHdr) {
+ mRefreshRateInHbmHdr = refreshRate;
+ onDeviceConfigRefreshRateInHbmChanged();
+ }
+ }
+
@Override
public void onDisplayAdded(int displayId) {}
@Override
public void onDisplayRemoved(int displayId) {
mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, null);
+ mHbmMode.delete(displayId);
+ mHbmActive.delete(displayId);
}
@Override
@@ -2284,31 +2375,116 @@ public class DisplayModeDirector {
// Display no longer there. Assume we'll get an onDisplayRemoved very soon.
return;
}
- final boolean isHbmEnabled =
- info.highBrightnessMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
- if (isHbmEnabled == mHbmEnabled.get(displayId)) {
+
+ final int hbmMode = info.highBrightnessMode;
+ final boolean isHbmActive = hbmMode != BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF &&
+ info.adjustedBrightness > info.highBrightnessTransitionPoint;
+ if (hbmMode == mHbmMode.get(displayId) &&
+ isHbmActive == mHbmActive.get(displayId)) {
// no change, ignore.
return;
}
+ mHbmMode.put(displayId, hbmMode);
+ mHbmActive.put(displayId, isHbmActive);
+ recalculateVotesForDisplay(displayId);
+ }
+
+ private void onDeviceConfigRefreshRateInHbmChanged() {
+ final int[] displayIds = mHbmMode.copyKeys();
+ if (displayIds != null) {
+ for (int id : displayIds) {
+ recalculateVotesForDisplay(id);
+ }
+ }
+ }
+
+ private void recalculateVotesForDisplay(int displayId) {
Vote vote = null;
- mHbmEnabled.put(displayId, isHbmEnabled);
- if (isHbmEnabled) {
- final List<RefreshRateLimitation> limits =
- mDisplayManagerInternal.getRefreshRateLimitations(displayId);
- for (int i = 0; limits != null && i < limits.size(); i++) {
- final RefreshRateLimitation limitation = limits.get(i);
- if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
- vote = Vote.forRefreshRates(limitation.range.min, limitation.range.max);
- break;
+ if (mHbmActive.get(displayId, false)) {
+ final int hbmMode =
+ mHbmMode.get(displayId, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF);
+ if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT) {
+ // Device resource properties take priority over DisplayDeviceConfig
+ if (mRefreshRateInHbmSunlight > 0) {
+ vote = Vote.forRefreshRates(mRefreshRateInHbmSunlight,
+ mRefreshRateInHbmSunlight);
+ } else {
+ final List<RefreshRateLimitation> limits =
+ mDisplayManagerInternal.getRefreshRateLimitations(displayId);
+ for (int i = 0; limits != null && i < limits.size(); i++) {
+ final RefreshRateLimitation limitation = limits.get(i);
+ if (limitation.type == REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE) {
+ vote = Vote.forRefreshRates(limitation.range.min,
+ limitation.range.max);
+ break;
+ }
+ }
}
+ } else if (hbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR &&
+ mRefreshRateInHbmHdr > 0) {
+ // HBM for HDR vote isn't supported through DisplayDeviceConfig yet, so look for
+ // a vote from Device properties
+ vote = Vote.forRefreshRates(mRefreshRateInHbmHdr, mRefreshRateInHbmHdr);
+ } else {
+ Slog.w(TAG, "Unexpected HBM mode " + hbmMode + " for display ID " + displayId);
}
+
}
mBallotBox.vote(displayId, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE, vote);
}
void dumpLocked(PrintWriter pw) {
pw.println(" HbmObserver");
- pw.println(" mHbmEnabled: " + mHbmEnabled);
+ pw.println(" mHbmMode: " + mHbmMode);
+ pw.println(" mHbmActive: " + mHbmActive);
+ pw.println(" mRefreshRateInHbmSunlight: " + mRefreshRateInHbmSunlight);
+ pw.println(" mRefreshRateInHbmHdr: " + mRefreshRateInHbmHdr);
+ }
+ }
+
+ private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
+ private final BallotBox mBallotBox;
+ private final Injector mInjector;
+
+ private @Temperature.ThrottlingStatus int mStatus = -1;
+
+ SkinThermalStatusObserver(Injector injector, BallotBox ballotBox) {
+ mInjector = injector;
+ mBallotBox = ballotBox;
+ }
+
+ @Override
+ public void notifyThrottling(Temperature temp) {
+ mStatus = temp.getStatus();
+ if (mLoggingEnabled) {
+ Slog.d(TAG, "New thermal throttling status "
+ + ", current thermal status = " + mStatus);
+ }
+ final Vote vote;
+ if (mStatus >= Temperature.THROTTLING_CRITICAL) {
+ vote = Vote.forRefreshRates(0f, 60f);
+ } else {
+ vote = null;
+ }
+ mBallotBox.vote(GLOBAL_ID, Vote.PRIORITY_SKIN_TEMPERATURE, vote);
+ }
+
+ public void observe() {
+ IThermalService thermalService = mInjector.getThermalService();
+ if (thermalService == null) {
+ Slog.w(TAG, "Could not observe thermal status. Service not available");
+ return;
+ }
+ try {
+ thermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to register thermal status listener", e);
+ }
+ }
+
+ void dumpLocked(PrintWriter writer) {
+ writer.println(" SkinThermalStatusObserver:");
+ writer.println(" mStatus: " + mStatus);
}
}
@@ -2381,6 +2557,29 @@ public class DisplayModeDirector {
return refreshRate;
}
+ public int getRefreshRateInHbmSunlight() {
+ final int defaultRefreshRateInHbmSunlight =
+ mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInHbmSunlight);
+
+ final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT,
+ defaultRefreshRateInHbmSunlight);
+
+ return refreshRate;
+ }
+
+ public int getRefreshRateInHbmHdr() {
+ final int defaultRefreshRateInHbmHdr =
+ mContext.getResources().getInteger(R.integer.config_defaultRefreshRateInHbmHdr);
+
+ final int refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR,
+ defaultRefreshRateInHbmHdr);
+
+ return refreshRate;
+ }
+
/*
* Return null if no such property
*/
@@ -2420,6 +2619,15 @@ public class DisplayModeDirector {
.sendToTarget();
mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HIGH_ZONE_CHANGED, refreshRateInHighZone, 0)
.sendToTarget();
+
+ final int refreshRateInHbmSunlight = getRefreshRateInHbmSunlight();
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_SUNLIGHT_CHANGED,
+ refreshRateInHbmSunlight, 0)
+ .sendToTarget();
+
+ final int refreshRateInHbmHdr = getRefreshRateInHbmHdr();
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_HBM_HDR_CHANGED, refreshRateInHbmHdr, 0)
+ .sendToTarget();
}
private int[] getIntArrayProperty(String prop) {
@@ -2451,19 +2659,11 @@ public class DisplayModeDirector {
}
interface Injector {
- // TODO: brightnessfloat: change this to the float setting
- Uri DISPLAY_BRIGHTNESS_URI = Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
Uri PEAK_REFRESH_RATE_URI = Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
@NonNull
DeviceConfigInterface getDeviceConfig();
- void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer);
-
- void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer);
-
void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer);
@@ -2473,6 +2673,8 @@ public class DisplayModeDirector {
BrightnessInfo getBrightnessInfo(int displayId);
boolean isDozeState(Display d);
+
+ IThermalService getThermalService();
}
@VisibleForTesting
@@ -2491,19 +2693,6 @@ public class DisplayModeDirector {
}
@Override
- public void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- cr.registerContentObserver(DISPLAY_BRIGHTNESS_URI, false /*notifyDescendants*/,
- observer, UserHandle.USER_SYSTEM);
- }
-
- @Override
- public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- cr.unregisterContentObserver(observer);
- }
-
- @Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
cr.registerContentObserver(PEAK_REFRESH_RATE_URI, false /*notifyDescendants*/,
@@ -2533,6 +2722,12 @@ public class DisplayModeDirector {
return Display.isDozeState(d.getState());
}
+ @Override
+ public IThermalService getThermalService() {
+ return IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ }
+
private DisplayManager getDisplayManager() {
if (mDisplayManager == null) {
mDisplayManager = mContext.getSystemService(DisplayManager.class);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index abbe13ac260f..1224902aa7be 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -125,6 +125,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private static final int MSG_IGNORE_PROXIMITY = 8;
private static final int MSG_STOP = 9;
private static final int MSG_UPDATE_BRIGHTNESS = 10;
+ private static final int MSG_UPDATE_RBC = 11;
private static final int PROXIMITY_UNKNOWN = -1;
private static final int PROXIMITY_NEGATIVE = 0;
@@ -422,13 +423,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
// PowerManager.BRIGHTNESS_INVALID_FLOAT when there's no temporary adjustment set.
private float mTemporaryAutoBrightnessAdjustment;
- // Whether a reduce bright colors (rbc) change has been initiated by the user. We want to
- // retain the current backlight level when rbc is toggled, since rbc additionally makes the
- // screen appear dimmer using screen colors rather than backlight levels, and therefore we
- // don't actually want to compensate for this by then in/decreasing the backlight when
- // toggling this feature.
+ // Whether reduce bright colors (rbc) has been turned on, or a change in strength has been
+ // requested. We want to retain the current backlight level when rbc is toggled, since rbc
+ // additionally makes the screen appear dimmer using screen colors rather than backlight levels,
+ // and therefore we don't actually want to compensate for this by then in/decreasing the
+ // backlight when toggling this feature.
// This should be false during system start up.
- private boolean mPendingUserRbcChange;
+ private boolean mPendingRbcOnOrChanged = false;
// Animators.
private ObjectAnimator mColorFadeOnAnimator;
@@ -564,23 +565,35 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
@Override
public void onReduceBrightColorsActivationChanged(boolean activated,
boolean userInitiated) {
- applyReduceBrightColorsSplineAdjustment(userInitiated);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, activated);
+
}
@Override
public void onReduceBrightColorsStrengthChanged(int strength) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ true, /* justActivated= */ false);
}
});
if (active) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, /* justActivated= */ false);
}
} else {
mCdsi = null;
}
}
- private void applyReduceBrightColorsSplineAdjustment(boolean userInitiated) {
+ private void applyReduceBrightColorsSplineAdjustment(
+ boolean rbcStrengthChanged, boolean justActivated) {
+ final int strengthChanged = rbcStrengthChanged ? 1 : 0;
+ final int activated = justActivated ? 1 : 0;
+ mHandler.obtainMessage(MSG_UPDATE_RBC, strengthChanged, activated).sendToTarget();
+ sendUpdatePowerState();
+ }
+
+ private void handleRbcChanged(boolean strengthChanged, boolean justActivated) {
if (mBrightnessMapper == null) {
Log.w(TAG, "No brightness mapping available to recalculate splines");
return;
@@ -591,8 +604,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
adjustedNits[i] = mCdsi.getReduceBrightColorsAdjustedBrightnessNits(mNitsRange[i]);
}
mBrightnessMapper.recalculateSplines(mCdsi.isReduceBrightColorsActivated(), adjustedNits);
- mPendingUserRbcChange = userInitiated;
- sendUpdatePowerState();
+
+ mPendingRbcOnOrChanged = strengthChanged || justActivated;
+
+ // Reset model if strength changed OR rbc is turned off
+ if (strengthChanged || !justActivated && mAutomaticBrightnessController != null) {
+ mAutomaticBrightnessController.resetShortTermModel();
+ }
}
/**
@@ -926,7 +944,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
private void reloadReduceBrightColours() {
if (mCdsi != null && mCdsi.isReduceBrightColorsActivated()) {
- applyReduceBrightColorsSplineAdjustment(/*userInitiated*/ false);
+ applyReduceBrightColorsSplineAdjustment(
+ /* rbcStrengthChanged= */ false, /* justActivated= */ false);
}
}
@@ -1259,10 +1278,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
putScreenBrightnessSetting(brightnessState, /* updateCurrent */ true);
}
- // We save the brightness info *after* the brightness setting has been changed so that
- // the brightness info reflects the latest value.
- saveBrightnessInfo(getScreenBrightnessSetting());
-
// Apply dimming by at least some minimum amount when user activity
// timeout is about to expire.
if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
@@ -1393,6 +1408,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
hadUserBrightnessPoint);
}
+ // We save the brightness info *after* the brightness setting has been changed and
+ // adjustments made so that the brightness info reflects the latest value.
+ saveBrightnessInfo(getScreenBrightnessSetting(), animateValue);
+ } else {
+ saveBrightnessInfo(getScreenBrightnessSetting());
}
// Log any changes to what is currently driving the brightness setting.
@@ -1509,18 +1529,27 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
synchronized (mCachedBrightnessInfo) {
return new BrightnessInfo(
mCachedBrightnessInfo.brightness,
+ mCachedBrightnessInfo.adjustedBrightness,
mCachedBrightnessInfo.brightnessMin,
mCachedBrightnessInfo.brightnessMax,
- mCachedBrightnessInfo.hbmMode);
+ mCachedBrightnessInfo.hbmMode,
+ mCachedBrightnessInfo.highBrightnessTransitionPoint);
}
}
private void saveBrightnessInfo(float brightness) {
+ saveBrightnessInfo(brightness, brightness);
+ }
+
+ private void saveBrightnessInfo(float brightness, float adjustedBrightness) {
synchronized (mCachedBrightnessInfo) {
mCachedBrightnessInfo.brightness = brightness;
+ mCachedBrightnessInfo.adjustedBrightness = adjustedBrightness;
mCachedBrightnessInfo.brightnessMin = mHbmController.getCurrentBrightnessMin();
mCachedBrightnessInfo.brightnessMax = mHbmController.getCurrentBrightnessMax();
mCachedBrightnessInfo.hbmMode = mHbmController.getHighBrightnessMode();
+ mCachedBrightnessInfo.highBrightnessTransitionPoint =
+ mHbmController.getTransitionPoint();
}
}
@@ -2062,21 +2091,24 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
return true;
}
+ // We want to return true if the user has set the screen brightness.
+ // If they have just turned RBC on (and therefore added that interaction to the curve),
+ // or changed the brightness another way, then we should return true.
private boolean updateUserSetScreenBrightness() {
- final boolean brightnessSplineChanged = mPendingUserRbcChange;
- if (mPendingUserRbcChange && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
+ final boolean treatAsIfUserChanged = mPendingRbcOnOrChanged;
+ if (treatAsIfUserChanged && !Float.isNaN(mCurrentScreenBrightnessSetting)) {
mLastUserSetScreenBrightness = mCurrentScreenBrightnessSetting;
}
- mPendingUserRbcChange = false;
+ mPendingRbcOnOrChanged = false;
if ((Float.isNaN(mPendingScreenBrightnessSetting)
|| mPendingScreenBrightnessSetting < 0.0f)) {
- return brightnessSplineChanged;
+ return treatAsIfUserChanged;
}
if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
mPendingScreenBrightnessSetting = PowerManager.BRIGHTNESS_INVALID_FLOAT;
mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
- return brightnessSplineChanged;
+ return treatAsIfUserChanged;
}
setCurrentScreenBrightness(mPendingScreenBrightnessSetting);
mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
@@ -2195,6 +2227,18 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
pw.println(" mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
pw.println(" mColorFadeFadesConfig=" + mColorFadeFadesConfig);
pw.println(" mColorFadeEnabled=" + mColorFadeEnabled);
+ synchronized (mCachedBrightnessInfo) {
+ pw.println(" mCachedBrightnessInfo.brightness=" + mCachedBrightnessInfo.brightness);
+ pw.println(" mCachedBrightnessInfo.adjustedBrightness=" +
+ mCachedBrightnessInfo.adjustedBrightness);
+ pw.println(" mCachedBrightnessInfo.brightnessMin=" +
+ mCachedBrightnessInfo.brightnessMin);
+ pw.println(" mCachedBrightnessInfo.brightnessMax=" +
+ mCachedBrightnessInfo.brightnessMax);
+ pw.println(" mCachedBrightnessInfo.hbmMode=" + mCachedBrightnessInfo.hbmMode);
+ pw.println(" mCachedBrightnessInfo.highBrightnessTransitionPoint=" +
+ mCachedBrightnessInfo.highBrightnessTransitionPoint);
+ }
pw.println(" mDisplayBlanksAfterDozeConfig=" + mDisplayBlanksAfterDozeConfig);
pw.println(" mBrightnessBucketsInDozeConfig=" + mBrightnessBucketsInDozeConfig);
@@ -2406,6 +2450,12 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
handleSettingsChange(false /*userSwitch*/);
break;
+
+ case MSG_UPDATE_RBC:
+ final int strengthChanged = msg.arg1;
+ final int justActivated = msg.arg2;
+ handleRbcChanged(strengthChanged == 1, justActivated == 1);
+ break;
}
}
}
@@ -2606,8 +2656,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
static class CachedBrightnessInfo {
public float brightness;
+ public float adjustedBrightness;
public float brightnessMin;
public float brightnessMax;
public int hbmMode;
+ public float highBrightnessTransitionPoint;
}
}
diff --git a/services/core/java/com/android/server/display/DisplayPowerState.java b/services/core/java/com/android/server/display/DisplayPowerState.java
index 6af192371e3d..147050cd271f 100644
--- a/services/core/java/com/android/server/display/DisplayPowerState.java
+++ b/services/core/java/com/android/server/display/DisplayPowerState.java
@@ -411,7 +411,7 @@ final class DisplayPowerState {
* Updates the state of the screen and backlight asynchronously on a separate thread.
*/
private final class PhotonicModulator extends Thread {
- private static final int INITIAL_SCREEN_STATE = Display.STATE_OFF; // unknown, assume off
+ private static final int INITIAL_SCREEN_STATE = Display.STATE_UNKNOWN;
private static final float INITIAL_BACKLIGHT_FLOAT = PowerManager.BRIGHTNESS_INVALID_FLOAT;
private final Object mLock = new Object();
@@ -494,7 +494,9 @@ final class DisplayPowerState {
if (!backlightChanged) {
mBacklightChangeInProgress = false;
}
- if (!stateChanged && !backlightChanged) {
+ boolean valid = state != Display.STATE_UNKNOWN && !Float.isNaN(brightnessState);
+ boolean changed = stateChanged || backlightChanged;
+ if (!valid || !changed) {
try {
mLock.wait();
} catch (InterruptedException ex) {
diff --git a/services/core/java/com/android/server/display/HighBrightnessModeController.java b/services/core/java/com/android/server/display/HighBrightnessModeController.java
index 2791f6a409be..1e1cfeb5b9dd 100644
--- a/services/core/java/com/android/server/display/HighBrightnessModeController.java
+++ b/services/core/java/com/android/server/display/HighBrightnessModeController.java
@@ -59,6 +59,9 @@ class HighBrightnessModeController {
private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f;
+ @VisibleForTesting
+ static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
+
private final float mBrightnessMin;
private final float mBrightnessMax;
private final Handler mHandler;
@@ -214,6 +217,14 @@ class HighBrightnessModeController {
return mHbmMode;
}
+ float getTransitionPoint() {
+ if (deviceSupportsHbm()) {
+ return mHbmData.transitionPoint;
+ } else {
+ return HBM_TRANSITION_POINT_INVALID;
+ }
+ }
+
void stop() {
registerHdrListener(null /*displayToken*/);
mSkinThermalStatusObserver.stopObserving();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index fa33338a61e7..03e421bfed67 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -209,6 +209,12 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
*/
private AtomicBoolean mIsPendingIntentCancelled = new AtomicBoolean(false);
+ /**
+ * True if a permissions query has been issued and is being processed. Used to prevent too many
+ * queries from being issued by a single client at once.
+ */
+ private AtomicBoolean mIsPermQueryIssued = new AtomicBoolean(false);
+
/*
* True if the application creating the client has the ACCESS_CONTEXT_HUB permission.
*/
@@ -240,11 +246,11 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
private final IContextHubTransactionCallback mQueryPermsCallback =
new IContextHubTransactionCallback.Stub() {
@Override
- public void onTransactionComplete(int result) {
- }
+ public void onTransactionComplete(int result) {}
@Override
public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+ mIsPermQueryIssued.set(false);
if (result != ContextHubTransaction.RESULT_SUCCESS && nanoAppStateList != null) {
Log.e(TAG, "Permissions query failed, but still received nanoapp state");
} else if (nanoAppStateList != null) {
@@ -656,9 +662,11 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
* communicated with in the past.
*/
private void checkNanoappPermsAsync() {
- ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
- mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
- mTransactionManager.addTransaction(transaction);
+ if (!mIsPermQueryIssued.getAndSet(true)) {
+ ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+ mAttachedContextHubInfo.getId(), mQueryPermsCallback, mPackage);
+ mTransactionManager.addTransaction(transaction);
+ }
}
private int updateNanoAppAuthState(
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index f3dcfbbf2c0a..9956da274263 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -16,6 +16,7 @@
package com.android.server.location.gnss;
+import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
import static android.location.provider.ProviderProperties.ACCURACY_FINE;
import static android.location.provider.ProviderProperties.POWER_USAGE_HIGH;
@@ -43,6 +44,8 @@ import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_MODE_STANDALONE;
import static com.android.server.location.gnss.hal.GnssNative.GNSS_POSITION_RECURRENCE_PERIODIC;
+import static java.lang.Math.abs;
+import static java.lang.Math.max;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.app.AlarmManager;
@@ -85,6 +88,7 @@ import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
+import android.text.format.DateUtils;
import android.util.Log;
import android.util.TimeUtils;
@@ -103,7 +107,9 @@ import com.android.server.location.provider.AbstractLocationProvider;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -156,6 +162,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
private static final long LOCATION_UPDATE_DURATION_MILLIS = 10 * 1000;
// Update duration extension multiplier for emergency REQUEST_LOCATION.
private static final int EMERGENCY_LOCATION_UPDATE_DURATION_MULTIPLIER = 3;
+ // maximum length gnss batching may go for (1 day)
+ private static final int MIN_BATCH_INTERVAL_MS = (int) DateUtils.SECOND_IN_MILLIS;
+ private static final long MAX_BATCH_LENGTH_MS = DateUtils.DAY_IN_MILLIS;
+ private static final long MAX_BATCH_TIMESTAMP_DELTA_MS = 500;
// Threadsafe class to hold stats reported in the Extras Bundle
private static class LocationExtras {
@@ -245,6 +255,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
private boolean mShutdown;
private boolean mStarted;
private boolean mBatchingStarted;
+ private AlarmManager.OnAlarmListener mBatchingAlarm;
private long mStartedChangedElapsedRealtime;
private int mFixInterval = 1000;
@@ -845,18 +856,15 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
mFixInterval = Integer.MAX_VALUE;
}
- // requested batch size, or zero to disable batching
- long batchSize =
- mBatchingEnabled ? mProviderRequest.getMaxUpdateDelayMillis() / Math.max(
- mFixInterval, 1) : 0;
- if (batchSize < getBatchSize()) {
- batchSize = 0;
- }
+ int batchIntervalMs = max(mFixInterval, MIN_BATCH_INTERVAL_MS);
+ long batchLengthMs = Math.min(mProviderRequest.getMaxUpdateDelayMillis(),
+ MAX_BATCH_LENGTH_MS);
// apply request to GPS engine
- if (batchSize > 0) {
+ if (mBatchingEnabled && batchLengthMs / 2 >= batchIntervalMs) {
stopNavigating();
- startBatching();
+ mFixInterval = batchIntervalMs;
+ startBatching(batchLengthMs);
} else {
stopBatching();
@@ -875,7 +883,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
if (mFixInterval >= NO_FIX_TIMEOUT) {
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG,
mTimeoutListener, mHandler);
}
@@ -1066,7 +1074,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
// set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
// and our fix interval is not short
if (mFixInterval >= NO_FIX_TIMEOUT) {
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT, TAG, mTimeoutListener,
mHandler);
}
@@ -1090,12 +1098,37 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
mAlarmManager.cancel(mWakeupListener);
}
- private void startBatching() {
+ private void startBatching(long batchLengthMs) {
+ long batchSize = batchLengthMs / mFixInterval;
+
if (DEBUG) {
- Log.d(TAG, "startBatching " + mFixInterval);
+ Log.d(TAG, "startBatching " + mFixInterval + " " + batchLengthMs);
}
if (mGnssNative.startBatch(MILLISECONDS.toNanos(mFixInterval), true)) {
mBatchingStarted = true;
+
+ if (batchSize < getBatchSize()) {
+ // if the batch size is smaller than the hardware batch size, use an alarm to flush
+ // locations as appropriate
+ mBatchingAlarm = () -> {
+ boolean flush = false;
+ synchronized (mLock) {
+ if (mBatchingAlarm != null) {
+ flush = true;
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+ mBatchingAlarm, FgThread.getHandler());
+ }
+ }
+
+ if (flush) {
+ mGnssNative.flushBatch();
+ }
+ };
+ mAlarmManager.setExact(ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + batchLengthMs, TAG,
+ mBatchingAlarm, FgThread.getHandler());
+ }
} else {
Log.e(TAG, "native_start_batch failed in startBatching()");
}
@@ -1104,6 +1137,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
private void stopBatching() {
if (DEBUG) Log.d(TAG, "stopBatching");
if (mBatchingStarted) {
+ if (mBatchingAlarm != null) {
+ mAlarmManager.cancel(mBatchingAlarm);
+ mBatchingAlarm = null;
+ }
mGnssNative.stopBatch();
mBatchingStarted = false;
}
@@ -1120,7 +1157,7 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
// stop GPS until our next fix interval arrives
stopNavigating();
long now = SystemClock.elapsedRealtime();
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
+ mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + mFixInterval, TAG,
mWakeupListener, mHandler);
}
@@ -1485,16 +1522,50 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
Log.d(TAG, "Location batch of size " + locations.length + " reported");
}
+ if (locations.length > 0) {
+ // attempt to fix up timestamps if necessary
+ if (locations.length > 1) {
+ // check any realtimes outside of expected bounds
+ boolean fixRealtime = false;
+ for (int i = locations.length - 2; i >= 0; i--) {
+ long timeDeltaMs = locations[i + 1].getTime() - locations[i].getTime();
+ long realtimeDeltaMs = locations[i + 1].getElapsedRealtimeMillis()
+ - locations[i].getElapsedRealtimeMillis();
+ if (abs(timeDeltaMs - realtimeDeltaMs) > MAX_BATCH_TIMESTAMP_DELTA_MS) {
+ fixRealtime = true;
+ break;
+ }
+ }
+
+ if (fixRealtime) {
+ // sort for monotonically increasing time before fixing realtime - realtime will
+ // thus also be monotonically increasing
+ Arrays.sort(locations,
+ Comparator.comparingLong(Location::getTime));
+
+ long expectedDeltaMs =
+ locations[locations.length - 1].getTime()
+ - locations[locations.length - 1].getElapsedRealtimeMillis();
+ for (int i = locations.length - 2; i >= 0; i--) {
+ locations[i].setElapsedRealtimeNanos(
+ MILLISECONDS.toNanos(
+ max(locations[i].getTime() - expectedDeltaMs, 0)));
+ }
+ } else {
+ // sort for monotonically increasing realttime
+ Arrays.sort(locations,
+ Comparator.comparingLong(Location::getElapsedRealtimeNanos));
+ }
+ }
+
+ reportLocation(LocationResult.wrap(locations).validate());
+ }
+
Runnable[] listeners;
synchronized (mLock) {
listeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
-
- if (locations.length > 0) {
- reportLocation(LocationResult.wrap(locations).validate());
- }
-
for (Runnable listener : listeners) {
listener.run();
}
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index 222750629802..4d9253eff69e 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -444,12 +444,11 @@ class GnssNetworkConnectivityHandler {
capabilities = networkAttributes.mCapabilities;
Log.i(TAG, String.format(
"updateNetworkState, state=%s, connected=%s, network=%s, capabilities=%s"
- + ", apn: %s, availableNetworkCount: %d",
+ + ", availableNetworkCount: %d",
agpsDataConnStateAsString(),
isConnected,
network,
capabilities,
- apn,
mAvailableNetworkAttributes.size()));
if (native_is_agps_ril_supported()) {
diff --git a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
index a47c48fa16d4..2df21017156d 100644
--- a/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
+++ b/services/core/java/com/android/server/location/injector/LocationPermissionsHelper.java
@@ -100,7 +100,7 @@ public abstract class LocationPermissionsHelper {
return false;
}
- return mAppOps.checkOpNoThrow(permissionLevel, identity);
+ return mAppOps.checkOpNoThrow(LocationPermissions.asAppOp(permissionLevel), identity);
}
protected abstract boolean hasPermission(String permission, CallerIdentity callerIdentity);
diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
index d882705c1835..83de0b304704 100644
--- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java
+++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java
@@ -243,15 +243,24 @@ public class LocationProviderManager extends
intent.putExtra(KEY_LOCATIONS, locationResult.asList().toArray(new Location[0]));
}
+ // send() SHOULD only run the completion callback if it completes successfully. however,
+ // b/199464864 (which could not be fixed in the S timeframe) means that it's possible
+ // for send() to throw an exception AND run the completion callback. if this happens, we
+ // would over-release the wakelock... we take matters into our own hands to ensure that
+ // the completion callback can only be run if send() completes successfully. this means
+ // the completion callback may be run inline - but as we've never specified what thread
+ // the callback is run on, this is fine.
+ GatedCallback gatedCallback = new GatedCallback(onCompleteCallback);
+
mPendingIntent.send(
mContext,
0,
intent,
- onCompleteCallback != null ? (pI, i, rC, rD, rE) -> onCompleteCallback.run()
- : null,
+ (pI, i, rC, rD, rE) -> gatedCallback.run(),
null,
null,
options.toBundle());
+ gatedCallback.allow();
}
@Override
@@ -1078,7 +1087,7 @@ public class LocationProviderManager extends
}
private void onTransportFailure(Exception e) {
- if (e instanceof RemoteException) {
+ if (e instanceof PendingIntent.CanceledException) {
Log.w(TAG, mName + " provider registration " + getIdentity() + " removed", e);
synchronized (mLock) {
remove();
@@ -1960,11 +1969,6 @@ public class LocationProviderManager extends
Preconditions.checkState(Thread.holdsLock(mLock));
}
- if (mDelayedRegister != null) {
- mAlarmHelper.cancel(mDelayedRegister);
- mDelayedRegister = null;
- }
-
// calculate how long the new request should be delayed before sending it off to the
// provider, under the assumption that once we send the request off, the provider will
// immediately attempt to deliver a new location satisfying that request.
@@ -1997,8 +2001,8 @@ public class LocationProviderManager extends
public void onAlarm() {
synchronized (mLock) {
if (mDelayedRegister == this) {
- setProviderRequest(newRequest);
mDelayedRegister = null;
+ setProviderRequest(newRequest);
}
}
}
@@ -2025,6 +2029,11 @@ public class LocationProviderManager extends
@GuardedBy("mLock")
void setProviderRequest(ProviderRequest request) {
+ if (mDelayedRegister != null) {
+ mAlarmHelper.cancel(mDelayedRegister);
+ mDelayedRegister = null;
+ }
+
EVENT_LOG.logProviderUpdateRequest(mName, request);
mProvider.getController().setRequest(request);
@@ -2734,4 +2743,49 @@ public class LocationProviderManager extends
}
}
}
+
+ private static class GatedCallback implements Runnable {
+
+ private @Nullable Runnable mCallback;
+
+ @GuardedBy("this")
+ private boolean mGate;
+ @GuardedBy("this")
+ private boolean mRun;
+
+ GatedCallback(Runnable callback) {
+ mCallback = callback;
+ }
+
+ public void allow() {
+ Runnable callback = null;
+ synchronized (this) {
+ mGate = true;
+ if (mRun && mCallback != null) {
+ callback = mCallback;
+ mCallback = null;
+ }
+ }
+
+ if (callback != null) {
+ callback.run();
+ }
+ }
+
+ @Override
+ public void run() {
+ Runnable callback = null;
+ synchronized (this) {
+ mRun = true;
+ if (mGate && mCallback != null) {
+ callback = mCallback;
+ mCallback = null;
+ }
+ }
+
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 9f02c3caa388..a57d7db0ec54 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -903,26 +903,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
if (intent.getAction().equals(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED)) {
BluetoothDevice btDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
synchronized (mLock) {
- boolean wasA2dpOn = mGlobalBluetoothA2dpOn;
mActiveBluetoothDevice = btDevice;
mGlobalBluetoothA2dpOn = btDevice != null;
- if (wasA2dpOn != mGlobalBluetoothA2dpOn) {
- Slog.d(TAG, "GlobalBluetoothA2dpOn is changed to '"
- + mGlobalBluetoothA2dpOn + "'");
- UserRecord userRecord = mUserRecords.get(mCurrentUserId);
- if (userRecord != null) {
- for (ClientRecord cr : userRecord.mClientRecords) {
- // mSelectedRouteId will be null for BT and phone speaker.
- if (cr.mSelectedRouteId == null) {
- try {
- cr.mClient.onGlobalA2dpChanged(mGlobalBluetoothA2dpOn);
- } catch (RemoteException e) {
- // Ignore exception
- }
- }
- }
- }
- }
}
}
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index bccc52fe350a..ddaaa1eeff4a 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1536,7 +1536,7 @@ abstract public class ManagedServices {
@Override
public void onNullBinding(ComponentName name) {
Slog.v(TAG, "onNullBinding() called with: name = [" + name + "]");
- mServicesBound.remove(servicesBindingTag);
+ mContext.unbindService(this);
}
};
if (!mContext.bindServiceAsUser(intent,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index b54e8f973bd6..7ba0f04a435f 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4363,8 +4363,7 @@ public class NotificationManagerService extends SystemService {
final int userId = r.getSbn().getUserId();
if (userId != info.userid && userId != UserHandle.USER_ALL &&
!mUserProfiles.isCurrentProfile(userId)) {
- throw new SecurityException("Disallowed call from listener: "
- + info.service);
+ continue;
}
cancelNotificationFromListenerLocked(info, callingUid, callingPid,
r.getSbn().getPackageName(), r.getSbn().getTag(),
@@ -4431,8 +4430,7 @@ public class NotificationManagerService extends SystemService {
final int userId = r.getSbn().getUserId();
if (userId != info.userid && userId != UserHandle.USER_ALL
&& !mUserProfiles.isCurrentProfile(userId)) {
- throw new SecurityException("Disallowed call from listener: "
- + info.service);
+ continue;
}
seen.add(r);
if (!r.isSeen()) {
@@ -5399,6 +5397,7 @@ public class NotificationManagerService extends SystemService {
== IMPORTANCE_NONE) {
cancelNotificationsFromListener(token, new String[]{r.getKey()});
} else {
+ r.setPendingLogUpdate(true);
needsSort = true;
}
}
@@ -7451,15 +7450,21 @@ public class NotificationManagerService extends SystemService {
sentAccessibilityEvent = true;
}
if (DBG) Slog.v(TAG, "Interrupting!");
+ boolean isInsistentUpdate = isInsistentUpdate(record);
if (hasValidSound) {
- if (isInCall()) {
- playInCallNotification();
+ if (isInsistentUpdate) {
+ // don't reset insistent sound, it's jarring
beep = true;
} else {
- beep = playSound(record, soundUri);
- }
- if(beep) {
- mSoundNotificationKey = key;
+ if (isInCall()) {
+ playInCallNotification();
+ beep = true;
+ } else {
+ beep = playSound(record, soundUri);
+ }
+ if (beep) {
+ mSoundNotificationKey = key;
+ }
}
}
@@ -7467,9 +7472,13 @@ public class NotificationManagerService extends SystemService {
mAudioManager.getRingerModeInternal()
== AudioManager.RINGER_MODE_SILENT;
if (!isInCall() && hasValidVibrate && !ringerModeSilent) {
- buzz = playVibration(record, vibration, hasValidSound);
- if(buzz) {
- mVibrateNotificationKey = key;
+ if (isInsistentUpdate) {
+ buzz = true;
+ } else {
+ buzz = playVibration(record, vibration, hasValidSound);
+ if (buzz) {
+ mVibrateNotificationKey = key;
+ }
}
}
} else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
@@ -7573,6 +7582,19 @@ public class NotificationManagerService extends SystemService {
}
@GuardedBy("mNotificationLock")
+ boolean isInsistentUpdate(final NotificationRecord record) {
+ return (Objects.equals(record.getKey(), mSoundNotificationKey)
+ || Objects.equals(record.getKey(), mVibrateNotificationKey))
+ && isCurrentlyInsistent();
+ }
+
+ @GuardedBy("mNotificationLock")
+ boolean isCurrentlyInsistent() {
+ return isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+ || isLoopingRingtoneNotification(mNotificationsByKey.get(mVibrateNotificationKey));
+ }
+
+ @GuardedBy("mNotificationLock")
boolean shouldMuteNotificationLocked(final NotificationRecord record) {
// Suppressed because it's a silent update
final Notification notification = record.getNotification();
@@ -7611,10 +7633,8 @@ public class NotificationManagerService extends SystemService {
return true;
}
- // A looping ringtone, such as an incoming call is playing
- if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
- || isLoopingRingtoneNotification(
- mNotificationsByKey.get(mVibrateNotificationKey))) {
+ // A different looping ringtone, such as an incoming call is playing
+ if (isCurrentlyInsistent() && !isInsistentUpdate(record)) {
return true;
}
@@ -8038,64 +8058,151 @@ public class NotificationManagerService extends SystemService {
}
}
+ static class NotificationRecordExtractorData {
+ // Class that stores any field in a NotificationRecord that can change via an extractor.
+ // Used to cache previous data used in a sort.
+ int mPosition;
+ int mVisibility;
+ boolean mShowBadge;
+ boolean mAllowBubble;
+ boolean mIsBubble;
+ NotificationChannel mChannel;
+ String mGroupKey;
+ ArrayList<String> mOverridePeople;
+ ArrayList<SnoozeCriterion> mSnoozeCriteria;
+ Integer mUserSentiment;
+ Integer mSuppressVisually;
+ ArrayList<Notification.Action> mSystemSmartActions;
+ ArrayList<CharSequence> mSmartReplies;
+ int mImportance;
+
+ // These fields may not trigger a reranking but diffs here may be logged.
+ float mRankingScore;
+ boolean mIsConversation;
+
+ NotificationRecordExtractorData(int position, int visibility, boolean showBadge,
+ boolean allowBubble, boolean isBubble, NotificationChannel channel, String groupKey,
+ ArrayList<String> overridePeople, ArrayList<SnoozeCriterion> snoozeCriteria,
+ Integer userSentiment, Integer suppressVisually,
+ ArrayList<Notification.Action> systemSmartActions,
+ ArrayList<CharSequence> smartReplies, int importance, float rankingScore,
+ boolean isConversation) {
+ mPosition = position;
+ mVisibility = visibility;
+ mShowBadge = showBadge;
+ mAllowBubble = allowBubble;
+ mIsBubble = isBubble;
+ mChannel = channel;
+ mGroupKey = groupKey;
+ mOverridePeople = overridePeople;
+ mSnoozeCriteria = snoozeCriteria;
+ mUserSentiment = userSentiment;
+ mSuppressVisually = suppressVisually;
+ mSystemSmartActions = systemSmartActions;
+ mSmartReplies = smartReplies;
+ mImportance = importance;
+ mRankingScore = rankingScore;
+ mIsConversation = isConversation;
+ }
+
+ // Returns whether the provided NotificationRecord differs from the cached data in any way.
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForRankingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || mVisibility != r.getPackageVisibilityOverride()
+ || mShowBadge != r.canShowBadge()
+ || mAllowBubble != r.canBubble()
+ || mIsBubble != r.getNotification().isBubbleNotification()
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSuppressVisually, r.getSuppressedVisualEffects())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance();
+ }
+
+ // Returns whether the NotificationRecord has a change from this data for which we should
+ // log an update. This method specifically targets fields that may be changed via
+ // adjustments from the assistant.
+ //
+ // Fields here are the union of things in NotificationRecordLogger.shouldLogReported
+ // and NotificationRecord.applyAdjustments.
+ //
+ // Should be guarded by mNotificationLock; not annotated here as this class is static.
+ boolean hasDiffForLoggingLocked(NotificationRecord r, int newPosition) {
+ return mPosition != newPosition
+ || !Objects.equals(mChannel, r.getChannel())
+ || !Objects.equals(mGroupKey, r.getGroupKey())
+ || !Objects.equals(mOverridePeople, r.getPeopleOverride())
+ || !Objects.equals(mSnoozeCriteria, r.getSnoozeCriteria())
+ || !Objects.equals(mUserSentiment, r.getUserSentiment())
+ || !Objects.equals(mSystemSmartActions, r.getSystemGeneratedSmartActions())
+ || !Objects.equals(mSmartReplies, r.getSmartReplies())
+ || mImportance != r.getImportance()
+ || !r.rankingScoreMatches(mRankingScore)
+ || mIsConversation != r.isConversation();
+ }
+ }
+
void handleRankingSort() {
if (mRankingHelper == null) return;
synchronized (mNotificationLock) {
final int N = mNotificationList.size();
// Any field that can change via one of the extractors needs to be added here.
- ArrayList<String> orderBefore = new ArrayList<>(N);
- int[] visibilities = new int[N];
- boolean[] showBadges = new boolean[N];
- boolean[] allowBubbles = new boolean[N];
- boolean[] isBubble = new boolean[N];
- ArrayList<NotificationChannel> channelBefore = new ArrayList<>(N);
- ArrayList<String> groupKeyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
- ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
- ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
- ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
- ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
- ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
- int[] importancesBefore = new int[N];
+ ArrayMap<String, NotificationRecordExtractorData> extractorDataBefore =
+ new ArrayMap<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- orderBefore.add(r.getKey());
- visibilities[i] = r.getPackageVisibilityOverride();
- showBadges[i] = r.canShowBadge();
- allowBubbles[i] = r.canBubble();
- isBubble[i] = r.getNotification().isBubbleNotification();
- channelBefore.add(r.getChannel());
- groupKeyBefore.add(r.getGroupKey());
- overridePeopleBefore.add(r.getPeopleOverride());
- snoozeCriteriaBefore.add(r.getSnoozeCriteria());
- userSentimentBefore.add(r.getUserSentiment());
- suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
- systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
- smartRepliesBefore.add(r.getSmartReplies());
- importancesBefore[i] = r.getImportance();
+ NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData(
+ i,
+ 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());
+ extractorDataBefore.put(r.getKey(), extractorData);
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
- if (!orderBefore.get(i).equals(r.getKey())
- || visibilities[i] != r.getPackageVisibilityOverride()
- || showBadges[i] != r.canShowBadge()
- || allowBubbles[i] != r.canBubble()
- || isBubble[i] != r.getNotification().isBubbleNotification()
- || !Objects.equals(channelBefore.get(i), r.getChannel())
- || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
- || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
- || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
- || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
- || !Objects.equals(suppressVisuallyBefore.get(i),
- r.getSuppressedVisualEffects())
- || !Objects.equals(systemSmartActionsBefore.get(i),
- r.getSystemGeneratedSmartActions())
- || !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())
- || importancesBefore[i] != r.getImportance()) {
+ if (!extractorDataBefore.containsKey(r.getKey())) {
+ // This shouldn't happen given that we just built this with all the
+ // notifications, but check just to be safe.
+ continue;
+ }
+ if (extractorDataBefore.get(r.getKey()).hasDiffForRankingLocked(r, i)) {
mHandler.scheduleSendRankingUpdate();
- return;
+ }
+
+ // If this notification is one for which we wanted to log an update, and
+ // sufficient relevant bits are different, log update.
+ if (r.hasPendingLogUpdate()) {
+ // We need to acquire the previous data associated with this specific
+ // notification, as the one at the current index may be unrelated if
+ // notification order has changed.
+ NotificationRecordExtractorData prevData = extractorDataBefore.get(r.getKey());
+ if (prevData.hasDiffForLoggingLocked(r, i)) {
+ mNotificationRecordLogger.logNotificationAdjusted(r, i, 0,
+ getGroupInstanceId(r.getSbn().getGroupKey()));
+ }
+
+ // Remove whether there was a diff or not; we've sorted the key, so if it
+ // turns out there was nothing to log, that's fine too.
+ r.setPendingLogUpdate(false);
}
}
}
@@ -8758,10 +8865,22 @@ public class NotificationManagerService extends SystemService {
void snoozeNotificationInt(String key, long duration, String snoozeCriterionId,
ManagedServiceInfo listener) {
- String listenerName = listener == null ? null : listener.component.toShortString();
+ if (listener == null) {
+ return;
+ }
+ String listenerName = listener.component.toShortString();
if ((duration <= 0 && snoozeCriterionId == null) || key == null) {
return;
}
+ synchronized (mNotificationLock) {
+ final NotificationRecord r = findInCurrentAndSnoozedNotificationByKeyLocked(key);
+ if (r == null) {
+ return;
+ }
+ if (!listener.enabledAndUserMatches(r.getSbn().getNormalizedUserId())){
+ return;
+ }
+ }
if (DBG) {
Slog.d(TAG, String.format("snooze event(%s, %d, %s, %s)", key, duration,
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index f66cfa99ce98..b4ca5118e10f 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -200,6 +200,10 @@ public final class NotificationRecord {
private boolean mIsAppImportanceLocked;
private ArraySet<Uri> mGrantableUris;
+ // Whether this notification record should have an update logged the next time notifications
+ // are sorted.
+ private boolean mPendingLogUpdate = false;
+
public NotificationRecord(Context context, StatusBarNotification sbn,
NotificationChannel channel) {
this.sbn = sbn;
@@ -648,17 +652,23 @@ public final class NotificationRecord {
final ArrayList<String> people =
adjustment.getSignals().getStringArrayList(Adjustment.KEY_PEOPLE);
setPeopleOverride(people);
+ EventLogTags.writeNotificationAdjusted(
+ getKey(), Adjustment.KEY_PEOPLE, people.toString());
}
if (signals.containsKey(Adjustment.KEY_SNOOZE_CRITERIA)) {
final ArrayList<SnoozeCriterion> snoozeCriterionList =
adjustment.getSignals().getParcelableArrayList(
Adjustment.KEY_SNOOZE_CRITERIA);
setSnoozeCriteria(snoozeCriterionList);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_SNOOZE_CRITERIA,
+ snoozeCriterionList.toString());
}
if (signals.containsKey(Adjustment.KEY_GROUP_KEY)) {
final String groupOverrideKey =
adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
setOverrideGroupKey(groupOverrideKey);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_GROUP_KEY,
+ groupOverrideKey);
}
if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
// Only allow user sentiment update from assistant if user hasn't already
@@ -667,27 +677,42 @@ public final class NotificationRecord {
&& (getChannel().getUserLockedFields() & USER_LOCKED_IMPORTANCE) == 0) {
setUserSentiment(adjustment.getSignals().getInt(
Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_USER_SENTIMENT,
+ Integer.toString(getUserSentiment()));
}
}
if (signals.containsKey(Adjustment.KEY_CONTEXTUAL_ACTIONS)) {
setSystemGeneratedSmartActions(
signals.getParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS));
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_CONTEXTUAL_ACTIONS,
+ getSystemGeneratedSmartActions().toString());
}
if (signals.containsKey(Adjustment.KEY_TEXT_REPLIES)) {
setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_TEXT_REPLIES));
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_TEXT_REPLIES,
+ getSmartReplies().toString());
}
if (signals.containsKey(Adjustment.KEY_IMPORTANCE)) {
int importance = signals.getInt(Adjustment.KEY_IMPORTANCE);
importance = Math.max(IMPORTANCE_UNSPECIFIED, importance);
importance = Math.min(IMPORTANCE_HIGH, importance);
setAssistantImportance(importance);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_IMPORTANCE,
+ Integer.toString(importance));
}
if (signals.containsKey(Adjustment.KEY_RANKING_SCORE)) {
mRankingScore = signals.getFloat(Adjustment.KEY_RANKING_SCORE);
+ EventLogTags.writeNotificationAdjusted(getKey(), Adjustment.KEY_RANKING_SCORE,
+ Float.toString(mRankingScore));
}
if (signals.containsKey(Adjustment.KEY_NOT_CONVERSATION)) {
mIsNotConversationOverride = signals.getBoolean(
Adjustment.KEY_NOT_CONVERSATION);
+ EventLogTags.writeNotificationAdjusted(getKey(),
+ Adjustment.KEY_NOT_CONVERSATION,
+ Boolean.toString(mIsNotConversationOverride));
}
if (!signals.isEmpty() && adjustment.getIssuer() != null) {
mAdjustmentIssuer = adjustment.getIssuer();
@@ -1478,6 +1503,24 @@ public final class NotificationRecord {
return sbn;
}
+ /**
+ * Returns whether this record's ranking score is approximately equal to otherScore
+ * (the difference must be within 0.0001).
+ */
+ public boolean rankingScoreMatches(float otherScore) {
+ return Math.abs(mRankingScore - otherScore) < 0.0001;
+ }
+
+ protected void setPendingLogUpdate(boolean pendingLogUpdate) {
+ mPendingLogUpdate = pendingLogUpdate;
+ }
+
+ // If a caller of this function subsequently logs the update, they should also call
+ // setPendingLogUpdate to false to make sure other callers don't also do so.
+ protected boolean hasPendingLogUpdate() {
+ return mPendingLogUpdate;
+ }
+
@VisibleForTesting
static final class Light {
public final int color;
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index 0e2ff7523c85..7d8564fe1190 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -59,6 +59,20 @@ public interface NotificationRecordLogger {
InstanceId groupId);
/**
+ * Logs a NotificationReported atom reflecting an adjustment to a notification.
+ * Unlike maybeLogNotificationPosted, this method is guaranteed to log a notification update,
+ * so the caller must take responsibility for checking that that logging update is necessary,
+ * and that the notification is meaningfully changed.
+ * @param r The NotificationRecord. If null, no action is taken.
+ * @param position The position at which this notification is ranked.
+ * @param buzzBeepBlink Logging code reflecting whether this notification alerted the user.
+ * @param groupId The instance Id of the group summary notification, or null.
+ */
+ void logNotificationAdjusted(@Nullable NotificationRecord r,
+ int position, int buzzBeepBlink,
+ InstanceId groupId);
+
+ /**
* Logs a notification cancel / dismiss event using UiEventReported (event ids from the
* NotificationCancelledEvents enum).
* @param r The NotificationRecord. If null, no action is taken.
@@ -96,7 +110,9 @@ public interface NotificationRecordLogger {
@UiEvent(doc = "New notification enqueued to post")
NOTIFICATION_POSTED(162),
@UiEvent(doc = "Notification substantially updated, or alerted again.")
- NOTIFICATION_UPDATED(163);
+ NOTIFICATION_UPDATED(163),
+ @UiEvent(doc = "Notification adjusted by assistant.")
+ NOTIFICATION_ADJUSTED(908);
private final int mId;
NotificationReportedEvent(int id) {
@@ -349,7 +365,8 @@ public interface NotificationRecordLogger {
&& Objects.equals(r.getSbn().getNotification().category,
old.getSbn().getNotification().category)
&& (r.getImportance() == old.getImportance())
- && (getLoggingImportance(r) == getLoggingImportance(old)));
+ && (getLoggingImportance(r) == getLoggingImportance(old))
+ && r.rankingScoreMatches(old.getRankingScore()));
}
/**
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
index 1a99fb0e55f3..1859ec4816a3 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLoggerImpl.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import android.annotation.Nullable;
+
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
@@ -37,33 +39,49 @@ public class NotificationRecordLoggerImpl implements NotificationRecordLogger {
if (!p.shouldLogReported(buzzBeepBlink)) {
return;
}
+ writeNotificationReportedAtom(p, NotificationReportedEvent.fromRecordPair(p),
+ position, buzzBeepBlink, groupId);
+ }
+
+ @Override
+ public void logNotificationAdjusted(@Nullable NotificationRecord r,
+ int position, int buzzBeepBlink,
+ InstanceId groupId) {
+ NotificationRecordPair p = new NotificationRecordPair(r, null);
+ writeNotificationReportedAtom(p, NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ position, buzzBeepBlink, groupId);
+ }
+
+ private void writeNotificationReportedAtom(NotificationRecordPair p,
+ NotificationReportedEvent eventType, int position, int buzzBeepBlink,
+ InstanceId groupId) {
FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_REPORTED,
- /* int32 event_id = 1 */ NotificationReportedEvent.fromRecordPair(p).getId(),
- /* int32 uid = 2 */ r.getUid(),
- /* string package_name = 3 */ r.getSbn().getPackageName(),
+ /* int32 event_id = 1 */ eventType.getId(),
+ /* int32 uid = 2 */ p.r.getUid(),
+ /* string package_name = 3 */ p.r.getSbn().getPackageName(),
/* int32 instance_id = 4 */ p.getInstanceId(),
/* int32 notification_id_hash = 5 */ p.getNotificationIdHash(),
/* int32 channel_id_hash = 6 */ p.getChannelIdHash(),
/* string group_id_hash = 7 */ p.getGroupIdHash(),
/* int32 group_instance_id = 8 */ (groupId == null) ? 0 : groupId.getId(),
- /* bool is_group_summary = 9 */ r.getSbn().getNotification().isGroupSummary(),
- /* string category = 10 */ r.getSbn().getNotification().category,
+ /* bool is_group_summary = 9 */ p.r.getSbn().getNotification().isGroupSummary(),
+ /* string category = 10 */ p.r.getSbn().getNotification().category,
/* int32 style = 11 */ p.getStyle(),
/* int32 num_people = 12 */ p.getNumPeople(),
/* int32 position = 13 */ position,
/* android.stats.sysui.NotificationImportance importance = 14 */
- NotificationRecordLogger.getLoggingImportance(r),
+ NotificationRecordLogger.getLoggingImportance(p.r),
/* int32 alerting = 15 */ buzzBeepBlink,
/* NotificationImportanceExplanation importance_source = 16 */
- r.getImportanceExplanationCode(),
+ p.r.getImportanceExplanationCode(),
/* android.stats.sysui.NotificationImportance importance_initial = 17 */
- r.getInitialImportance(),
+ p.r.getInitialImportance(),
/* NotificationImportanceExplanation importance_initial_source = 18 */
- r.getInitialImportanceExplanationCode(),
+ p.r.getInitialImportanceExplanationCode(),
/* android.stats.sysui.NotificationImportance importance_asst = 19 */
- r.getAssistantImportance(),
+ p.r.getAssistantImportance(),
/* int32 assistant_hash = 20 */ p.getAssistantHash(),
- /* float assistant_ranking_score = 21 */ r.getRankingScore()
+ /* float assistant_ranking_score = 21 */ p.r.getRankingScore()
);
}
diff --git a/services/core/java/com/android/server/notification/NotificationShellCmd.java b/services/core/java/com/android/server/notification/NotificationShellCmd.java
index 7112ae16c363..628a322bf8cd 100644
--- a/services/core/java/com/android/server/notification/NotificationShellCmd.java
+++ b/services/core/java/com/android/server/notification/NotificationShellCmd.java
@@ -22,6 +22,7 @@ import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -44,6 +45,8 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Slog;
@@ -392,19 +395,28 @@ public class NotificationShellCmd extends ShellCommand {
+ "--context <snooze-criterion-id>) <key>");
return 1;
}
- if (null == mDirectService.getNotificationRecord(key)) {
- pw.println("error: no notification matching key: " + key);
- return 1;
- }
if (duration > 0 || criterion != null) {
+ ShellNls nls = new ShellNls();
+ nls.registerAsSystemService(mDirectService.getContext(),
+ new ComponentName(nls.getClass().getPackageName(),
+ nls.getClass().getName()),
+ ActivityManager.getCurrentUser());
+ if (!waitForBind(nls)) {
+ pw.println("error: could not bind a listener in time");
+ return 1;
+ }
if (duration > 0) {
pw.println(String.format("snoozing <%s> until time: %s", key,
new Date(System.currentTimeMillis() + duration)));
+ nls.snoozeNotification(key, duration);
} else {
pw.println(String.format("snoozing <%s> until criterion: %s", key,
criterion));
+ nls.snoozeNotification(key, criterion);
}
- mDirectService.snoozeNotificationInt(key, duration, criterion, null);
+ waitForSnooze(nls, key);
+ nls.unregisterAsSystemService();
+ waitForUnbind(nls);
} else {
pw.println("error: invalid value for --" + subflag + ": " + flagarg);
return 1;
@@ -527,14 +539,17 @@ public class NotificationShellCmd extends ShellCommand {
final PendingIntent pi;
if ("broadcast".equals(intentKind)) {
pi = PendingIntent.getBroadcastAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED,
UserHandle.CURRENT);
} else if ("service".equals(intentKind)) {
pi = PendingIntent.getService(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED);
} else {
pi = PendingIntent.getActivityAsUser(
- context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
+ context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_MUTABLE_UNAUDITED, null,
UserHandle.CURRENT);
}
builder.setContentIntent(pi);
@@ -685,9 +700,79 @@ public class NotificationShellCmd extends ShellCommand {
return 0;
}
+ private void waitForSnooze(ShellNls nls, String key) {
+ for (int i = 0; i < 20; i++) {
+ StatusBarNotification[] sbns = nls.getSnoozedNotifications();
+ for (StatusBarNotification sbn : sbns) {
+ if (sbn.getKey().equals(key)) {
+ return;
+ }
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ return;
+ }
+
+ private boolean waitForBind(ShellNls nls) {
+ for (int i = 0; i < 20; i++) {
+ if (nls.isConnected) {
+ Slog.i(TAG, "Bound Shell NLS");
+ return true;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return false;
+ }
+
+ private void waitForUnbind(ShellNls nls) {
+ for (int i = 0; i < 10; i++) {
+ if (!nls.isConnected) {
+ Slog.i(TAG, "Unbound Shell NLS");
+ return;
+ } else {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
@Override
public void onHelp() {
getOutPrintWriter().println(USAGE);
}
+
+ @SuppressLint("OverrideAbstract")
+ private static class ShellNls extends NotificationListenerService {
+ private static ShellNls
+ sNotificationListenerInstance = null;
+ boolean isConnected;
+
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ sNotificationListenerInstance = this;
+ isConnected = true;
+ }
+ @Override
+ public void onListenerDisconnected() {
+ isConnected = false;
+ }
+
+ public static ShellNls getInstance() {
+ return sNotificationListenerInstance;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 96bde3df1e68..e8a3a8150377 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -330,8 +330,7 @@ public class PreferencesHelper implements RankingConfig {
}
}
- if (isShortcutOk(channel) && isDeletionOk(channel)
- && !channel.isSoundMissing()) {
+ if (isShortcutOk(channel) && isDeletionOk(channel)) {
r.channels.put(id, channel);
}
}
diff --git a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
index 947405ed2a78..b276c6f37201 100644
--- a/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
+++ b/services/core/java/com/android/server/os/DeviceIdentifiersPolicyService.java
@@ -19,10 +19,13 @@ package com.android.server.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
import android.os.Build;
import android.os.IDeviceIdentifiersPolicyService;
import android.os.RemoteException;
import android.os.SystemProperties;
+import android.os.UserHandle;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.server.SystemService;
@@ -65,11 +68,31 @@ public final class DeviceIdentifiersPolicyService extends SystemService {
@Override
public @Nullable String getSerialForPackage(String callingPackage,
String callingFeatureId) throws RemoteException {
+ if (!checkPackageBelongsToCaller(callingPackage)) {
+ throw new IllegalArgumentException(
+ "Invalid callingPackage or callingPackage does not belong to caller's uid:"
+ + Binder.getCallingUid());
+ }
+
if (!TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(mContext,
callingPackage, callingFeatureId, "getSerial")) {
return Build.UNKNOWN;
}
return SystemProperties.get("ro.serialno", Build.UNKNOWN);
}
+
+ private boolean checkPackageBelongsToCaller(String callingPackage) {
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ int callingPackageUid;
+ try {
+ callingPackageUid = mContext.getPackageManager().getPackageUidAsUser(
+ callingPackage, callingUserId);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+
+ return callingPackageUid == callingUid;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8e18508adc90..3e0a7fdd51ef 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2134,6 +2134,13 @@ public class PackageManagerService extends IPackageManager.Stub
boolean filterAppAccess(String packageName, int callingUid, int userId);
@LiveImplementation(override = LiveImplementation.MANDATORY)
void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState);
+ @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+ FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered);
+ @LiveImplementation(override = LiveImplementation.NOT_ALLOWED)
+ ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean debug, int userId);
}
/**
@@ -2916,7 +2923,24 @@ public class PackageManagerService extends IPackageManager.Stub
}
allHomeCandidates.addAll(resolveInfos);
- final String packageName = mDefaultAppProvider.getDefaultHome(userId);
+ String packageName = mDefaultAppProvider.getDefaultHome(userId);
+ if (packageName == null) {
+ // Role changes are not and cannot be atomic because its implementation lives inside
+ // a system app, so when the home role changes, there is a window when the previous
+ // role holder is removed and the new role holder is granted the preferred activity,
+ // but hasn't become the role holder yet. However, this case may be easily hit
+ // because the preferred activity change triggers a broadcast and receivers may try
+ // to get the default home activity there. So we need to fix it for this time
+ // window, and an easy workaround is to fallback to the current preferred activity.
+ final int appId = UserHandle.getAppId(Binder.getCallingUid());
+ final boolean filtered = appId >= Process.FIRST_APPLICATION_UID;
+ FindPreferredActivityBodyResult result = findPreferredActivityInternal(
+ intent, null, 0, resolveInfos, true, false, false, userId, filtered);
+ ResolveInfo preferredResolveInfo = result.mPreferredResolveInfo;
+ if (preferredResolveInfo != null && preferredResolveInfo.activityInfo != null) {
+ packageName = preferredResolveInfo.activityInfo.packageName;
+ }
+ }
if (packageName == null) {
return null;
}
@@ -4848,6 +4872,284 @@ public class PackageManagerService extends IPackageManager.Stub
}
} // switch
}
+
+ // The body of findPreferredActivity.
+ protected FindPreferredActivityBodyResult findPreferredActivityBody(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+ int callingUid, boolean isDeviceProvisioned) {
+ FindPreferredActivityBodyResult result = new FindPreferredActivityBodyResult();
+
+ flags = updateFlagsForResolve(
+ flags, userId, callingUid, false /*includeInstantApps*/,
+ isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId,
+ resolvedType, flags));
+ intent = updateIntentForResolve(intent);
+
+ // Try to find a matching persistent preferred activity.
+ result.mPreferredResolveInfo = findPersistentPreferredActivityLP(intent,
+ resolvedType, flags, query, debug, userId);
+
+ // If a persistent preferred activity matched, use it.
+ if (result.mPreferredResolveInfo != null) {
+ return result;
+ }
+
+ PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
+ // Get the list of preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
+ List<PreferredActivity> prefs = pir != null
+ ? pir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+ userId)
+ : null;
+ if (prefs != null && prefs.size() > 0) {
+
+ // First figure out how good the original match set is.
+ // We will only allow preferred activities that came
+ // from the same match quality.
+ int match = 0;
+
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
+
+ final int N = query.size();
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Match for " + ri.activityInfo
+ + ": 0x" + Integer.toHexString(match));
+ }
+ if (ri.match > match) {
+ match = ri.match;
+ }
+ }
+
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Best match: 0x" + Integer.toHexString(match));
+ }
+ match &= IntentFilter.MATCH_CATEGORY_MASK;
+ final int M = prefs.size();
+ for (int i = 0; i < M; i++) {
+ final PreferredActivity pa = prefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PreferredActivity ds="
+ + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
+ + "\n component=" + pa.mPref.mComponent);
+ pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ if (pa.mPref.mMatch != match) {
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Skipping bad match "
+ + Integer.toHexString(pa.mPref.mMatch));
+ }
+ continue;
+ }
+ // If it's not an "always" type preferred activity and that's what we're
+ // looking for, skip it.
+ if (always && !pa.mPref.mAlways) {
+ if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
+ continue;
+ }
+ final ActivityInfo ai = getActivityInfo(
+ pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
+ | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
+ && !isDeviceProvisioned;
+ final boolean allowSetMutation = !excludeSetupWizardHomeActivity
+ && !queryMayBeFiltered;
+ if (ai == null) {
+ // Do not remove launcher's preferred activity during SetupWizard
+ // due to it may not install yet
+ if (!allowSetMutation) {
+ continue;
+ }
+
+ // This previously registered preferred activity
+ // component is no longer known. Most likely an update
+ // to the app was installed and in the new version this
+ // component no longer exists. Clean it up by removing
+ // it from the preferred activities list, and skip it.
+ Slog.w(TAG, "Removing dangling preferred activity: "
+ + pa.mPref.mComponent);
+ pir.removeFilter(pa);
+ result.mChanged = true;
+ continue;
+ }
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+
+ if (removeMatches && allowSetMutation) {
+ pir.removeFilter(pa);
+ result.mChanged = true;
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
+ }
+ break;
+ }
+
+ // Okay we found a previously set preferred or last chosen app.
+ // If the result set is different from when this
+ // was created, and is not a subset of the preferred set, we need to
+ // clear it and re-ask the user their preference, if we're looking for
+ // an "always" type entry.
+
+ if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
+ if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
+ if (allowSetMutation) {
+ // some components of the set are no longer present in
+ // the query, but the preferred activity can still be reused
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Result set changed, but PreferredActivity"
+ + " is still valid as only non-preferred"
+ + " components were removed for " + intent
+ + " type " + resolvedType);
+ }
+ // remove obsolete components and re-add the up-to-date
+ // filter
+ PreferredActivity freshPa = new PreferredActivity(pa,
+ pa.mPref.mMatch,
+ pa.mPref.discardObsoleteComponents(query),
+ pa.mPref.mComponent,
+ pa.mPref.mAlways);
+ pir.removeFilter(pa);
+ pir.addFilter(freshPa);
+ result.mChanged = true;
+ } else {
+ if (DEBUG_PREFERRED) {
+ Slog.i(TAG, "Do not remove preferred activity");
+ }
+ }
+ } else {
+ if (allowSetMutation) {
+ Slog.i(TAG,
+ "Result set changed, dropping preferred activity "
+ + "for " + intent + " type "
+ + resolvedType);
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG,
+ "Removing preferred activity since set changed "
+ + pa.mPref.mComponent);
+ }
+ pir.removeFilter(pa);
+ // Re-add the filter as a "last chosen" entry (!always)
+ PreferredActivity lastChosen = new PreferredActivity(
+ pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
+ false);
+ pir.addFilter(lastChosen);
+ result.mChanged = true;
+ }
+ result.mPreferredResolveInfo = null;
+ return result;
+ }
+ }
+
+ // Yay! Either the set matched or we're looking for the last chosen
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning preferred activity: "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ result.mPreferredResolveInfo = ri;
+ return result;
+ }
+ }
+ }
+ return result;
+ }
+
+ public final FindPreferredActivityBodyResult findPreferredActivityInternal(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+
+ final int callingUid = Binder.getCallingUid();
+ // Do NOT hold the packages lock; this calls up into the settings provider which
+ // could cause a deadlock.
+ final boolean isDeviceProvisioned =
+ android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+ android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
+ // Find the preferred activity - the lock is held inside the method.
+ return findPreferredActivityBody(
+ intent, resolvedType, flags, query, always, removeMatches, debug,
+ userId, queryMayBeFiltered, callingUid, isDeviceProvisioned);
+ }
+
+ public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType,
+ int flags, List<ResolveInfo> query, boolean debug, int userId) {
+ final int N = query.size();
+ PersistentPreferredIntentResolver ppir =
+ mSettings.getPersistentPreferredActivities(userId);
+ // Get the list of persistent preferred activities that handle the intent
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Looking for persistent preferred activities...");
+ }
+ List<PersistentPreferredActivity> pprefs = ppir != null
+ ? ppir.queryIntent(intent, resolvedType,
+ (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
+ userId)
+ : null;
+ if (pprefs != null && pprefs.size() > 0) {
+ final int M = pprefs.size();
+ for (int i = 0; i < M; i++) {
+ final PersistentPreferredActivity ppa = pprefs.get(i);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Checking PersistentPreferredActivity ds="
+ + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
+ + "\n component=" + ppa.mComponent);
+ ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ }
+ final ActivityInfo ai = getActivityInfo(ppa.mComponent,
+ flags | MATCH_DISABLED_COMPONENTS, userId);
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Found persistent preferred activity:");
+ if (ai != null) {
+ ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
+ } else {
+ Slog.v(TAG, " null");
+ }
+ }
+ if (ai == null) {
+ // This previously registered persistent preferred activity
+ // component is no longer known. Ignore it and do NOT remove it.
+ continue;
+ }
+ for (int j = 0; j < N; j++) {
+ final ResolveInfo ri = query.get(j);
+ if (!ri.activityInfo.applicationInfo.packageName
+ .equals(ai.applicationInfo.packageName)) {
+ continue;
+ }
+ if (!ri.activityInfo.name.equals(ai.name)) {
+ continue;
+ }
+ // Found a persistent preference that can handle the intent.
+ if (DEBUG_PREFERRED || debug) {
+ Slog.v(TAG, "Returning persistent preferred activity: "
+ + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
+ }
+ return ri;
+ }
+ }
+ }
+ return null;
+ }
}
/**
@@ -5007,6 +5309,16 @@ public class PackageManagerService extends IPackageManager.Stub
super.dump(type, fd, pw, dumpState);
}
}
+ public final FindPreferredActivityBodyResult findPreferredActivityBody(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered,
+ int callingUid, boolean isDeviceProvisioned) {
+ synchronized (mLock) {
+ return super.findPreferredActivityBody(intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered, callingUid,
+ isDeviceProvisioned);
+ }
+ }
}
/**
@@ -5574,6 +5886,28 @@ public class PackageManagerService extends IPackageManager.Stub
current.release();
}
}
+ public final FindPreferredActivityBodyResult findPreferredActivityInternal(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ ThreadComputer current = live();
+ try {
+ return current.mComputer.findPreferredActivityInternal(intent, resolvedType, flags,
+ query, always, removeMatches, debug, userId, queryMayBeFiltered);
+ } finally {
+ current.release();
+ }
+ }
+ public final ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType, int flags, List<ResolveInfo> query, boolean debug,
+ int userId) {
+ ThreadComputer current = live();
+ try {
+ return current.mComputer.findPersistentPreferredActivityLP(intent, resolvedType,
+ flags, query, debug, userId);
+ } finally {
+ current.release();
+ }
+ }
}
@@ -7222,6 +7556,7 @@ public class PackageManagerService extends IPackageManager.Stub
t.traceEnd();
mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
+ mPermissionManager.readLegacyPermissionStateTEMP();
if (!mOnlyCore && mFirstBoot) {
requestCopyPreoptedFiles(mInjector);
@@ -7644,7 +7979,6 @@ public class PackageManagerService extends IPackageManager.Stub
+ ((SystemClock.uptimeMillis()-startTime)/1000f)
+ " seconds");
- mPermissionManager.readLegacyPermissionStateTEMP();
// If the build fingerprint has changed since the last time we booted,
// we need to re-grant app permission to catch any new ones that
// appear. This is really a hack, and means that apps can in some
@@ -9077,7 +9411,7 @@ public class PackageManagerService extends IPackageManager.Stub
/**
* Update given intent when being used to request {@link ResolveInfo}.
*/
- private Intent updateIntentForResolve(Intent intent) {
+ private static Intent updateIntentForResolve(Intent intent) {
if (intent.getSelector() != null) {
intent = intent.getSelector();
}
@@ -10249,7 +10583,7 @@ public class PackageManagerService extends IPackageManager.Stub
userId);
// Find any earlier preferred or last chosen entries and nuke them
findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, 0, false, true, false, userId);
+ intent, resolvedType, flags, query, false, true, false, userId);
// Add the new activity as the last chosen for this filter
addPreferredActivity(filter, match, null, activity, false, userId,
"Setting last chosen", false);
@@ -10265,7 +10599,7 @@ public class PackageManagerService extends IPackageManager.Stub
final List<ResolveInfo> query = queryIntentActivitiesInternal(intent, resolvedType, flags,
userId);
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, 0, false, false, false, userId);
+ intent, resolvedType, flags, query, false, false, false, userId);
}
private void requestInstantAppResolutionPhaseTwo(AuxiliaryResolveInfo responseObj,
@@ -10307,7 +10641,7 @@ public class PackageManagerService extends IPackageManager.Stub
// If we have saved a preference for a preferred activity for
// this Intent, use that.
ResolveInfo ri = findPreferredActivityNotLocked(intent, resolvedType,
- flags, query, r0.priority, true, false, debug, userId, queryMayBeFiltered);
+ flags, query, true, false, debug, userId, queryMayBeFiltered);
if (ri != null) {
return ri;
}
@@ -10420,287 +10754,72 @@ public class PackageManagerService extends IPackageManager.Stub
}
@GuardedBy("mLock")
- private ResolveInfo findPersistentPreferredActivityLP(Intent intent, String resolvedType,
+ private ResolveInfo findPersistentPreferredActivityLP(Intent intent,
+ String resolvedType,
int flags, List<ResolveInfo> query, boolean debug, int userId) {
- final int N = query.size();
- PersistentPreferredIntentResolver ppir = mSettings.getPersistentPreferredActivities(userId);
- // Get the list of persistent preferred activities that handle the intent
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for presistent preferred activities...");
- List<PersistentPreferredActivity> pprefs = ppir != null
- ? ppir.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId)
- : null;
- if (pprefs != null && pprefs.size() > 0) {
- final int M = pprefs.size();
- for (int i=0; i<M; i++) {
- final PersistentPreferredActivity ppa = pprefs.get(i);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Checking PersistentPreferredActivity ds="
- + (ppa.countDataSchemes() > 0 ? ppa.getDataScheme(0) : "<none>")
- + "\n component=" + ppa.mComponent);
- ppa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- }
- final ActivityInfo ai = getActivityInfo(ppa.mComponent,
- flags | MATCH_DISABLED_COMPONENTS, userId);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Found persistent preferred activity:");
- if (ai != null) {
- ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- } else {
- Slog.v(TAG, " null");
- }
- }
- if (ai == null) {
- // This previously registered persistent preferred activity
- // component is no longer known. Ignore it and do NOT remove it.
- continue;
- }
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (!ri.activityInfo.applicationInfo.packageName
- .equals(ai.applicationInfo.packageName)) {
- continue;
- }
- if (!ri.activityInfo.name.equals(ai.name)) {
- continue;
- }
- // Found a persistent preference that can handle the intent.
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Returning persistent preferred activity: " +
- ri.activityInfo.packageName + "/" + ri.activityInfo.name);
- }
- return ri;
- }
- }
- }
- return null;
+ return mComputer.findPersistentPreferredActivityLP(intent,
+ resolvedType,
+ flags, query, debug, userId);
}
- private boolean isHomeIntent(Intent intent) {
+ private static boolean isHomeIntent(Intent intent) {
return ACTION_MAIN.equals(intent.getAction())
&& intent.hasCategory(CATEGORY_HOME)
&& intent.hasCategory(CATEGORY_DEFAULT);
}
+
+ // findPreferredActivityBody returns two items: a "things changed" flag and a
+ // ResolveInfo, which is the preferred activity itself.
+ private static class FindPreferredActivityBodyResult {
+ boolean mChanged;
+ ResolveInfo mPreferredResolveInfo;
+ }
+
+ private FindPreferredActivityBodyResult findPreferredActivityInternal(
+ Intent intent, String resolvedType, int flags,
+ List<ResolveInfo> query, boolean always,
+ boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
+ return mComputer.findPreferredActivityInternal(
+ intent, resolvedType, flags,
+ query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ }
+
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, int priority, boolean always,
+ List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId) {
return findPreferredActivityNotLocked(
- intent, resolvedType, flags, query, priority, always, removeMatches, debug, userId,
+ intent, resolvedType, flags, query, always, removeMatches, debug, userId,
UserHandle.getAppId(Binder.getCallingUid()) >= Process.FIRST_APPLICATION_UID);
}
// TODO: handle preferred activities missing while user has amnesia
/** <b>must not hold {@link #mLock}</b> */
ResolveInfo findPreferredActivityNotLocked(Intent intent, String resolvedType, int flags,
- List<ResolveInfo> query, int priority, boolean always,
+ List<ResolveInfo> query, boolean always,
boolean removeMatches, boolean debug, int userId, boolean queryMayBeFiltered) {
if (Thread.holdsLock(mLock)) {
Slog.wtf(TAG, "Calling thread " + Thread.currentThread().getName()
+ " is holding mLock", new Throwable());
}
if (!mUserManager.exists(userId)) return null;
- final int callingUid = Binder.getCallingUid();
- // Do NOT hold the packages lock; this calls up into the settings provider which
- // could cause a deadlock.
- final boolean isDeviceProvisioned =
- android.provider.Settings.Global.getInt(mContext.getContentResolver(),
- android.provider.Settings.Global.DEVICE_PROVISIONED, 0) == 1;
- flags = updateFlagsForResolve(
- flags, userId, callingUid, false /*includeInstantApps*/,
- isImplicitImageCaptureIntentAndNotSetByDpcLocked(intent, userId, resolvedType,
- flags));
- intent = updateIntentForResolve(intent);
- // writer
- synchronized (mLock) {
- // Try to find a matching persistent preferred activity.
- ResolveInfo pri = findPersistentPreferredActivityLP(intent, resolvedType, flags, query,
- debug, userId);
- // If a persistent preferred activity matched, use it.
- if (pri != null) {
- return pri;
+ FindPreferredActivityBodyResult body = findPreferredActivityInternal(
+ intent, resolvedType, flags, query, always,
+ removeMatches, debug, userId, queryMayBeFiltered);
+ if (body.mChanged) {
+ if (DEBUG_PREFERRED) {
+ Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
}
-
- PreferredIntentResolver pir = mSettings.getPreferredActivities(userId);
- // Get the list of preferred activities that handle the intent
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities...");
- List<PreferredActivity> prefs = pir != null
- ? pir.queryIntent(intent, resolvedType,
- (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
- userId)
- : null;
- if (prefs != null && prefs.size() > 0) {
- boolean changed = false;
- try {
- // First figure out how good the original match set is.
- // We will only allow preferred activities that came
- // from the same match quality.
- int match = 0;
-
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match...");
-
- final int N = query.size();
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Match for " + ri.activityInfo
- + ": 0x" + Integer.toHexString(match));
- if (ri.match > match) {
- match = ri.match;
- }
- }
-
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x"
- + Integer.toHexString(match));
-
- match &= IntentFilter.MATCH_CATEGORY_MASK;
- final int M = prefs.size();
- for (int i=0; i<M; i++) {
- final PreferredActivity pa = prefs.get(i);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Checking PreferredActivity ds="
- + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>")
- + "\n component=" + pa.mPref.mComponent);
- pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- }
- if (pa.mPref.mMatch != match) {
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match "
- + Integer.toHexString(pa.mPref.mMatch));
- continue;
- }
- // If it's not an "always" type preferred activity and that's what we're
- // looking for, skip it.
- if (always && !pa.mPref.mAlways) {
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry");
- continue;
- }
- final ActivityInfo ai = getActivityInfo(
- pa.mPref.mComponent, flags | MATCH_DISABLED_COMPONENTS
- | MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
- userId);
- if (DEBUG_PREFERRED || debug) {
- Slog.v(TAG, "Found preferred activity:");
- if (ai != null) {
- ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " ");
- } else {
- Slog.v(TAG, " null");
- }
- }
- final boolean excludeSetupWizardHomeActivity = isHomeIntent(intent)
- && !isDeviceProvisioned;
- final boolean allowSetMutation = !excludeSetupWizardHomeActivity
- && !queryMayBeFiltered;
- if (ai == null) {
- // Do not remove launcher's preferred activity during SetupWizard
- // due to it may not install yet
- if (!allowSetMutation) {
- continue;
- }
-
- // This previously registered preferred activity
- // component is no longer known. Most likely an update
- // to the app was installed and in the new version this
- // component no longer exists. Clean it up by removing
- // it from the preferred activities list, and skip it.
- Slog.w(TAG, "Removing dangling preferred activity: "
- + pa.mPref.mComponent);
- pir.removeFilter(pa);
- changed = true;
- continue;
- }
- for (int j=0; j<N; j++) {
- final ResolveInfo ri = query.get(j);
- if (!ri.activityInfo.applicationInfo.packageName
- .equals(ai.applicationInfo.packageName)) {
- continue;
- }
- if (!ri.activityInfo.name.equals(ai.name)) {
- continue;
- }
-
- if (removeMatches && allowSetMutation) {
- pir.removeFilter(pa);
- changed = true;
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Removing match " + pa.mPref.mComponent);
- }
- break;
- }
-
- // Okay we found a previously set preferred or last chosen app.
- // If the result set is different from when this
- // was created, and is not a subset of the preferred set, we need to
- // clear it and re-ask the user their preference, if we're looking for
- // an "always" type entry.
-
- if (always && !pa.mPref.sameSet(query, excludeSetupWizardHomeActivity)) {
- if (pa.mPref.isSuperset(query, excludeSetupWizardHomeActivity)) {
- if (allowSetMutation) {
- // some components of the set are no longer present in
- // the query, but the preferred activity can still be reused
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Result set changed, but PreferredActivity"
- + " is still valid as only non-preferred"
- + " components were removed for " + intent
- + " type " + resolvedType);
- }
- // remove obsolete components and re-add the up-to-date
- // filter
- PreferredActivity freshPa = new PreferredActivity(pa,
- pa.mPref.mMatch,
- pa.mPref.discardObsoleteComponents(query),
- pa.mPref.mComponent,
- pa.mPref.mAlways);
- pir.removeFilter(pa);
- pir.addFilter(freshPa);
- changed = true;
- } else {
- if (DEBUG_PREFERRED) {
- Slog.i(TAG, "Do not remove preferred activity");
- }
- }
- } else {
- if (allowSetMutation) {
- Slog.i(TAG,
- "Result set changed, dropping preferred activity "
- + "for " + intent + " type "
- + resolvedType);
- if (DEBUG_PREFERRED) {
- Slog.v(TAG,
- "Removing preferred activity since set changed "
- + pa.mPref.mComponent);
- }
- pir.removeFilter(pa);
- // Re-add the filter as a "last chosen" entry (!always)
- PreferredActivity lastChosen = new PreferredActivity(
- pa, pa.mPref.mMatch, null, pa.mPref.mComponent,
- false);
- pir.addFilter(lastChosen);
- changed = true;
- }
- return null;
- }
- }
-
- // Yay! Either the set matched or we're looking for the last chosen
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Returning preferred activity: "
- + ri.activityInfo.packageName + "/" + ri.activityInfo.name);
- return ri;
- }
- }
- } finally {
- if (changed) {
- if (DEBUG_PREFERRED) {
- Slog.v(TAG, "Preferred activity bookkeeping changed; writing restrictions");
- }
- scheduleWritePackageRestrictionsLocked(userId);
- }
- }
+ synchronized (mLock) {
+ scheduleWritePackageRestrictionsLocked(userId);
}
}
- if (DEBUG_PREFERRED || debug) Slog.v(TAG, "No preferred activity to return");
- return null;
+ if ((DEBUG_PREFERRED || debug) && body.mPreferredResolveInfo == null) {
+ Slog.v(TAG, "No preferred activity to return");
+ }
+ return body.mPreferredResolveInfo;
}
/*
@@ -16053,8 +16172,7 @@ public class PackageManagerService extends IPackageManager.Stub
}
@VisibleForTesting(visibility = Visibility.PRIVATE)
- void sendPackagesSuspendedForUser(String[] pkgList, int[] uidList, int userId,
- boolean suspended) {
+ void sendPackagesSuspendedForUser(String intent, String[] pkgList, int[] uidList, int userId) {
final List<List<String>> pkgsToSend = new ArrayList(pkgList.length);
final List<IntArray> uidsToSend = new ArrayList(pkgList.length);
final List<SparseArray<int[]>> allowListsToSend = new ArrayList(pkgList.length);
@@ -16095,11 +16213,8 @@ public class PackageManagerService extends IPackageManager.Stub
extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidsToSend.get(i).toArray());
final SparseArray<int[]> allowList = allowListsToSend.get(i).size() == 0
? null : allowListsToSend.get(i);
- sendPackageBroadcast(
- suspended ? Intent.ACTION_PACKAGES_SUSPENDED
- : Intent.ACTION_PACKAGES_UNSUSPENDED,
- null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null,
- userIds, null, allowList, null);
+ sendPackageBroadcast(intent, null, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, null,
+ null, userIds, null, allowList, null);
}
}
@@ -16413,6 +16528,8 @@ public class PackageManagerService extends IPackageManager.Stub
final List<String> changedPackagesList = new ArrayList<>(packageNames.length);
final IntArray changedUids = new IntArray(packageNames.length);
+ final List<String> modifiedPackagesList = new ArrayList<>(packageNames.length);
+ final IntArray modifiedUids = new IntArray(packageNames.length);
final List<String> unactionedPackages = new ArrayList<>(packageNames.length);
final boolean[] canSuspend = suspended ? canSuspendPackageForUserInternal(packageNames,
userId) : null;
@@ -16440,13 +16557,14 @@ public class PackageManagerService extends IPackageManager.Stub
unactionedPackages.add(packageName);
continue;
}
- boolean packageUnsuspended;
+ final boolean packageUnsuspended;
+ final boolean packageModified;
synchronized (mLock) {
if (suspended) {
- pkgSetting.addOrUpdateSuspension(callingPackage, dialogInfo, appExtras,
- launcherExtras, userId);
+ packageModified = pkgSetting.addOrUpdateSuspension(callingPackage,
+ dialogInfo, appExtras, launcherExtras, userId);
} else {
- pkgSetting.removeSuspension(callingPackage, userId);
+ packageModified = pkgSetting.removeSuspension(callingPackage, userId);
}
packageUnsuspended = !suspended && !pkgSetting.getSuspended(userId);
}
@@ -16454,18 +16572,29 @@ public class PackageManagerService extends IPackageManager.Stub
changedPackagesList.add(packageName);
changedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
}
+ if (packageModified) {
+ modifiedPackagesList.add(packageName);
+ modifiedUids.add(UserHandle.getUid(userId, pkgSetting.appId));
+ }
}
if (!changedPackagesList.isEmpty()) {
- final String[] changedPackages = changedPackagesList.toArray(
- new String[changedPackagesList.size()]);
- sendPackagesSuspendedForUser(changedPackages, changedUids.toArray(), userId, suspended);
+ final String[] changedPackages = changedPackagesList.toArray(new String[0]);
+ sendPackagesSuspendedForUser(
+ suspended ? Intent.ACTION_PACKAGES_SUSPENDED
+ : Intent.ACTION_PACKAGES_UNSUSPENDED,
+ changedPackages, changedUids.toArray(), userId);
sendMyPackageSuspendedOrUnsuspended(changedPackages, suspended, userId);
synchronized (mLock) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
- return unactionedPackages.toArray(new String[unactionedPackages.size()]);
+ // Send the suspension changed broadcast to ensure suspension state is not stale.
+ if (!modifiedPackagesList.isEmpty()) {
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ modifiedPackagesList.toArray(new String[0]), modifiedUids.toArray(), userId);
+ }
+ return unactionedPackages.toArray(new String[0]);
}
@Override
@@ -16594,7 +16723,8 @@ public class PackageManagerService extends IPackageManager.Stub
final String[] packageArray = unsuspendedPackages.toArray(
new String[unsuspendedPackages.size()]);
sendMyPackageSuspendedOrUnsuspended(packageArray, false, userId);
- sendPackagesSuspendedForUser(packageArray, unsuspendedUids.toArray(), userId, false);
+ sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_UNSUSPENDED,
+ packageArray, unsuspendedUids.toArray(), userId);
}
}
@@ -22538,8 +22668,9 @@ public class PackageManagerService extends IPackageManager.Stub
removeKeystoreDataIfNeeded(mInjector.getUserManagerInternal(), userId, appId);
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(userId)) {
+ if (StorageManager.isUserKeyUnlocked(userId) && smInternal.isCeStoragePrepared(userId)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(userId)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -23479,7 +23610,7 @@ public class PackageManagerService extends IPackageManager.Stub
final List<ResolveInfo> resolveInfos = queryIntentActivitiesInternal(intent, null,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userId);
final ResolveInfo preferredResolveInfo = findPreferredActivityNotLocked(
- intent, null, 0, resolveInfos, 0, true, false, false, userId);
+ intent, null, 0, resolveInfos, true, false, false, userId);
final String packageName = preferredResolveInfo != null
&& preferredResolveInfo.activityInfo != null
? preferredResolveInfo.activityInfo.packageName : null;
@@ -25404,9 +25535,11 @@ public class PackageManagerService extends IPackageManager.Stub
// Reconcile app data for all started/unlocked users
final StorageManager sm = mInjector.getSystemService(StorageManager.class);
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
+ StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /* includeDying */)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)
+ && smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
@@ -25746,7 +25879,8 @@ public class PackageManagerService extends IPackageManager.Stub
StorageManagerInternal smInternal = mInjector.getLocalService(StorageManagerInternal.class);
for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
final int flags;
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ if (StorageManager.isUserKeyUnlocked(user.id)
+ && smInternal.isCeStoragePrepared(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
} else if (umInternal.isUserRunning(user.id)) {
flags = StorageManager.FLAG_STORAGE_DE;
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 88dd03333262..5536fc5aa506 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -455,7 +455,7 @@ public abstract class PackageSettingBase extends SettingBase {
return state.suspendParams != null && state.suspendParams.containsKey(suspendingPackage);
}
- void addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
+ boolean addOrUpdateSuspension(String suspendingPackage, SuspendDialogInfo dialogInfo,
PersistableBundle appExtras, PersistableBundle launcherExtras, int userId) {
final PackageUserState existingUserState = modifyUserState(userId);
final PackageUserState.SuspendParams newSuspendParams =
@@ -464,21 +464,27 @@ public abstract class PackageSettingBase extends SettingBase {
if (existingUserState.suspendParams == null) {
existingUserState.suspendParams = new ArrayMap<>();
}
- existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
+ final PackageUserState.SuspendParams oldSuspendParams =
+ existingUserState.suspendParams.put(suspendingPackage, newSuspendParams);
existingUserState.suspended = true;
onChanged();
+ return !Objects.equals(oldSuspendParams, newSuspendParams);
}
- void removeSuspension(String suspendingPackage, int userId) {
+ boolean removeSuspension(String suspendingPackage, int userId) {
+ boolean wasModified = false;
final PackageUserState existingUserState = modifyUserState(userId);
if (existingUserState.suspendParams != null) {
- existingUserState.suspendParams.remove(suspendingPackage);
+ if (existingUserState.suspendParams.remove(suspendingPackage) != null) {
+ wasModified = true;
+ }
if (existingUserState.suspendParams.size() == 0) {
existingUserState.suspendParams = null;
}
}
existingUserState.suspended = (existingUserState.suspendParams != null);
onChanged();
+ return wasModified;
}
void removeSuspension(Predicate<String> suspendingPackagePredicate, int userId) {
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index fcbf40e29933..62d6717e847a 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1664,6 +1664,19 @@ public class ShortcutService extends IShortcutService.Stub {
mContext.enforceCallingPermission(permission, message);
}
+ private void verifyCallerUserId(@UserIdInt int userId) {
+ if (isCallerSystem()) {
+ return; // no check
+ }
+
+ final int callingUid = injectBinderCallingUid();
+
+ // Otherwise, make sure the arguments are valid.
+ if (UserHandle.getUserId(callingUid) != userId) {
+ throw new SecurityException("Invalid user-ID");
+ }
+ }
+
private void verifyCaller(@NonNull String packageName, @UserIdInt int userId) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -2847,6 +2860,8 @@ public class ShortcutService extends IShortcutService.Stub {
@Override
public boolean isRequestPinItemSupported(int callingUserId, int requestType) {
+ verifyCallerUserId(callingUserId);
+
final long token = injectClearCallingIdentity();
try {
return mShortcutRequestPinProcessor
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index d4feb3a728c8..6d8137e74061 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -80,6 +80,7 @@ import android.os.UserManager;
import android.os.UserManager.EnforcingUser;
import android.os.UserManager.QuietModeFlag;
import android.os.storage.StorageManager;
+import android.os.storage.StorageManagerInternal;
import android.provider.Settings;
import android.security.GateKeeper;
import android.service.gatekeeper.IGateKeeperService;
@@ -4815,6 +4816,10 @@ public class UserManagerService extends IUserManager.Stub {
// Migrate only if build fingerprints mismatch
boolean migrateAppsData = !Build.FINGERPRINT.equals(userInfo.lastLoggedInFingerprint);
mUserDataPreparer.prepareUserData(userId, userSerial, StorageManager.FLAG_STORAGE_CE);
+
+ StorageManagerInternal smInternal = LocalServices.getService(StorageManagerInternal.class);
+ smInternal.markCeStoragePrepared(userId);
+
mPm.reconcileAppsData(userId, StorageManager.FLAG_STORAGE_CE, migrateAppsData);
}
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 7bea01820a03..46cb720cb2c0 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -558,11 +558,14 @@ final class DefaultPermissionGrantPolicy {
grantPermissionsToSystemPackage(pm, verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS);
// SetupWizard
- grantPermissionsToSystemPackage(pm,
- ArrayUtils.firstOrNull(getKnownPackages(
- PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)), userId,
- PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
- CAMERA_PERMISSIONS);
+ final String setupWizardPackage = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId));
+ grantPermissionsToSystemPackage(pm, setupWizardPackage, userId, PHONE_PERMISSIONS,
+ CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS, CAMERA_PERMISSIONS);
+ if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) {
+ grantPermissionsToSystemPackage(
+ pm, setupWizardPackage, userId, NEARBY_DEVICES_PERMISSIONS);
+ }
// Camera
grantPermissionsToSystemPackage(pm,
diff --git a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
index b1676d0e545f..ea554d3d7996 100644
--- a/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/LegacyPermissionManagerService.java
@@ -30,6 +30,7 @@ import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.permission.ILegacyPermissionManager;
+import android.util.EventLog;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -187,10 +188,25 @@ public class LegacyPermissionManagerService extends ILegacyPermissionManager.Stu
private void verifyCallerCanCheckAccess(String packageName, String message, int pid, int uid) {
// If the check is being requested by an app then only allow the app to query its own
// access status.
+ boolean reportError = false;
int callingUid = mInjector.getCallingUid();
int callingPid = mInjector.getCallingPid();
if (UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID && (callingUid != uid
|| callingPid != pid)) {
+ reportError = true;
+ }
+ // If the query is against an app on the device, then the check should only be allowed if
+ // the provided uid matches that of the specified package.
+ if (packageName != null && UserHandle.getAppId(uid) >= Process.FIRST_APPLICATION_UID) {
+ int packageUid = mInjector.getPackageUidForUser(packageName, UserHandle.getUserId(uid));
+ if (uid != packageUid) {
+ EventLog.writeEvent(0x534e4554, "193441322",
+ UserHandle.getAppId(callingUid) >= Process.FIRST_APPLICATION_UID
+ ? callingUid : uid, "Package uid mismatch");
+ reportError = true;
+ }
+ }
+ if (reportError) {
String response = String.format(
"Calling uid %d, pid %d cannot access for package %s (uid=%d, pid=%d): %s",
callingUid, callingPid, packageName, uid, pid, message);
@@ -385,12 +401,14 @@ public class LegacyPermissionManagerService extends ILegacyPermissionManager.Stu
@VisibleForTesting
public static class Injector {
private final Context mContext;
+ private final PackageManagerInternal mPackageManagerInternal;
/**
* Public constructor that accepts a {@code context} within which to operate.
*/
public Injector(@NonNull Context context) {
mContext = context;
+ mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
}
/**
@@ -453,5 +471,12 @@ public class LegacyPermissionManagerService extends ILegacyPermissionManager.Stu
return mContext.getPackageManager().getApplicationInfoAsUser(packageName, 0,
UserHandle.getUserHandleForUid(uid));
}
+
+ /**
+ * Returns the uid for the specified {@code packageName} under the provided {@code userId}.
+ */
+ public int getPackageUidForUser(String packageName, int userId) {
+ return mPackageManagerInternal.getPackageUid(packageName, 0, userId);
+ }
}
}
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 7b12709e4efd..54a6c678e0da 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1215,6 +1215,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private boolean checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(
@NonNull String permName) {
+ final String permissionPackageName;
final boolean isImmutablyRestrictedPermission;
synchronized (mLock) {
final Permission bp = mRegistry.getPermission(permName);
@@ -1222,15 +1223,25 @@ public class PermissionManagerService extends IPermissionManager.Stub {
Slog.w(TAG, "No such permissions: " + permName);
return false;
}
+ permissionPackageName = bp.getPackageName();
isImmutablyRestrictedPermission = bp.isHardOrSoftRestricted()
&& bp.isImmutablyRestricted();
}
+
+ final int callingUid = getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (mPackageManagerInt.filterAppAccess(permissionPackageName, callingUid, callingUserId)) {
+ EventLog.writeEvent(0x534e4554, "186404356", callingUid, permName);
+ return false;
+ }
+
if (isImmutablyRestrictedPermission && mContext.checkCallingOrSelfPermission(
Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Cannot modify allowlisting of an immutably "
+ "restricted permission: " + permName);
}
+
return true;
}
diff --git a/services/core/java/com/android/server/pm/verify/domain/OWNERS b/services/core/java/com/android/server/pm/verify/domain/OWNERS
index c669112e0512..445a8330037b 100644
--- a/services/core/java/com/android/server/pm/verify/domain/OWNERS
+++ b/services/core/java/com/android/server/pm/verify/domain/OWNERS
@@ -2,4 +2,3 @@
chiuwinson@google.com
patb@google.com
-toddke@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4a9772093bb7..e05f7ef135fa 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -484,6 +484,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int mLidNavigationAccessibility;
int mShortPressOnPowerBehavior;
int mLongPressOnPowerBehavior;
+ long mLongPressOnPowerAssistantTimeoutMs;
int mVeryLongPressOnPowerBehavior;
int mDoublePressOnPowerBehavior;
int mTriplePressOnPowerBehavior;
@@ -732,6 +733,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Settings.Global.POWER_BUTTON_LONG_PRESS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.Global.getUriFor(
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS), false, this,
UserHandle.USER_ALL);
resolver.registerContentObserver(Settings.Global.getUriFor(
@@ -1732,6 +1736,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_shortPressOnPowerBehavior);
mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
+ mLongPressOnPowerAssistantTimeoutMs = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerDurationMs);
mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
@@ -1955,7 +1961,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
*/
private final class PowerKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
PowerKeyRule(int gestures) {
- super(KEYCODE_POWER, gestures);
+ super(mContext, KEYCODE_POWER, gestures);
}
@Override
@@ -1970,6 +1976,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
@Override
+ long getLongPressTimeoutMs() {
+ if (getResolvedLongPressOnPowerBehavior() == LONG_PRESS_POWER_ASSISTANT) {
+ return mLongPressOnPowerAssistantTimeoutMs;
+ } else {
+ return super.getLongPressTimeoutMs();
+ }
+ }
+
+ @Override
void onLongPress(long eventTime) {
if (mSingleKeyGestureDetector.beganFromNonInteractive()
&& !mSupportLongPressPowerWhenNonInteractive) {
@@ -1997,7 +2012,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
*/
private final class BackKeyRule extends SingleKeyGestureDetector.SingleKeyRule {
BackKeyRule(int gestures) {
- super(KEYCODE_BACK, gestures);
+ super(mContext, KEYCODE_BACK, gestures);
}
@Override
@@ -2017,7 +2032,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void initSingleKeyGestureRules() {
- mSingleKeyGestureDetector = new SingleKeyGestureDetector(mContext);
+ mSingleKeyGestureDetector = new SingleKeyGestureDetector();
int powerKeyGestures = 0;
if (hasVeryLongPressOnPowerBehavior()) {
@@ -2115,6 +2130,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Settings.Global.POWER_BUTTON_LONG_PRESS,
mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior));
+ mLongPressOnPowerAssistantTimeoutMs = Settings.Global.getLong(
+ mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
+ mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_longPressOnPowerDurationMs));
mVeryLongPressOnPowerBehavior = Settings.Global.getInt(resolver,
Settings.Global.POWER_BUTTON_VERY_LONG_PRESS,
mContext.getResources().getInteger(
@@ -5367,6 +5387,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pw.print("mLongPressOnPowerBehavior=");
pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
pw.print(prefix);
+ pw.print("mLongPressOnPowerAssistantTimeoutMs=");
+ pw.println(mLongPressOnPowerAssistantTimeoutMs);
+ pw.print(prefix);
pw.print("mVeryLongPressOnPowerBehavior=");
pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
pw.print(prefix);
diff --git a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
index 1ef2bf9151e0..6fee69bf0472 100644
--- a/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
+++ b/services/core/java/com/android/server/policy/SingleKeyGestureDetector.java
@@ -44,9 +44,6 @@ public final class SingleKeyGestureDetector {
private static final int MSG_KEY_VERY_LONG_PRESS = 1;
private static final int MSG_KEY_DELAYED_PRESS = 2;
- private final long mLongPressTimeout;
- private final long mVeryLongPressTimeout;
-
private volatile int mKeyPressCounter;
private boolean mBeganFromNonInteractive = false;
@@ -86,12 +83,19 @@ public final class SingleKeyGestureDetector {
* </pre>
*/
abstract static class SingleKeyRule {
+
private final int mKeyCode;
private final int mSupportedGestures;
+ private final long mDefaultLongPressTimeout;
+ private final long mDefaultVeryLongPressTimeout;
- SingleKeyRule(int keyCode, @KeyGestureFlag int supportedGestures) {
+ SingleKeyRule(Context context, int keyCode, @KeyGestureFlag int supportedGestures) {
mKeyCode = keyCode;
mSupportedGestures = supportedGestures;
+ mDefaultLongPressTimeout =
+ ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
+ mDefaultVeryLongPressTimeout = context.getResources().getInteger(
+ com.android.internal.R.integer.config_veryLongPressTimeout);
}
/**
@@ -134,10 +138,28 @@ public final class SingleKeyGestureDetector {
*/
void onMultiPress(long downTime, int count) {}
/**
+ * Returns the timeout in milliseconds for a long press.
+ *
+ * If multipress is also supported, this should always be greater than the multipress
+ * timeout. If very long press is supported, this should always be less than the very long
+ * press timeout.
+ */
+ long getLongPressTimeoutMs() {
+ return mDefaultLongPressTimeout;
+ }
+ /**
* Callback when long press has been detected.
*/
void onLongPress(long eventTime) {}
/**
+ * Returns the timeout in milliseconds for a very long press.
+ *
+ * If long press is supported, this should always be longer than the long press timeout.
+ */
+ long getVeryLongPressTimeoutMs() {
+ return mDefaultVeryLongPressTimeout;
+ }
+ /**
* Callback when very long press has been detected.
*/
void onVeryLongPress(long eventTime) {}
@@ -151,10 +173,7 @@ public final class SingleKeyGestureDetector {
}
}
- public SingleKeyGestureDetector(Context context) {
- mLongPressTimeout = ViewConfiguration.get(context).getDeviceGlobalActionKeyTimeout();
- mVeryLongPressTimeout = context.getResources().getInteger(
- com.android.internal.R.integer.config_veryLongPressTimeout);
+ public SingleKeyGestureDetector() {
mHandler = new KeyHandler();
}
@@ -225,14 +244,14 @@ public final class SingleKeyGestureDetector {
final Message msg = mHandler.obtainMessage(MSG_KEY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, mLongPressTimeout);
+ mHandler.sendMessageDelayed(msg, mActiveRule.getLongPressTimeoutMs());
}
if (mActiveRule.supportVeryLongPress()) {
final Message msg = mHandler.obtainMessage(MSG_KEY_VERY_LONG_PRESS, keyCode, 0,
eventTime);
msg.setAsynchronous(true);
- mHandler.sendMessageDelayed(msg, mVeryLongPressTimeout);
+ mHandler.sendMessageDelayed(msg, mActiveRule.getVeryLongPressTimeoutMs());
}
} else {
mHandler.removeMessages(MSG_KEY_LONG_PRESS);
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 7555a7f2920b..c91d8dedc41b 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -96,6 +96,7 @@ public class Notifier {
private static final int MSG_BROADCAST_ENHANCED_PREDICTION = 4;
private static final int MSG_PROFILE_TIMED_OUT = 5;
private static final int MSG_WIRED_CHARGING_STARTED = 6;
+ private static final int MSG_SCREEN_POLICY = 7;
private static final long[] CHARGING_VIBRATION_TIME = {
40, 40, 40, 40, 40, 40, 40, 40, 40, // ramp-up sampling rate = 40ms
@@ -120,6 +121,7 @@ public class Notifier {
private final SuspendBlocker mSuspendBlocker;
private final WindowManagerPolicy mPolicy;
private final FaceDownDetector mFaceDownDetector;
+ private final ScreenUndimDetector mScreenUndimDetector;
private final ActivityManagerInternal mActivityManagerInternal;
private final InputManagerInternal mInputManagerInternal;
private final InputMethodManagerInternal mInputMethodManagerInternal;
@@ -167,13 +169,14 @@ public class Notifier {
public Notifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
mContext = context;
mBatteryStats = batteryStats;
mAppOps = mContext.getSystemService(AppOpsManager.class);
mSuspendBlocker = suspendBlocker;
mPolicy = policy;
mFaceDownDetector = faceDownDetector;
+ mScreenUndimDetector = screenUndimDetector;
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
mInputMethodManagerInternal = LocalServices.getService(InputMethodManagerInternal.class);
@@ -620,6 +623,22 @@ public class Notifier {
}
/**
+ * Called when the screen policy changes.
+ */
+ public void onScreenPolicyUpdate(int newPolicy) {
+ if (DEBUG) {
+ Slog.d(TAG, "onScreenPolicyUpdate: newPolicy=" + newPolicy);
+ }
+
+ synchronized (mLock) {
+ Message msg = mHandler.obtainMessage(MSG_SCREEN_POLICY);
+ msg.arg1 = newPolicy;
+ msg.setAsynchronous(true);
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /**
* Dumps data for bugreports.
*
* @param pw The stream to print to.
@@ -659,6 +678,7 @@ public class Notifier {
tm.notifyUserActivity();
mPolicy.userActivity();
mFaceDownDetector.userActivity(event);
+ mScreenUndimDetector.userActivity();
}
void postEnhancedDischargePredictionBroadcast(long delayMs) {
@@ -812,6 +832,10 @@ public class Notifier {
mSuspendBlocker.release();
}
+ private void screenPolicyChanging(int screenPolicy) {
+ mScreenUndimDetector.recordScreenPolicy(screenPolicy);
+ }
+
private void lockProfile(@UserIdInt int userId) {
mTrustManager.setDeviceLockedForUser(userId, true /*locked*/);
}
@@ -852,6 +876,9 @@ public class Notifier {
case MSG_WIRED_CHARGING_STARTED:
showWiredChargingStarted(msg.arg1);
break;
+ case MSG_SCREEN_POLICY:
+ screenPolicyChanging(msg.arg1);
+ break;
}
}
}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d982336f9d5f..e7e0425d9171 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -129,6 +129,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.Objects;
/**
@@ -273,6 +274,7 @@ public final class PowerManagerService extends SystemService
private final BatterySavingStats mBatterySavingStats;
private final AttentionDetector mAttentionDetector;
private final FaceDownDetector mFaceDownDetector;
+ private final ScreenUndimDetector mScreenUndimDetector;
private final BinderService mBinderService;
private final LocalService mLocalService;
private final NativeWrapper mNativeWrapper;
@@ -836,9 +838,10 @@ public final class PowerManagerService extends SystemService
static class Injector {
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return new Notifier(
- looper, context, batteryStats, suspendBlocker, policy, faceDownDetector);
+ looper, context, batteryStats, suspendBlocker, policy, faceDownDetector,
+ screenUndimDetector);
}
SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
@@ -959,6 +962,7 @@ public final class PowerManagerService extends SystemService
mInjector.createAmbientDisplaySuppressionController(context);
mAttentionDetector = new AttentionDetector(this::onUserAttention, mLock);
mFaceDownDetector = new FaceDownDetector(this::onFlip);
+ mScreenUndimDetector = new ScreenUndimDetector();
mBatterySavingStats = new BatterySavingStats(mLock);
mBatterySaverPolicy =
@@ -1145,7 +1149,7 @@ public final class PowerManagerService extends SystemService
mBatteryStats = BatteryStatsService.getService();
mNotifier = mInjector.createNotifier(Looper.getMainLooper(), mContext, mBatteryStats,
mInjector.createSuspendBlocker(this, "PowerManagerService.Broadcasts"),
- mPolicy, mFaceDownDetector);
+ mPolicy, mFaceDownDetector, mScreenUndimDetector);
mWirelessChargerDetector = mInjector.createWirelessChargerDetector(sensorManager,
mInjector.createSuspendBlocker(
@@ -1180,6 +1184,7 @@ public final class PowerManagerService extends SystemService
mBatterySaverController.systemReady();
mBatterySaverPolicy.systemReady();
mFaceDownDetector.systemReady(mContext);
+ mScreenUndimDetector.systemReady(mContext);
// Register for settings changes.
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -1495,7 +1500,11 @@ public final class PowerManagerService extends SystemService
mRequestWaitForNegativeProximity = true;
}
- wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+ try {
+ wakeLock.mLock.unlinkToDeath(wakeLock, 0);
+ } catch (NoSuchElementException e) {
+ Slog.wtf(TAG, "Failed to unlink wakelock", e);
+ }
removeWakeLockLocked(wakeLock, index);
}
}
@@ -3185,6 +3194,7 @@ public final class PowerManagerService extends SystemService
final boolean ready = mDisplayManagerInternal.requestPowerState(groupId,
displayPowerRequest, mRequestWaitForNegativeProximity);
+ mNotifier.onScreenPolicyUpdate(displayPowerRequest.policy);
if (DEBUG_SPEW) {
Slog.d(TAG, "updateDisplayPowerStateLocked: displayReady=" + ready
diff --git a/services/core/java/com/android/server/power/ScreenUndimDetector.java b/services/core/java/com/android/server/power/ScreenUndimDetector.java
new file mode 100644
index 000000000000..951bc1f76e6d
--- /dev/null
+++ b/services/core/java/com/android/server/power/ScreenUndimDetector.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Detects when user manually undims the screen (x times) and acquires a wakelock to keep the screen
+ * on temporarily (without changing the screen timeout setting).
+ */
+public class ScreenUndimDetector {
+ private static final String TAG = "ScreenUndimDetector";
+ private static final boolean DEBUG = false;
+
+ private static final String UNDIM_DETECTOR_WAKE_LOCK = "UndimDetectorWakeLock";
+
+ /** DeviceConfig flag: is keep screen on feature enabled. */
+ static final String KEY_KEEP_SCREEN_ON_ENABLED = "keep_screen_on_enabled";
+ private static final boolean DEFAULT_KEEP_SCREEN_ON_ENABLED = true;
+ private static final int OUTCOME_POWER_BUTTON =
+ FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__POWER_BUTTON;
+ private static final int OUTCOME_TIMEOUT =
+ FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME__TIMEOUT;
+ private boolean mKeepScreenOnEnabled;
+
+ /** DeviceConfig flag: how long should we keep the screen on. */
+ @VisibleForTesting
+ static final String KEY_KEEP_SCREEN_ON_FOR_MILLIS = "keep_screen_on_for_millis";
+ @VisibleForTesting
+ static final long DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS = TimeUnit.MINUTES.toMillis(10);
+ private long mKeepScreenOnForMillis;
+
+ /** DeviceConfig flag: how many user undims required to trigger keeping the screen on. */
+ @VisibleForTesting
+ static final String KEY_UNDIMS_REQUIRED = "undims_required";
+ @VisibleForTesting
+ static final int DEFAULT_UNDIMS_REQUIRED = 2;
+ private int mUndimsRequired;
+
+ /**
+ * DeviceConfig flag: what is the maximum duration between undims to still consider them
+ * consecutive.
+ */
+ @VisibleForTesting
+ static final String KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS =
+ "max_duration_between_undims_millis";
+ @VisibleForTesting
+ static final long DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS = TimeUnit.MINUTES.toMillis(5);
+ private long mMaxDurationBetweenUndimsMillis;
+
+ @VisibleForTesting
+ PowerManager.WakeLock mWakeLock;
+
+ @VisibleForTesting
+ int mCurrentScreenPolicy;
+ @VisibleForTesting
+ int mUndimCounter = 0;
+ @VisibleForTesting
+ long mUndimCounterStartedMillis;
+ private long mUndimOccurredTime = -1;
+ private long mInteractionAfterUndimTime = -1;
+ private InternalClock mClock;
+
+ public ScreenUndimDetector() {
+ mClock = new InternalClock();
+ }
+
+ ScreenUndimDetector(InternalClock clock) {
+ mClock = clock;
+ }
+
+ static class InternalClock {
+ public long getCurrentTime() {
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ /** Should be called in parent's systemReady() */
+ public void systemReady(Context context) {
+ readValuesFromDeviceConfig();
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ context.getMainExecutor(),
+ (properties) -> onDeviceConfigChange(properties.getKeyset()));
+
+ final PowerManager powerManager = context.getSystemService(PowerManager.class);
+ mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
+ | PowerManager.ON_AFTER_RELEASE,
+ UNDIM_DETECTOR_WAKE_LOCK);
+ }
+
+ /**
+ * Launches a message that figures out the screen transitions and detects user undims. Must be
+ * called by the parent that is trying to update the screen policy.
+ */
+ public void recordScreenPolicy(int newPolicy) {
+ if (newPolicy == mCurrentScreenPolicy) {
+ return;
+ }
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Screen policy transition: " + mCurrentScreenPolicy + " -> " + newPolicy);
+ }
+
+ // update the current policy with the new one immediately so we don't accidentally get
+ // into a loop (which is possible if the switch below triggers a new policy).
+ final int currentPolicy = mCurrentScreenPolicy;
+ mCurrentScreenPolicy = newPolicy;
+
+ if (!mKeepScreenOnEnabled) {
+ return;
+ }
+
+ switch (currentPolicy) {
+ case POLICY_DIM:
+ if (newPolicy == POLICY_BRIGHT) {
+ final long now = mClock.getCurrentTime();
+ final long timeElapsedSinceFirstUndim = now - mUndimCounterStartedMillis;
+ if (timeElapsedSinceFirstUndim >= mMaxDurationBetweenUndimsMillis) {
+ reset();
+ }
+ if (mUndimCounter == 0) {
+ mUndimCounterStartedMillis = now;
+ }
+
+ mUndimCounter++;
+
+ if (DEBUG) {
+ Slog.d(TAG, "User undim, counter=" + mUndimCounter
+ + " (required=" + mUndimsRequired + ")"
+ + ", timeElapsedSinceFirstUndim=" + timeElapsedSinceFirstUndim
+ + " (max=" + mMaxDurationBetweenUndimsMillis + ")");
+ }
+ if (mUndimCounter >= mUndimsRequired) {
+ reset();
+ if (DEBUG) {
+ Slog.d(TAG, "Acquiring a wake lock for " + mKeepScreenOnForMillis);
+ }
+ if (mWakeLock != null) {
+ mUndimOccurredTime = mClock.getCurrentTime();
+ mWakeLock.acquire(mKeepScreenOnForMillis);
+ }
+ }
+ } else {
+ if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+ checkAndLogUndim(OUTCOME_TIMEOUT);
+ }
+ reset();
+ }
+ break;
+ case POLICY_BRIGHT:
+ if (newPolicy == POLICY_OFF || newPolicy == POLICY_DOZE) {
+ checkAndLogUndim(OUTCOME_POWER_BUTTON);
+ }
+ if (newPolicy != POLICY_DIM) {
+ reset();
+ }
+ break;
+ }
+ }
+
+ @VisibleForTesting
+ void reset() {
+ if (DEBUG) {
+ Slog.d(TAG, "Resetting the undim detector");
+ }
+ mUndimCounter = 0;
+ mUndimCounterStartedMillis = 0;
+ if (mWakeLock != null && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ private boolean readKeepScreenOnNotificationEnabled() {
+ return DeviceConfig.getBoolean(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED,
+ DEFAULT_KEEP_SCREEN_ON_ENABLED);
+ }
+
+ private long readKeepScreenOnForMillis() {
+ return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_FOR_MILLIS,
+ DEFAULT_KEEP_SCREEN_ON_FOR_MILLIS);
+ }
+
+ private int readUndimsRequired() {
+ int undimsRequired = DeviceConfig.getInt(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ DEFAULT_UNDIMS_REQUIRED);
+
+ if (undimsRequired < 1 || undimsRequired > 5) {
+ Slog.e(TAG, "Provided undimsRequired=" + undimsRequired
+ + " is not allowed [1, 5]; using the default=" + DEFAULT_UNDIMS_REQUIRED);
+ return DEFAULT_UNDIMS_REQUIRED;
+ }
+
+ return undimsRequired;
+ }
+
+ private long readMaxDurationBetweenUndimsMillis() {
+ return DeviceConfig.getLong(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+ DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS);
+ }
+
+ private void onDeviceConfigChange(@NonNull Set<String> keys) {
+ for (String key : keys) {
+ Slog.i(TAG, "onDeviceConfigChange; key=" + key);
+ switch (key) {
+ case KEY_KEEP_SCREEN_ON_ENABLED:
+ case KEY_KEEP_SCREEN_ON_FOR_MILLIS:
+ case KEY_UNDIMS_REQUIRED:
+ case KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS:
+ readValuesFromDeviceConfig();
+ return;
+ default:
+ Slog.i(TAG, "Ignoring change on " + key);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void readValuesFromDeviceConfig() {
+ mKeepScreenOnEnabled = readKeepScreenOnNotificationEnabled();
+ mKeepScreenOnForMillis = readKeepScreenOnForMillis();
+ mUndimsRequired = readUndimsRequired();
+ mMaxDurationBetweenUndimsMillis = readMaxDurationBetweenUndimsMillis();
+
+ Slog.i(TAG, "readValuesFromDeviceConfig():"
+ + "\nmKeepScreenOnForMillis=" + mKeepScreenOnForMillis
+ + "\nmKeepScreenOnNotificationEnabled=" + mKeepScreenOnEnabled
+ + "\nmUndimsRequired=" + mUndimsRequired);
+
+ }
+
+ /**
+ * The user interacted with the screen after an undim, indicating the phone is in use.
+ * We use this event for logging.
+ */
+ public void userActivity() {
+ if (mUndimOccurredTime != 1 && mInteractionAfterUndimTime == -1) {
+ mInteractionAfterUndimTime = mClock.getCurrentTime();
+ }
+ }
+
+ /**
+ * Checks and logs if an undim occurred.
+ *
+ * A log will occur if an undim seems to have resulted in a timeout or a direct screen off such
+ * as from a power button. Some outcomes may not be correctly assigned to a
+ * TIMEOUT_AUTO_EXTENDED_REPORTED__OUTCOME value.
+ */
+ private void checkAndLogUndim(int outcome) {
+ if (mUndimOccurredTime != -1) {
+ long now = mClock.getCurrentTime();
+ FrameworkStatsLog.write(FrameworkStatsLog.TIMEOUT_AUTO_EXTENDED_REPORTED,
+ outcome,
+ /* time_to_outcome_millis=*/ now - mUndimOccurredTime,
+ /* time_to_first_interaction_millis= */
+ mInteractionAfterUndimTime != -1 ? now - mInteractionAfterUndimTime : -1
+ );
+ mUndimOccurredTime = -1;
+ mInteractionAfterUndimTime = -1;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
index 2a95416747a6..06253a08d937 100644
--- a/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
+++ b/services/core/java/com/android/server/powerstats/PowerStatsDataStorage.java
@@ -124,12 +124,8 @@ public class PowerStatsDataStorage {
@Override
public void read(InputStream in) throws IOException {
while (in.available() > 0) {
- try {
- DataElement dataElement = new DataElement(in);
- mCallback.onReadDataElement(dataElement.getData());
- } catch (IOException e) {
- Slog.e(TAG, "Failed to read from storage. " + e.getMessage());
- }
+ DataElement dataElement = new DataElement(in);
+ mCallback.onReadDataElement(dataElement.getData());
}
}
}
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3ad2e25..2450dbb6bfcd 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -8,4 +8,3 @@ sharaienko@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3a7e13b8d872..abe81e139b43 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -783,13 +783,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
- int userId, String opPackageName, long operationId,
+ int userId, long operationId, String opPackageName, long requestId,
@BiometricMultiSensorMode int multiSensorConfig) {
enforceBiometricDialog();
if (mBar != null) {
try {
mBar.showAuthenticationDialog(promptInfo, receiver, sensorIds, credentialAllowed,
- requireConfirmation, userId, opPackageName, operationId, multiSensorConfig);
+ requireConfirmation, userId, operationId, opPackageName, requestId,
+ multiSensorConfig);
} catch (RemoteException ex) {
}
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 771332071756..a51ed09790a4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -16,6 +16,7 @@
package com.android.server.wallpaper;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_LOCK;
import static android.app.WallpaperManager.FLAG_SYSTEM;
@@ -207,7 +208,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* wallpaper set and is created for the first time. The CLOSE_WRITE is triggered
* every time the wallpaper is changed.
*/
- private class WallpaperObserver extends FileObserver {
+ class WallpaperObserver extends FileObserver {
final int mUserId;
final WallpaperData mWallpaper;
@@ -225,7 +226,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
mWallpaperLockFile = new File(mWallpaperDir, WALLPAPER_LOCK_ORIG);
}
- private WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
+ WallpaperData dataForEvent(boolean sysChanged, boolean lockChanged) {
WallpaperData wallpaper = null;
synchronized (mLock) {
if (lockChanged) {
@@ -308,9 +309,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
wallpaper.imageWallpaperPending = false;
if (sysWallpaperChanged) {
+ IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "publish system wallpaper changed!");
+ }
+ notifyWallpaperChanged(wallpaper);
+ }
+ };
// If this was the system wallpaper, rebind...
bindWallpaperComponentLocked(mImageWallpaper, true,
- false, wallpaper, null);
+ false, wallpaper, callback);
notifyColorsWhich |= FLAG_SYSTEM;
}
if (lockWallpaperChanged
@@ -330,15 +340,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
saveSettingsLocked(wallpaper.userId);
-
- // Publish completion *after* we've persisted the changes
- if (wallpaper.setComplete != null) {
- try {
- wallpaper.setComplete.onWallpaperChanged();
- } catch (RemoteException e) {
- // if this fails we don't really care; the setting app may just
- // have crashed and that sort of thing is a fact of life.
- }
+ // Notify the client immediately if only lockscreen wallpaper changed.
+ if (lockWallpaperChanged && !sysWallpaperChanged) {
+ notifyWallpaperChanged(wallpaper);
}
}
}
@@ -352,6 +356,18 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ private void notifyWallpaperChanged(WallpaperData wallpaper) {
+ // Publish completion *after* we've persisted the changes
+ if (wallpaper.setComplete != null) {
+ try {
+ wallpaper.setComplete.onWallpaperChanged();
+ } catch (RemoteException e) {
+ // if this fails we don't really care; the setting app may just
+ // have crashed and that sort of thing is a fact of life.
+ }
+ }
+ }
+
private void notifyLockWallpaperChanged() {
final IWallpaperManagerCallback cb = mKeyguardListener;
if (cb != null) {
@@ -363,7 +379,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
- private void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
+ void notifyWallpaperColorsChanged(@NonNull WallpaperData wallpaper, int which) {
if (wallpaper.connection != null) {
wallpaper.connection.forEachDisplayConnector(connector -> {
notifyWallpaperColorsChangedOnDisplay(wallpaper, which, connector.mDisplayId);
@@ -567,7 +583,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* Once a new wallpaper has been written via setWallpaper(...), it needs to be cropped
* for display.
*/
- private void generateCrop(WallpaperData wallpaper) {
+ void generateCrop(WallpaperData wallpaper) {
boolean success = false;
// Only generate crop for default display.
@@ -2045,7 +2061,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
}
+ private boolean hasCrossUserPermission() {
+ final int interactPermission =
+ mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL);
+ return interactPermission == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @Override
public boolean hasNamedWallpaper(String name) {
+ final int callingUser = UserHandle.getCallingUserId();
+ final boolean allowCrossUser = hasCrossUserPermission();
+ if (DEBUG) {
+ Slog.d(TAG, "hasNamedWallpaper() caller " + Binder.getCallingUid()
+ + " cross-user?: " + allowCrossUser);
+ }
+
synchronized (mLock) {
List<UserInfo> users;
final long ident = Binder.clearCallingIdentity();
@@ -2055,6 +2085,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Binder.restoreCallingIdentity(ident);
}
for (UserInfo user: users) {
+ if (!allowCrossUser && callingUser != user.id) {
+ // No cross-user information for callers without permission
+ continue;
+ }
+
// ignore managed profiles
if (user.isManagedProfile()) {
continue;
@@ -2814,7 +2849,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return false;
}
- private boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
+ boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
if (DEBUG_LIVE) {
Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
@@ -3101,7 +3136,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return new JournaledFile(new File(base), new File(base + ".tmp"));
}
- private void saveSettingsLocked(int userId) {
+ void saveSettingsLocked(int userId) {
JournaledFile journal = makeJournaledFile(userId);
FileOutputStream fstream = null;
try {
@@ -3250,7 +3285,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
* Important: this method loads settings to initialize the given user's wallpaper data if
* there is no current in-memory state.
*/
- private WallpaperData getWallpaperSafeLocked(int userId, int which) {
+ WallpaperData getWallpaperSafeLocked(int userId, int which) {
// We're setting either just system (work with the system wallpaper),
// both (also work with the system wallpaper), or just the lock
// wallpaper (update against the existing lock wallpaper if any).
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 3a4faf73bfe1..e02e8671f211 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -21,6 +21,8 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_APPLICATION;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -53,6 +55,7 @@ import android.content.ComponentName;
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;
@@ -64,6 +67,7 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
+import android.os.UserHandle;
import android.service.voice.VoiceInteractionManagerInternal;
import android.util.Slog;
import android.view.RemoteAnimationDefinition;
@@ -74,6 +78,7 @@ import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.uri.NeededUriGrants;
import com.android.server.vr.VrManagerInternal;
@@ -557,20 +562,45 @@ class ActivityClientController extends IActivityClientController.Stub {
@Override
public int getLaunchedFromUid(IBinder token) {
+ if (!canGetLaunchedFrom()) {
+ return INVALID_UID;
+ }
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- return r != null ? r.launchedFromUid : android.os.Process.INVALID_UID;
+ return r != null ? r.launchedFromUid : INVALID_UID;
}
}
@Override
public String getLaunchedFromPackage(IBinder token) {
+ if (!canGetLaunchedFrom()) {
+ return null;
+ }
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
return r != null ? r.launchedFromPackage : null;
}
}
+ /** Whether the caller can get the package or uid that launched its activity. */
+ private boolean canGetLaunchedFrom() {
+ final int uid = Binder.getCallingUid();
+ if (UserHandle.getAppId(uid) == SYSTEM_UID) {
+ return true;
+ }
+ final PackageManagerInternal pm = mService.mWindowManager.mPmInternal;
+ final AndroidPackage callingPkg = pm.getPackage(uid);
+ if (callingPkg == null) {
+ return false;
+ }
+ if (callingPkg.isSignedWithPlatformKey()) {
+ return true;
+ }
+ final String[] installerNames = pm.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.getUserId(uid));
+ return installerNames.length > 0 && callingPkg.getPackageName().equals(installerNames[0]);
+ }
+
@Override
public void setRequestedOrientation(IBinder token, int requestedOrientation) {
final long origId = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d1f21778177d..b52e527b0556 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4157,7 +4157,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The activity now gets access to the data associated with this Intent.
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(intentGrants,
getUriPermissionsLocked());
- final ReferrerIntent rintent = new ReferrerIntent(intent, referrer);
+ final ReferrerIntent rintent = new ReferrerIntent(intent, getFilteredReferrer(referrer));
boolean unsent = true;
final boolean isTopActivityWhileSleeping = isTopRunningActivity() && isSleeping();
@@ -8514,6 +8514,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
+ * Gets the referrer package name with respect to package visibility. This method returns null
+ * if the given package is not visible to this activity.
+ */
+ String getFilteredReferrer(String referrerPackage) {
+ if (referrerPackage == null || (!referrerPackage.equals(packageName)
+ && mWmService.mPmInternal.filterAppAccess(
+ referrerPackage, info.applicationInfo.uid, mUserId))) {
+ return null;
+ }
+ return referrerPackage;
+ }
+
+ /**
* Determines whether this ActivityRecord can turn the screen on. It checks whether the flag
* {@link ActivityRecord#getTurnScreenOnFlag} is set and checks whether the ActivityRecord
* should be visible depending on Keyguard state.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index e3459a1edc0f..efa67e934543 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -848,9 +848,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
- r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
- r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
- r.takeOptions(), isTransitionForward,
+ r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
+ proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
+ results, newIntents, r.takeOptions(), isTransitionForward,
proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,
r.createFixedRotationAdjustmentsIfNeeded(), r.shareableActivityToken,
r.getLaunchedFromBubble()));
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 73d31bf7e0c8..d0457b08aa34 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -355,6 +355,8 @@ public class DisplayPolicy {
private PointerLocationView mPointerLocationView;
+ private int mDisplayCutoutTouchableRegionSize;
+
/**
* The area covered by system windows which belong to another display. Forwarded insets is set
* in case this is a virtual display, this is displayed on another display that has insets, and
@@ -1081,8 +1083,21 @@ public class DisplayPolicy {
(displayFrames, windowState, rect) -> {
rect.bottom = rect.top + getStatusBarHeight(displayFrames);
};
+ final TriConsumer<DisplayFrames, WindowState, Rect> gestureFrameProvider =
+ (displayFrames, windowState, rect) -> {
+ rect.bottom = rect.top + getStatusBarHeight(displayFrames);
+ final DisplayCutout cutout =
+ displayFrames.mInsetsState.getDisplayCutout();
+ if (cutout != null) {
+ final Rect top = cutout.getBoundingRectTop();
+ if (!top.isEmpty()) {
+ rect.bottom = rect.bottom + mDisplayCutoutTouchableRegionSize;
+ }
+ }
+ };
mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, win, frameProvider);
- mDisplayContent.setInsetProvider(ITYPE_TOP_MANDATORY_GESTURES, win, frameProvider);
+ mDisplayContent.setInsetProvider(
+ ITYPE_TOP_MANDATORY_GESTURES, win, gestureFrameProvider);
mDisplayContent.setInsetProvider(ITYPE_TOP_TAPPABLE_ELEMENT, win, frameProvider);
break;
case TYPE_NAVIGATION_BAR:
@@ -1993,11 +2008,14 @@ public class DisplayPolicy {
mStatusBarHeightForRotation[landscapeRotation] =
mStatusBarHeightForRotation[seascapeRotation] =
res.getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+ mDisplayCutoutTouchableRegionSize = res.getDimensionPixelSize(
+ R.dimen.display_cutout_touchable_region_size);
} else {
mStatusBarHeightForRotation[portraitRotation] =
mStatusBarHeightForRotation[upsideDownRotation] =
mStatusBarHeightForRotation[landscapeRotation] =
mStatusBarHeightForRotation[seascapeRotation] = 0;
+ mDisplayCutoutTouchableRegionSize = 0;
}
// Height of the navigation bar when presented horizontally at bottom
@@ -2980,6 +2998,7 @@ public class DisplayPolicy {
pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+ mSystemGestures.dump(pw, prefix);
pw.print(prefix); pw.println("Looper state:");
mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + " ");
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 73d6cecd9155..c9db14de507c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -444,7 +444,9 @@ public class DisplayRotation {
}
if (mDisplayContent.mFixedRotationTransitionListener
- .isTopFixedOrientationRecentsAnimating()) {
+ .isTopFixedOrientationRecentsAnimating()
+ // If screen is off or the device is going to sleep, then still allow to update.
+ && mService.mPolicy.okToAnimate(false /* ignoreScreenOn */)) {
// During the recents animation, the closing app might still be considered on top.
// In order to ignore its requested orientation to avoid a sensor led rotation (e.g
// user rotating the device while the recents animation is running), we ignore
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 18ea738b08ce..aa257f847e25 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -464,7 +464,8 @@ class DragState {
if (mDragInProgress && isValidDropTarget(newWin, containsAppExtras, interceptsGlobalDrag)) {
// Only allow the extras to be dispatched to a global-intercepting drag target
ClipData data = interceptsGlobalDrag ? mData.copyForTransferWithActivityInfo() : null;
- DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED, touchX, touchY,
+ DragEvent event = obtainDragEvent(DragEvent.ACTION_DRAG_STARTED,
+ newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY),
data, false /* includeDragSurface */,
null /* dragAndDropPermission */);
try {
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index da47328691c0..ed1e784bf275 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -90,6 +90,24 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
onSourceChanged();
}
+ @Override
+ protected boolean updateClientVisibility(InsetsControlTarget caller) {
+ boolean changed = super.updateClientVisibility(caller);
+ if (changed && caller.getRequestedVisibility(mSource.getType())) {
+ reportImeDrawnForOrganizer(caller);
+ }
+ return changed;
+ }
+
+ private void reportImeDrawnForOrganizer(InsetsControlTarget caller) {
+ if (caller.getWindow() != null && caller.getWindow().getTask() != null) {
+ if (caller.getWindow().getTask().isOrganized()) {
+ mWin.mWmService.mAtmService.mTaskOrganizerController.reportImeDrawnOnTask(
+ caller.getWindow().getTask());
+ }
+ }
+ }
+
private void onSourceChanged() {
if (mLastSource.equals(mSource)) {
return;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8c781a13f7db..d417d56b6d31 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -39,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -650,6 +651,7 @@ final class InputMonitor {
|| type == TYPE_DOCK_DIVIDER
|| type == TYPE_ACCESSIBILITY_OVERLAY
|| type == TYPE_INPUT_CONSUMER
- || type == TYPE_VOICE_INTERACTION;
+ || type == TYPE_VOICE_INTERACTION
+ || type == TYPE_STATUS_BAR_ADDITIONAL;
}
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index a7216da9bed5..054854aef121 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -106,13 +106,13 @@ class KeyguardController {
}
/**
- * @return {@code true} for default display when AOD is showing. Otherwise, same as
- * {@link #isKeyguardOrAodShowing(int)}
+ * @return {@code true} for default display when AOD is showing, not going away. Otherwise, same
+ * as {@link #isKeyguardOrAodShowing(int)}
* TODO(b/125198167): Replace isKeyguardOrAodShowing() by this logic.
*/
boolean isKeyguardUnoccludedOrAodShowing(int displayId) {
if (displayId == DEFAULT_DISPLAY && mAodShowing) {
- return true;
+ return !mKeyguardGoingAway;
}
return isKeyguardOrAodShowing(displayId);
}
@@ -461,7 +461,7 @@ class KeyguardController {
final KeyguardDisplayState state = getDisplayState(displayId);
if (isKeyguardUnoccludedOrAodShowing(displayId)) {
state.mSleepTokenAcquirer.acquire(displayId);
- } else if (!isKeyguardUnoccludedOrAodShowing(displayId)) {
+ } else {
state.mSleepTokenAcquirer.release(displayId);
}
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 3c6c23b08091..c7bf8ecfe949 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -59,11 +59,30 @@ class SurfaceAnimator {
@VisibleForTesting
final Animatable mAnimatable;
private final OnAnimationFinishedCallback mInnerAnimationFinishedCallback;
+
+ /**
+ * Static callback to run on all animations started through this SurfaceAnimator
+ * when an animation on a Surface is finished or cancelled without restart.
+ */
@VisibleForTesting
@Nullable
final OnAnimationFinishedCallback mStaticAnimationFinishedCallback;
+
+ /**
+ * Callback unique to each animation (i.e. AnimationAdapter). To be run when an animation on a
+ * Surface is finished or cancelled without restart.
+ */
+ @Nullable
+ private OnAnimationFinishedCallback mSurfaceAnimationFinishedCallback;
+
+ /**
+ * The callback is triggered after the SurfaceAnimator sends a cancel call to the underlying
+ * AnimationAdapter.
+ * NOTE: Must be called wherever we call onAnimationCancelled on mAnimation.
+ */
@Nullable
- private OnAnimationFinishedCallback mAnimationFinishedCallback;
+ private Runnable mAnimationCancelledCallback;
+
private boolean mAnimationStartDelayed;
/**
@@ -100,7 +119,7 @@ class SurfaceAnimator {
return;
}
final OnAnimationFinishedCallback animationFinishCallback =
- mAnimationFinishedCallback;
+ mSurfaceAnimationFinishedCallback;
reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);
if (staticAnimationFinishedCallback != null) {
staticAnimationFinishedCallback.onAnimationFinished(type, anim);
@@ -130,15 +149,19 @@ class SurfaceAnimator {
* This is important as it will start with the leash hidden or visible before
* handing it to the component that is responsible to run the animation.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
+ * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
+ * cancel call to the underlying AnimationAdapter.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
+ @Nullable Runnable animationCancelledCallback,
@Nullable SurfaceFreezer freezer) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
- mAnimationFinishedCallback = animationFinishedCallback;
+ mSurfaceAnimationFinishedCallback = animationFinishedCallback;
+ mAnimationCancelledCallback = animationCancelledCallback;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
@@ -161,14 +184,9 @@ class SurfaceAnimator {
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
- @AnimationType int type,
- @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
- startAnimation(t, anim, hidden, type, animationFinishedCallback, null /* freezer */);
- }
-
- void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type) {
- startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
+ startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */,
+ null /* animationCancelledCallback */, null /* freezer */);
}
/**
@@ -278,7 +296,8 @@ class SurfaceAnimator {
mLeash = from.mLeash;
mAnimation = from.mAnimation;
mAnimationType = from.mAnimationType;
- mAnimationFinishedCallback = from.mAnimationFinishedCallback;
+ mSurfaceAnimationFinishedCallback = from.mSurfaceAnimationFinishedCallback;
+ mAnimationCancelledCallback = from.mAnimationCancelledCallback;
// Cancel source animation, but don't let animation runner cancel the animation.
from.cancelAnimation(t, false /* restarting */, false /* forwardCancel */);
@@ -306,11 +325,16 @@ class SurfaceAnimator {
final SurfaceControl leash = mLeash;
final AnimationAdapter animation = mAnimation;
final @AnimationType int animationType = mAnimationType;
- final OnAnimationFinishedCallback animationFinishedCallback = mAnimationFinishedCallback;
+ final OnAnimationFinishedCallback animationFinishedCallback =
+ mSurfaceAnimationFinishedCallback;
+ final Runnable animationCancelledCallback = mAnimationCancelledCallback;
reset(t, false);
if (animation != null) {
if (!mAnimationStartDelayed && forwardCancel) {
animation.onAnimationCancelled(leash);
+ if (animationCancelledCallback != null) {
+ animationCancelledCallback.run();
+ }
}
if (!restarting) {
if (mStaticAnimationFinishedCallback != null) {
@@ -335,7 +359,7 @@ class SurfaceAnimator {
private void reset(Transaction t, boolean destroyLeash) {
mService.mAnimationTransferMap.remove(mAnimation);
mAnimation = null;
- mAnimationFinishedCallback = null;
+ mSurfaceAnimationFinishedCallback = null;
mAnimationType = ANIMATION_TYPE_NONE;
if (mLeash == null) {
return;
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 513b1b715a27..658f4efbdb2f 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -16,6 +16,12 @@
package com.android.server.wm;
+import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM;
+import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT;
+import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
+
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -33,6 +39,8 @@ import android.view.MotionEvent;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import android.widget.OverScroller;
+import java.io.PrintWriter;
+
/**
* Listens for system-wide input gestures, firing callbacks when detected.
* @hide
@@ -54,7 +62,8 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
private final Context mContext;
private final Handler mHandler;
private int mDisplayCutoutTouchableRegionSize;
- private int mSwipeStartThreshold;
+ // The thresholds for each edge of the display
+ private final Rect mSwipeStartThreshold = new Rect();
private int mSwipeDistanceThreshold;
private final Callbacks mCallbacks;
private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
@@ -66,7 +75,6 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
int screenHeight;
int screenWidth;
- private DisplayInfo mTmpDisplayInfo = new DisplayInfo();
private int mDownPointers;
private boolean mSwipeFireable;
private boolean mDebugFireable;
@@ -88,27 +96,41 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
void onConfigurationChanged() {
final Resources r = mContext.getResources();
+ final int defaultThreshold = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.system_gestures_start_threshold);
+ mSwipeStartThreshold.set(defaultThreshold, defaultThreshold, defaultThreshold,
+ defaultThreshold);
+ mSwipeDistanceThreshold = defaultThreshold;
+
final Display display = DisplayManagerGlobal.getInstance()
.getRealDisplay(Display.DEFAULT_DISPLAY);
- display.getDisplayInfo(mTmpDisplayInfo);
- mSwipeStartThreshold = mTmpDisplayInfo.logicalWidth > mTmpDisplayInfo.logicalHeight
- ? r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_landscape)
- : r.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height_portrait);
-
final DisplayCutout displayCutout = display.getCutout();
if (displayCutout != null) {
- final Rect bounds = displayCutout.getBoundingRectTop();
- if (!bounds.isEmpty()) {
- // Expand swipe start threshold such that we can catch touches that just start below
- // the notch area
- mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
- com.android.internal.R.dimen.display_cutout_touchable_region_size);
- mSwipeStartThreshold += mDisplayCutoutTouchableRegionSize;
+ // Expand swipe start threshold such that we can catch touches that just start beyond
+ // the notch area
+ mDisplayCutoutTouchableRegionSize = r.getDimensionPixelSize(
+ com.android.internal.R.dimen.display_cutout_touchable_region_size);
+ final Rect[] bounds = displayCutout.getBoundingRectsAll();
+ if (bounds[BOUNDS_POSITION_LEFT] != null) {
+ mSwipeStartThreshold.left = Math.max(mSwipeStartThreshold.left,
+ bounds[BOUNDS_POSITION_LEFT].width() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_TOP] != null) {
+ mSwipeStartThreshold.top = Math.max(mSwipeStartThreshold.top,
+ bounds[BOUNDS_POSITION_TOP].height() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_RIGHT] != null) {
+ mSwipeStartThreshold.right = Math.max(mSwipeStartThreshold.right,
+ bounds[BOUNDS_POSITION_RIGHT].width() + mDisplayCutoutTouchableRegionSize);
+ }
+ if (bounds[BOUNDS_POSITION_BOTTOM] != null) {
+ mSwipeStartThreshold.bottom = Math.max(mSwipeStartThreshold.bottom,
+ bounds[BOUNDS_POSITION_BOTTOM].height()
+ + mDisplayCutoutTouchableRegionSize);
}
}
- mSwipeDistanceThreshold = mSwipeStartThreshold;
if (DEBUG) Slog.d(TAG, "mSwipeStartThreshold=" + mSwipeStartThreshold
- + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
+ + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
}
private static <T> T checkNull(String name, T arg) {
@@ -275,22 +297,22 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
final long elapsed = time - mDownTime[i];
if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
+ " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
- if (fromY <= mSwipeStartThreshold
+ if (fromY <= mSwipeStartThreshold.top
&& y > fromY + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_TOP;
}
- if (fromY >= screenHeight - mSwipeStartThreshold
+ if (fromY >= screenHeight - mSwipeStartThreshold.bottom
&& y < fromY - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_BOTTOM;
}
- if (fromX >= screenWidth - mSwipeStartThreshold
+ if (fromX >= screenWidth - mSwipeStartThreshold.right
&& x < fromX - mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_RIGHT;
}
- if (fromX <= mSwipeStartThreshold
+ if (fromX <= mSwipeStartThreshold.left
&& x > fromX + mSwipeDistanceThreshold
&& elapsed < SWIPE_TIMEOUT_MS) {
return SWIPE_FROM_LEFT;
@@ -298,6 +320,15 @@ class SystemGesturesPointerEventListener implements PointerEventListener {
return SWIPE_NONE;
}
+ public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+ final String inner = prefix + " ";
+ pw.println(prefix + TAG + ":");
+ pw.print(inner); pw.print("mDisplayCutoutTouchableRegionSize=");
+ pw.println(mDisplayCutoutTouchableRegionSize);
+ pw.print(inner); pw.print("mSwipeStartThreshold="); pw.println(mSwipeStartThreshold);
+ pw.print(inner); pw.print("mSwipeDistanceThreshold="); pw.println(mSwipeDistanceThreshold);
+ }
+
private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
private OverScroller mOverscroller;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 963485554b5e..cae4f22f913f 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -5147,7 +5147,7 @@ class Task extends WindowContainer<WindowContainer> {
/**
* @return true if the task is currently focused.
*/
- private boolean isFocused() {
+ boolean isFocused() {
if (mDisplayContent == null || mDisplayContent.mCurrentFocus == null) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index d450dbffe4a1..ee4c629189dc 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -42,6 +42,9 @@ import static com.android.server.wm.Task.TASK_VISIBILITY_VISIBLE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static java.lang.Integer.MIN_VALUE;
+
+import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
@@ -80,6 +83,22 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
DisplayContent mDisplayContent;
/**
+ * A color layer that serves as a solid color background to certain animations.
+ */
+ private SurfaceControl mColorBackgroundLayer;
+
+ /**
+ * This counter is used to make sure we don't prematurely clear the background color in the
+ * case that background color animations are interleaved.
+ * NOTE: The last set color will remain until the counter is reset to 0, which means that an
+ * animation background color may sometime remain after the animation has finished through an
+ * animation with a different background color if an animation starts after and ends before
+ * another where both set different background colors. However, this is not a concern as
+ * currently all task animation backgrounds are the same color.
+ */
+ private int mColorLayerCounter = 0;
+
+ /**
* A control placed at the appropriate level for transitions to occur.
*/
private SurfaceControl mAppAnimationLayer;
@@ -961,6 +980,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
if (getParent() != null) {
super.onParentChanged(newParent, oldParent, () -> {
+ mColorBackgroundLayer = makeChildSurface(null)
+ .setColorLayer()
+ .setName("colorBackgroundLayer")
+ .setCallsite("TaskDisplayArea.onParentChanged")
+ .build();
mAppAnimationLayer = makeChildSurface(null)
.setName("animationLayer")
.setCallsite("TaskDisplayArea.onParentChanged")
@@ -977,6 +1001,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
.setName("splitScreenDividerAnchor")
.setCallsite("TaskDisplayArea.onParentChanged")
.build();
+
getSyncTransaction()
.show(mAppAnimationLayer)
.show(mBoostedAppAnimationLayer)
@@ -986,11 +1011,13 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
} else {
super.onParentChanged(newParent, oldParent);
mWmService.mTransactionFactory.get()
+ .remove(mColorBackgroundLayer)
.remove(mAppAnimationLayer)
.remove(mBoostedAppAnimationLayer)
.remove(mHomeAppAnimationLayer)
.remove(mSplitScreenDividerAnchor)
.apply();
+ mColorBackgroundLayer = null;
mAppAnimationLayer = null;
mBoostedAppAnimationLayer = null;
mHomeAppAnimationLayer = null;
@@ -998,6 +1025,39 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
}
+ void setBackgroundColor(@ColorInt int color) {
+ if (mColorBackgroundLayer == null) {
+ return;
+ }
+
+ float r = ((color >> 16) & 0xff) / 255.0f;
+ float g = ((color >> 8) & 0xff) / 255.0f;
+ float b = ((color >> 0) & 0xff) / 255.0f;
+ float a = ((color >> 24) & 0xff) / 255.0f;
+
+ mColorLayerCounter++;
+
+ getPendingTransaction().setLayer(mColorBackgroundLayer, MIN_VALUE)
+ .setColor(mColorBackgroundLayer, new float[]{r, g, b})
+ .setAlpha(mColorBackgroundLayer, a)
+ .setWindowCrop(mColorBackgroundLayer, getSurfaceWidth(), getSurfaceHeight())
+ .setPosition(mColorBackgroundLayer, 0, 0)
+ .show(mColorBackgroundLayer);
+
+ scheduleAnimation();
+ }
+
+ void clearBackgroundColor() {
+ mColorLayerCounter--;
+
+ // Only clear the color layer if we have received the same amounts of clear as set
+ // requests.
+ if (mColorLayerCounter == 0) {
+ getPendingTransaction().hide(mColorBackgroundLayer);
+ scheduleAnimation();
+ }
+ }
+
@Override
void migrateToNewSurfaceControl(SurfaceControl.Transaction t) {
super.migrateToNewSurfaceControl(t);
@@ -1006,6 +1066,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
// As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
+ t.reparent(mColorBackgroundLayer, mSurfaceControl);
t.reparent(mAppAnimationLayer, mSurfaceControl);
t.reparent(mBoostedAppAnimationLayer, mSurfaceControl);
t.reparent(mHomeAppAnimationLayer, mSurfaceControl);
@@ -2149,6 +2210,11 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
}
@Override
+ TaskDisplayArea getTaskDisplayArea() {
+ return this;
+ }
+
+ @Override
boolean isTaskDisplayArea() {
return true;
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 09c5581385dd..88467baa6c34 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -733,6 +733,17 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mPendingTaskEvents.clear();
}
+ void reportImeDrawnOnTask(Task task) {
+ final TaskOrganizerState state = mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+ if (state != null) {
+ try {
+ state.mOrganizer.mTaskOrganizer.onImeDrawnOnTask(task.mTaskId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception sending onImeDrawnOnTask callback", e);
+ }
+ }
+ }
+
void onTaskInfoChanged(Task task, boolean force) {
if (!task.mTaskAppearedSent) {
// Skip if task still not appeared.
diff --git a/services/core/java/com/android/server/wm/WindowAnimationSpec.java b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
index ec4eb5dc85de..073a508083f0 100644
--- a/services/core/java/com/android/server/wm/WindowAnimationSpec.java
+++ b/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@@ -118,16 +118,30 @@ public class WindowAnimationSpec implements AnimationSpec {
@Override
public long calculateStatusBarTransitionStartTime() {
TranslateAnimation openTranslateAnimation = findTranslateAnimation(mAnimation);
- if (openTranslateAnimation != null) {
- // Some interpolators are extremely quickly mostly finished, but not completely. For
- // our purposes, we need to find the fraction for which ther interpolator is mostly
- // there, and use that value for the calculation.
- float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
- return SystemClock.uptimeMillis()
- + openTranslateAnimation.getStartOffset()
- + (long)(openTranslateAnimation.getDuration() * t)
- - STATUS_BAR_TRANSITION_DURATION;
+ if (openTranslateAnimation != null) {
+ if (openTranslateAnimation.isXAxisTransition()
+ && openTranslateAnimation.isFullWidthTranslate()) {
+ // On X axis transitions that are fullscreen (heuristic for task like transitions)
+ // we want the status bar to animate right in the middle of the translation when
+ // the windows/tasks have each moved half way across.
+ float t = findMiddleOfTranslationFraction(openTranslateAnimation.getInterpolator());
+
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long) (openTranslateAnimation.getDuration() * t)
+ - (long) (STATUS_BAR_TRANSITION_DURATION * 0.5);
+ } else {
+ // Some interpolators are extremely quickly mostly finished, but not completely. For
+ // our purposes, we need to find the fraction for which their interpolator is mostly
+ // there, and use that value for the calculation.
+ float t = findAlmostThereFraction(openTranslateAnimation.getInterpolator());
+
+ return SystemClock.uptimeMillis()
+ + openTranslateAnimation.getStartOffset()
+ + (long) (openTranslateAnimation.getDuration() * t)
+ - STATUS_BAR_TRANSITION_DURATION;
+ }
} else {
return SystemClock.uptimeMillis();
}
@@ -176,20 +190,39 @@ public class WindowAnimationSpec implements AnimationSpec {
}
/**
- * Binary searches for a {@code t} such that there exists a {@code -0.01 < eps < 0.01} for which
- * {@code interpolator(t + eps) > 0.99}.
+ * Finds the fraction of the animation's duration at which the transition is almost done with a
+ * maximal error of 0.01 when it is animated with {@code interpolator}.
*/
private static float findAlmostThereFraction(Interpolator interpolator) {
+ return findInterpolationAdjustedTargetFraction(interpolator, 0.99f, 0.01f);
+ }
+
+ /**
+ * Finds the fraction of the animation's duration at which the transition is spacially half way
+ * done with a maximal error of 0.01 when it is animated with {@code interpolator}.
+ */
+ private float findMiddleOfTranslationFraction(Interpolator interpolator) {
+ return findInterpolationAdjustedTargetFraction(interpolator, 0.5f, 0.01f);
+ }
+
+ /**
+ * Binary searches for a {@code val} such that there exists an {@code -0.01 < epsilon < 0.01}
+ * for which {@code interpolator(val + epsilon) > target}.
+ */
+ private static float findInterpolationAdjustedTargetFraction(
+ Interpolator interpolator, float target, float epsilon) {
float val = 0.5f;
float adj = 0.25f;
- while (adj >= 0.01f) {
- if (interpolator.getInterpolation(val) < 0.99f) {
+
+ while (adj >= epsilon) {
+ if (interpolator.getInterpolation(val) < target) {
val += adj;
} else {
val -= adj;
}
adj /= 2;
}
+
return val;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index b1c7e196b70c..d99aed1b409a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -30,6 +30,10 @@ import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.os.UserHandle.USER_NULL;
import static android.view.SurfaceControl.Transaction;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_BACK;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_TO_FRONT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS_ANIM;
@@ -59,10 +63,13 @@ import static com.android.server.wm.WindowManagerService.logWithStack;
import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.app.WindowConfiguration;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -88,6 +95,7 @@ import android.view.animation.Animation;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
@@ -850,6 +858,12 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
return parent != null ? parent.getRootDisplayArea() : null;
}
+ @Nullable
+ TaskDisplayArea getTaskDisplayArea() {
+ WindowContainer parent = getParent();
+ return parent != null ? parent.getTaskDisplayArea() : null;
+ }
+
boolean isAttached() {
return getDisplayArea() != null;
}
@@ -2495,10 +2509,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* some point but the meaning is too weird to work for all containers.
* @param type The type of animation defined as {@link AnimationType}.
* @param animationFinishedCallback The callback being triggered when the animation finishes.
+ * @param animationCancelledCallback The callback is triggered after the SurfaceAnimator sends a
+ * cancel call to the underlying AnimationAdapter.
*/
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@AnimationType int type,
- @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+ @Nullable OnAnimationFinishedCallback animationFinishedCallback,
+ @Nullable Runnable animationCancelledCallback) {
if (DEBUG_ANIM) {
Slog.v(TAG, "Starting animation on " + this + ": type=" + type + ", anim=" + anim);
}
@@ -2506,7 +2523,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
// TODO: This should use isVisible() but because isVisible has a really weird meaning at
// the moment this doesn't work for all animatable window containers.
mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
- mSurfaceFreezer);
+ animationCancelledCallback, mSurfaceFreezer);
+ }
+
+ void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
+ @AnimationType int type,
+ @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
+ startAnimation(t, anim, hidden, type, animationFinishedCallback,
+ null /* adapterAnimationCancelledCallback */);
}
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
@@ -2714,8 +2738,26 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (sources != null) {
mSurfaceAnimationSources.addAll(sources);
}
+
+ TaskDisplayArea taskDisplayArea = getTaskDisplayArea();
+ boolean isSettingBackgroundColor = taskDisplayArea != null
+ && isTransitionWithBackgroundColor(transit);
+
+ if (isSettingBackgroundColor) {
+ Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ @ColorInt int backgroundColor = uiContext.getColor(R.color.overview_background);
+
+ taskDisplayArea.setBackgroundColor(backgroundColor);
+ }
+
+ final Runnable cleanUpCallback = isSettingBackgroundColor
+ ? taskDisplayArea::clearBackgroundColor : () -> {};
+
startAnimation(getPendingTransaction(), adapter, !isVisible(),
- ANIMATION_TYPE_APP_TRANSITION);
+ ANIMATION_TYPE_APP_TRANSITION,
+ (type, anim) -> cleanUpCallback.run(),
+ cleanUpCallback);
+
if (adapter.getShowWallpaper()) {
getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
@@ -2726,6 +2768,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
}
+ private boolean isTransitionWithBackgroundColor(@TransitionOldType int transit) {
+ return transit == TRANSIT_OLD_TASK_OPEN
+ || transit == TRANSIT_OLD_TASK_CLOSE
+ || transit == TRANSIT_OLD_TASK_TO_FRONT
+ || transit == TRANSIT_OLD_TASK_TO_BACK;
+ }
+
final SurfaceAnimationRunner getSurfaceAnimationRunner() {
return mWmService.mSurfaceAnimationRunner;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3421b28454f0..68e85c85b995 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2727,8 +2727,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public boolean attachWindowContextToDisplayArea(IBinder clientToken, int type, int displayId,
- Bundle options) {
+ public Configuration attachWindowContextToDisplayArea(IBinder clientToken, int
+ type, int displayId, Bundle options) {
final boolean callerCanManageAppTokens = checkCallingPermission(MANAGE_APP_TOKENS,
"attachWindowContextToDisplayArea", false /* printLog */);
final int callingUid = Binder.getCallingUid();
@@ -2739,15 +2739,17 @@ public class WindowManagerService extends IWindowManager.Stub
if (dc == null) {
ProtoLog.w(WM_ERROR, "attachWindowContextToDisplayArea: trying to attach"
+ " to a non-existing display:%d", displayId);
- return false;
+ return null;
}
// TODO(b/155340867): Investigate if we still need roundedCornerOverlay after
// the feature b/155340867 is completed.
final DisplayArea da = dc.findAreaForWindowType(type, options,
callerCanManageAppTokens, false /* roundedCornerOverlay */);
+ // TODO(b/190019118): Avoid to send onConfigurationChanged because it has been done
+ // in return value of attachWindowContextToDisplayArea.
mWindowContextListenerController.registerWindowContainerListener(clientToken, da,
callingUid, type, options);
- return true;
+ return da.getConfiguration();
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7119,6 +7121,7 @@ public class WindowManagerService extends IWindowManager.Stub
"requestScrollCapture: caught exception dispatching to window."
+ "token=%s", targetWindow.mClient.asBinder());
responseBuilder.setWindowTitle(targetWindow.getName());
+ responseBuilder.setPackageName(targetWindow.getOwningPackage());
responseBuilder.setDescription(String.format("caught exception: %s", e));
listener.onScrollCaptureResponse(responseBuilder.build());
}
@@ -8646,8 +8649,9 @@ public class WindowManagerService extends IWindowManager.Stub
if (imeTargetWindowTask == null) {
return false;
}
- final TaskSnapshot snapshot = mAtmService.getTaskSnapshot(imeTargetWindowTask.mTaskId,
- false /* isLowResolution */);
+ final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId,
+ imeTargetWindowTask.mUserId, false /* isLowResolution */,
+ false /* restoreFromDisk */);
return snapshot != null && snapshot.hasImeSurface();
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5e042efa2f11..0af6a29fad10 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -26,7 +26,6 @@ import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.graphics.GraphicsProtos.dumpPointProto;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
-import static android.hardware.input.InputManager.BLOCK_UNTRUSTED_TOUCHES;
import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.PowerManager.DRAW_WAKE_LOCK;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
@@ -194,7 +193,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.admin.DevicePolicyCache;
-import android.app.compat.CompatChanges;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Matrix;
@@ -1156,9 +1154,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
int getTouchOcclusionMode() {
- if (!CompatChanges.isChangeEnabled(BLOCK_UNTRUSTED_TOUCHES, mOwnerUid)) {
- return TouchOcclusionMode.ALLOW;
- }
if (WindowManager.LayoutParams.isSystemAlertWindowType(mAttrs.type)) {
return TouchOcclusionMode.USE_OPACITY;
}
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index e319e3febc21..4190a91710fc 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -55,6 +55,7 @@ using android::base::unique_fd;
#define SYNC_RECEIVED_WHILE_FROZEN (1)
#define ASYNC_RECEIVED_WHILE_FROZEN (2)
+#define TXNS_PENDING_WHILE_FROZEN (4)
namespace android {
@@ -232,17 +233,20 @@ static void com_android_server_am_CachedAppOptimizer_compactProcess(JNIEnv*, job
compactProcessOrFallback(pid, compactionFlags);
}
-static void com_android_server_am_CachedAppOptimizer_freezeBinder(
+static jint com_android_server_am_CachedAppOptimizer_freezeBinder(
JNIEnv *env, jobject clazz, jint pid, jboolean freeze) {
- if (IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */) != 0) {
+ jint retVal = IPCThreadState::freeze(pid, freeze, 100 /* timeout [ms] */);
+ if (retVal != 0 && retVal != -EAGAIN) {
jniThrowException(env, "java/lang/RuntimeException", "Unable to freeze/unfreeze binder");
}
+
+ return retVal;
}
static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv *env,
jobject clazz, jint pid) {
- bool syncReceived = false, asyncReceived = false;
+ uint32_t syncReceived = 0, asyncReceived = 0;
int error = IPCThreadState::getProcessFreezeInfo(pid, &syncReceived, &asyncReceived);
@@ -252,13 +256,12 @@ static jint com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo(JNIEnv
jint retVal = 0;
- if(syncReceived) {
- retVal |= SYNC_RECEIVED_WHILE_FROZEN;;
- }
-
- if(asyncReceived) {
- retVal |= ASYNC_RECEIVED_WHILE_FROZEN;
- }
+ // bit 0 of sync_recv goes to bit 0 of retVal
+ retVal |= syncReceived & SYNC_RECEIVED_WHILE_FROZEN;
+ // bit 0 of async_recv goes to bit 1 of retVal
+ retVal |= (asyncReceived << 1) & ASYNC_RECEIVED_WHILE_FROZEN;
+ // bit 1 of sync_recv goes to bit 2 of retVal
+ retVal |= (syncReceived << 1) & TXNS_PENDING_WHILE_FROZEN;
return retVal;
}
@@ -278,7 +281,7 @@ static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
{"compactProcess", "(II)V", (void*)com_android_server_am_CachedAppOptimizer_compactProcess},
- {"freezeBinder", "(IZ)V", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
+ {"freezeBinder", "(IZ)I", (void*)com_android_server_am_CachedAppOptimizer_freezeBinder},
{"getBinderFreezeInfo", "(I)I",
(void*)com_android_server_am_CachedAppOptimizer_getBinderFreezeInfo},
{"getFreezerCheckPath", "()Ljava/lang/String;",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index a94ad4ad7ef2..bb9740b60f78 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -337,7 +337,7 @@ public:
void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) override;
bool checkInjectEventsPermissionNonReentrant(int32_t injectorPid, int32_t injectorUid) override;
void onPointerDownOutsideFocus(const sp<IBinder>& touchedToken) override;
- void setPointerCapture(bool enabled) override;
+ void setPointerCapture(const PointerCaptureRequest& request) override;
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
/* --- PointerControllerPolicyInterface implementation --- */
@@ -372,8 +372,8 @@ private:
// Show touches feature enable/disable.
bool showTouches;
- // Pointer capture feature enable/disable.
- bool pointerCapture;
+ // The latest request to enable or disable Pointer Capture.
+ PointerCaptureRequest pointerCaptureRequest;
// Sprite controller singleton, created on first use.
sp<SpriteController> spriteController;
@@ -417,7 +417,6 @@ NativeInputManager::NativeInputManager(jobject contextObj,
mLocked.pointerSpeed = 0;
mLocked.pointerGesturesEnabled = true;
mLocked.showTouches = false;
- mLocked.pointerCapture = false;
mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
}
mInteractive = true;
@@ -446,7 +445,9 @@ void NativeInputManager::dump(std::string& dump) {
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
dump += StringPrintf(INDENT "Show Touches: %s\n", toString(mLocked.showTouches));
- dump += StringPrintf(INDENT "Pointer Capture Enabled: %s\n", toString(mLocked.pointerCapture));
+ dump += StringPrintf(INDENT "Pointer Capture: %s, seq=%" PRIu32 "\n",
+ mLocked.pointerCaptureRequest.enable ? "Enabled" : "Disabled",
+ mLocked.pointerCaptureRequest.seq);
}
dump += "\n";
@@ -634,7 +635,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
outConfig->showTouches = mLocked.showTouches;
- outConfig->pointerCapture = mLocked.pointerCapture;
+ outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
outConfig->setDisplayViewports(mLocked.viewports);
@@ -1383,16 +1384,16 @@ void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedTok
checkAndClearExceptionFromCallback(env, "onPointerDownOutsideFocus");
}
-void NativeInputManager::setPointerCapture(bool enabled) {
+void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) {
{ // acquire lock
AutoMutex _l(mLock);
- if (mLocked.pointerCapture == enabled) {
+ if (mLocked.pointerCaptureRequest == request) {
return;
}
- ALOGV("%s pointer capture.", enabled ? "Enabling" : "Disabling");
- mLocked.pointerCapture = enabled;
+ ALOGV("%s pointer capture.", request.enable ? "Enabling" : "Disabling");
+ mLocked.pointerCaptureRequest = request;
} // release lock
mInputManager->getReader()->requestRefreshConfiguration(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ac344d648f8b..9ceabce398ed 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3599,6 +3599,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(
+ isCallingFromPackage(adminReceiver.getPackageName(), caller.getUid())
+ || isSystemUid(caller));
synchronized (getLockObject()) {
ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver, userHandle);
@@ -12695,74 +12698,21 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
// This method is called from AM with its lock held, so don't take the DPMS lock.
// b/29242568
- ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
- if (profileOwner != null) {
- return DevicePolicyManagerService.this
- .createShowAdminSupportIntent(profileOwner, userId);
- }
-
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null && deviceOwner.first == userId) {
- return DevicePolicyManagerService.this
- .createShowAdminSupportIntent(deviceOwner.second, userId);
- }
-
- // We're not specifying the device admin because there isn't one.
- if (useDefaultIfNoAdmin) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(null, userId);
+ if (getEnforcingAdminAndUserDetailsInternal(userId, null) != null
+ || useDefaultIfNoAdmin) {
+ return DevicePolicyManagerService.this.createShowAdminSupportIntent(userId);
}
return null;
}
@Override
public Intent createUserRestrictionSupportIntent(int userId, String userRestriction) {
- final long ident = mInjector.binderClearCallingIdentity();
- try {
- final List<UserManager.EnforcingUser> sources = mUserManager
- .getUserRestrictionSources(userRestriction, UserHandle.of(userId));
- if (sources == null || sources.isEmpty()) {
- // The restriction is not enforced.
- return null;
- } else if (sources.size() > 1) {
- // In this case, we'll show an admin support dialog that does not
- // specify the admin.
- // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
- // the admin for the calling user.
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- null, userId);
- }
- 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) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- profileOwner, enforcingUserId);
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) {
- // Restriction was enforced by DO
- final Pair<Integer, ComponentName> deviceOwner =
- mOwners.getDeviceOwnerUserIdAndComponent();
- if (deviceOwner != null) {
- return DevicePolicyManagerService.this.createShowAdminSupportIntent(
- deviceOwner.second, deviceOwner.first);
- }
- } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
- /*
- * In this case, the user restriction is enforced by the system.
- * So we won't show an admin support intent, even if it is also
- * enforced by a profile/device owner.
- */
- return null;
- }
- } finally {
- mInjector.binderRestoreCallingIdentity(ident);
+ Intent intent = null;
+ if (getEnforcingAdminAndUserDetailsInternal(userId, userRestriction) != null) {
+ intent = DevicePolicyManagerService.this.createShowAdminSupportIntent(userId);
+ intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, userRestriction);
}
- return null;
+ return intent;
}
@Override
@@ -13057,53 +13007,153 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
}
}
- private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
+ private Intent createShowAdminSupportIntent(int userId) {
// This method is called with AMS lock held, so don't take DPMS lock
final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
intent.putExtra(Intent.EXTRA_USER_ID, userId);
- intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
return intent;
}
- @Override
- public Intent createAdminSupportIntent(String restriction) {
- Objects.requireNonNull(restriction);
- final CallerIdentity caller = getCallerIdentity();
- Intent intent = null;
- if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction) ||
- DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ */
+ private Bundle getEnforcingAdminAndUserDetailsInternal(int userId, String restriction) {
+ Bundle result = null;
+ if (restriction == null) {
+ ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId);
+ if (profileOwner != null) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ profileOwner);
+ return result;
+ }
+ final Pair<Integer, ComponentName> deviceOwner =
+ mOwners.getDeviceOwnerUserIdAndComponent();
+ if (deviceOwner != null && deviceOwner.first == userId) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ deviceOwner.second);
+ return result;
+ }
+ } else if (DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)
+ || DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction)) {
synchronized (getLockObject()) {
- final DevicePolicyData policy = getUserData(caller.getUserId());
+ final DevicePolicyData policy = getUserData(userId);
final int N = policy.mAdminList.size();
for (int i = 0; i < N; i++) {
final ActiveAdmin admin = policy.mAdminList.get(i);
if ((admin.disableCamera &&
- DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) ||
- (admin.disableScreenCapture && DevicePolicyManager
- .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
- intent = createShowAdminSupportIntent(admin.info.getComponent(),
- caller.getUserId());
- break;
+ DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction))
+ || (admin.disableScreenCapture && DevicePolicyManager
+ .POLICY_DISABLE_SCREEN_CAPTURE.equals(restriction))) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ admin.info.getComponent());
+ return result;
}
}
// For the camera, a device owner on a different user can disable it globally,
// so we need an additional check.
- if (intent == null
+ if (result == null
&& DevicePolicyManager.POLICY_DISABLE_CAMERA.equals(restriction)) {
final ActiveAdmin admin = getDeviceOwnerAdminLocked();
if (admin != null && admin.disableCamera) {
- intent = createShowAdminSupportIntent(admin.info.getComponent(),
- mOwners.getDeviceOwnerUserId());
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, mOwners.getDeviceOwnerUserId());
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ admin.info.getComponent());
+ return result;
}
}
}
} else {
- // if valid, |restriction| can only be a user restriction
- intent = mLocalService.createUserRestrictionSupportIntent(caller.getUserId(),
- restriction);
+ long ident = mInjector.binderClearCallingIdentity();
+ try {
+ List<UserManager.EnforcingUser> sources = mUserManager
+ .getUserRestrictionSources(restriction, UserHandle.of(userId));
+ if (sources == null || sources.isEmpty()) {
+ // The restriction is not enforced.
+ return null;
+ } else if (sources.size() > 1) {
+ // In this case, we'll show an admin support dialog that does not
+ // specify the admin.
+ // TODO(b/128928355): if this restriction is enforced by multiple DPCs, return
+ // the admin for the calling user.
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, userId);
+ return result;
+ }
+ 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) {
+ result = new Bundle();
+ result.putInt(Intent.EXTRA_USER_ID, deviceOwner.first);
+ result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN,
+ deviceOwner.second);
+ return result;
+ }
+ } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) {
+ /*
+ * In this case, the user restriction is enforced by the system.
+ * So we won't show an admin support intent, even if it is also
+ * enforced by a profile/device owner.
+ */
+ return null;
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(ident);
+ }
}
- if (intent != null) {
+ return null;
+ }
+
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ * @return Details of admin and user which enforced the restriction for the userId.
+ */
+ @Override
+ public Bundle getEnforcingAdminAndUserDetails(int userId, String restriction) {
+ Preconditions.checkCallAuthorization(isSystemUid(getCallerIdentity()));
+ return getEnforcingAdminAndUserDetailsInternal(userId, restriction);
+ }
+
+ /**
+ * @param restriction The restriction enforced by admin. It could be any user restriction or
+ * policy like {@link DevicePolicyManager#POLICY_DISABLE_CAMERA} and
+ * {@link DevicePolicyManager#POLICY_DISABLE_SCREEN_CAPTURE}.
+ */
+ @Override
+ public Intent createAdminSupportIntent(String restriction) {
+ Objects.requireNonNull(restriction);
+ final CallerIdentity caller = getCallerIdentity();
+ final int userId = caller.getUserId();
+ Intent intent = null;
+ if (getEnforcingAdminAndUserDetailsInternal(userId, restriction) != null) {
+ intent = createShowAdminSupportIntent(userId);
intent.putExtra(DevicePolicyManager.EXTRA_RESTRICTION, restriction);
}
return intent;
@@ -14061,6 +14111,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final CallerIdentity caller = getCallerIdentity();
Preconditions.checkCallAuthorization(hasFullCrossUsersPermission(caller, userHandle));
+ Preconditions.checkCallAuthorization(canManageUsers(caller));
Preconditions.checkCallAuthorization(isManagedProfile(userHandle),
"You can not get organization name outside a managed profile, userId = %d",
userHandle);
diff --git a/services/incremental/OWNERS b/services/incremental/OWNERS
index 7ebb962c8feb..fe8c253aa793 100644
--- a/services/incremental/OWNERS
+++ b/services/incremental/OWNERS
@@ -3,6 +3,5 @@ include /services/core/java/com/android/server/pm/OWNERS
alexbuy@google.com
schfan@google.com
-toddke@google.com
zyy@google.com
patb@google.com
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index aca7cc9070b1..e012dd2b3d0e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -436,7 +436,6 @@ public final class SystemServer implements Dumpable {
private static final String SYSPROP_START_UPTIME = "sys.system_server.start_uptime";
private Future<?> mZygotePreload;
- private Future<?> mBlobStoreServiceStart;
private final SystemServerDumper mDumper = new SystemServerDumper();
@@ -2251,12 +2250,9 @@ public final class SystemServer implements Dumpable {
t.traceEnd();
}
- mBlobStoreServiceStart = SystemServerInitThreadPool.submit(() -> {
- final TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
- traceLog.traceBegin(START_BLOB_STORE_SERVICE);
- mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
- traceLog.traceEnd();
- }, START_BLOB_STORE_SERVICE);
+ t.traceBegin(START_BLOB_STORE_SERVICE);
+ mSystemServiceManager.startService(BLOB_STORE_MANAGER_SERVICE_CLASS);
+ t.traceEnd();
// Dreams (interactive idle-time views, a/k/a screen savers, and doze mode)
t.traceBegin("StartDreamManager");
@@ -2655,9 +2651,6 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(MEDIA_COMMUNICATION_SERVICE_CLASS);
t.traceEnd();
- ConcurrentUtils.waitForFutureNoInterrupt(mBlobStoreServiceStart,
- START_BLOB_STORE_SERVICE);
-
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
diff --git a/services/tests/PackageManager/packageinstaller/Android.bp b/services/tests/PackageManager/packageinstaller/Android.bp
new file mode 100644
index 000000000000..35d754b4adc5
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/Android.bp
@@ -0,0 +1,39 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "PackageInstallerTests",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.runner",
+ "junit",
+ "kotlin-test",
+ "truth-prebuilt",
+ ],
+ platform_apis: true,
+ test_suites: ["device-tests"],
+}
diff --git a/services/tests/PackageManager/packageinstaller/AndroidManifest.xml b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
new file mode 100644
index 000000000000..d7062587c4bc
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.packageinstaller.test">
+
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.packageinstaller.test"
+ />
+
+</manifest>
+
diff --git a/services/tests/PackageManager/packageinstaller/AndroidTest.xml b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
new file mode 100644
index 000000000000..c39285ffca38
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Test module config for PackageInstallerTests">
+ <option name="test-tag" value="PackageInstallerTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="PackageInstallerTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.packageinstaller.test" />
+ </test>
+</configuration>
diff --git a/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
new file mode 100644
index 000000000000..d7d2726c1583
--- /dev/null
+++ b/services/tests/PackageManager/packageinstaller/src/com/android/packageinstaller/test/ExportedComponentTest.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.packageinstaller.test
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.net.Uri
+import androidx.test.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+
+class ExportedComponentTest {
+
+ private val context: Context = InstrumentationRegistry.getContext()
+
+ @Test
+ fun verifyNoExportedReceivers() {
+ val intent = Intent(Intent.ACTION_INSTALL_PACKAGE).apply {
+ data = Uri.parse("content://mockForTest")
+ }
+ val packageInstallers = context.packageManager.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY or PackageManager.MATCH_DISABLED_COMPONENTS)
+ .map { it.activityInfo.packageName }
+ .distinct()
+ .map { context.packageManager.getPackageInfo(it, PackageManager.GET_RECEIVERS) }
+
+ assertThat(packageInstallers).isNotEmpty()
+
+ packageInstallers.forEach {
+ val exported = it.receivers.filter { it.exported }
+ assertWithMessage("Receivers should not be exported").that(exported).isEmpty()
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/AndroidManifest.xml b/services/tests/mockingservicestests/AndroidManifest.xml
index 17a5dccb57da..3cab5ecd3de1 100644
--- a/services/tests/mockingservicestests/AndroidManifest.xml
+++ b/services/tests/mockingservicestests/AndroidManifest.xml
@@ -28,6 +28,7 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS"/>
<uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"/>
<uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
new file mode 100644
index 000000000000..a4de08a85487
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
new file mode 100644
index 000000000000..47649d7392e6
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="true" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
new file mode 100644
index 000000000000..4fd9ebf98717
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="true" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
new file mode 100644
index 000000000000..e8f9edfde2d7
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
+<sensor-privacy persistence-version="1" version="1">
+ <user id="0" enabled="false">
+ <individual-sensor-privacy sensor="1" enabled="false" />
+ <individual-sensor-privacy sensor="2" enabled="false" />
+ </user>
+</sensor-privacy>
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index acf50b4569c6..e472b062388e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -323,6 +323,7 @@ public class DeviceIdleControllerTest {
when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
doNothing().when(mWakeLock).acquire();
doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any());
+ doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
doNothing().when(mAlarmManager)
.setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any());
doReturn(mock(Sensor.class)).when(mSensorManager)
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index fd364ae77240..51fa8517e45f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -37,6 +37,8 @@ import static org.mockito.Mockito.times;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.os.Bundle;
import android.os.RecoverySystem;
@@ -83,6 +85,8 @@ public class RescuePartyTest {
private static final String CALLING_PACKAGE1 = "com.package.name1";
private static final String CALLING_PACKAGE2 = "com.package.name2";
private static final String CALLING_PACKAGE3 = "com.package.name3";
+ private static final String PERSISTENT_PACKAGE = "com.persistent.package";
+ private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
private static final String NAMESPACE1 = "namespace1";
private static final String NAMESPACE2 = "namespace2";
private static final String NAMESPACE3 = "namespace3";
@@ -103,6 +107,8 @@ public class RescuePartyTest {
private PackageWatchdog mMockPackageWatchdog;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private ContentResolver mMockContentResolver;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private PackageManager mPackageManager;
@Captor
private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
@@ -129,6 +135,17 @@ public class RescuePartyTest {
mNamespacesWiped = new HashSet<>();
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
+ ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
+ persistentApplicationInfo.flags |=
+ ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
+
+ // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
+ // system. Don't set any flags otherwise.
+ when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(persistentApplicationInfo);
+ when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
+ anyInt())).thenReturn(new ApplicationInfo());
// Reset observer instance to get new mock context on every run
RescuePartyObserver.reset();
@@ -241,29 +258,53 @@ public class RescuePartyTest {
@Test
public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
- notePersistentAppCrash(1);
+ noteAppCrash(1, true);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
- notePersistentAppCrash(2);
+ noteAppCrash(2, true);
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
- notePersistentAppCrash(3);
+ noteAppCrash(3, true);
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
/*configResetVerifiedTimesMap=*/ null);
- notePersistentAppCrash(4);
+ noteAppCrash(4, true);
assertTrue(RescueParty.isRebootPropertySet());
- notePersistentAppCrash(5);
+ noteAppCrash(5, true);
assertTrue(RescueParty.isFactoryResetPropertySet());
}
@Test
+ public void testNonPersistentAppOnlyPerformsFlagResets() {
+ noteAppCrash(1, false);
+
+ verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
+ /*configResetVerifiedTimesMap=*/ null);
+
+ noteAppCrash(2, false);
+
+ verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
+ /*configResetVerifiedTimesMap=*/ null);
+
+ noteAppCrash(3, false);
+
+ verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
+ /*configResetVerifiedTimesMap=*/ null);
+
+ noteAppCrash(4, false);
+ assertFalse(RescueParty.isRebootPropertySet());
+
+ noteAppCrash(5, false);
+ assertFalse(RescueParty.isFactoryResetPropertySet());
+ }
+
+ @Test
public void testNonPersistentAppCrashDetectionWithScopedResets() {
RescueParty.onSettingsProviderPublished(mMockContext);
verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
@@ -311,11 +352,11 @@ public class RescuePartyTest {
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
- assertTrue(RescueParty.isRebootPropertySet());
+ assertFalse(RescueParty.isRebootPropertySet());
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
- assertTrue(RescueParty.isFactoryResetPropertySet());
+ assertFalse(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -376,11 +417,11 @@ public class RescuePartyTest {
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
- assertTrue(RescueParty.isRebootPropertySet());
+ assertFalse(RescueParty.isRebootPropertySet());
observer.execute(new VersionedPackage(
CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
- assertTrue(RescueParty.isFactoryResetPropertySet());
+ assertFalse(RescueParty.isFactoryResetPropertySet());
}
@Test
@@ -627,9 +668,10 @@ public class RescuePartyTest {
RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
}
- private void notePersistentAppCrash(int mitigationCount) {
+ private void noteAppCrash(int mitigationCount, boolean isPersistent) {
+ String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
- "com.package.name", 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
+ packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
}
private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
index 022fadcc6dd0..609768c0e62a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CacheOomRankerTest.java
@@ -21,12 +21,16 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
+import android.app.ActivityManager;
+import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
+import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
@@ -48,8 +52,14 @@ import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import java.io.File;
-import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -60,8 +70,11 @@ import java.util.concurrent.TimeUnit;
* Build/Install/Run:
* atest FrameworksMockingServicesTests:CacheOomRankerTest
*/
+@SuppressWarnings("GuardedBy") // No tests are concurrent, so no need to test locking.
@RunWith(MockitoJUnitRunner.class)
public class CacheOomRankerTest {
+ private static final Instant NOW = LocalDate.of(2021, 1, 1).atStartOfDay(
+ ZoneOffset.UTC).toInstant();
@Mock
private AppOpsService mAppOpsService;
@@ -82,6 +95,7 @@ public class CacheOomRankerTest {
private int mNextUid = 30000;
private int mNextPackageUid = 40000;
private int mNextPackageName = 1;
+ private Map<Integer, Long> mPidToRss;
private TestExecutor mExecutor = new TestExecutor();
private CacheOomRanker mCacheOomRanker;
@@ -107,7 +121,15 @@ public class CacheOomRankerTest {
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
- mCacheOomRanker = new CacheOomRanker(mAms);
+ mPidToRss = new HashMap<>();
+ mCacheOomRanker = new CacheOomRanker(
+ mAms,
+ pid -> {
+ Long rss = mPidToRss.get(pid);
+ assertThat(rss).isNotNull();
+ return new long[]{rss};
+ }
+ );
mCacheOomRanker.init(mExecutor);
}
@@ -136,6 +158,15 @@ public class CacheOomRankerTest {
mExecutor.init();
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
+ Integer.toString(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1),
+ false);
+ mExecutor.waitForLatch();
+ assertThat(mCacheOomRanker.mPreserveTopNApps)
+ .isEqualTo(CacheOomRanker.DEFAULT_PRESERVE_TOP_N_APPS + 1);
+
+ mExecutor.init();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
Float.toString(CacheOomRanker.DEFAULT_OOM_RE_RANKING_LRU_WEIGHT + 0.1f),
false);
@@ -165,6 +196,9 @@ public class CacheOomRankerTest {
@Test
public void reRankLruCachedApps_lruImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 5,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 0.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */1.0f);
@@ -172,36 +206,40 @@ public class CacheOomRankerTest {
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord lastUsed40MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(lastUsed40MinutesAgo);
ProcessRecord lastUsed42MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(lastUsed42MinutesAgo);
ProcessRecord lastUsed60MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
processList.add(lastUsed60MinutesAgo);
ProcessRecord lastUsed15MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(lastUsed15MinutesAgo);
ProcessRecord lastUsed17MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
processList.add(lastUsed17MinutesAgo);
// Only re-ranking 5 entries so this should stay in most recent position.
ProcessRecord lastUsed30MinutesAgo = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 20);
processList.add(lastUsed30MinutesAgo);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 5 ordered by least recently used first, then last processes position unchanged.
assertThat(processList).containsExactly(lastUsed60MinutesAgo, lastUsed42MinutesAgo,
lastUsed40MinutesAgo, lastUsed17MinutesAgo, lastUsed15MinutesAgo,
- lastUsed30MinutesAgo);
+ lastUsed30MinutesAgo).inOrder();
}
@Test
public void reRankLruCachedApps_rssImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 0.0f,
/* pssWeight= */ 1.0f,
/* lruWeight= */ 0.0f);
@@ -209,145 +247,459 @@ public class CacheOomRankerTest {
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(rss10k);
ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(rss20k);
ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(60).toMillis(), 1024L, 10000);
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
processList.add(rss1k);
ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(rss100k);
ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(rss2k);
ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
processList.add(rss15k);
// Only re-ranking 6 entries so this should stay in most recent position.
ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 20);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 6 ordered by largest pss, then last processes position unchanged.
assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
- rss16k);
+ rss16k).inOrder();
}
@Test
+ public void reRankLruCachedApps_rssImpactsOrdering_cachedRssValues()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 10000000,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 1024L, 10000);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
+ processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+
+ // Clear mPidToRss so that Process.getRss calls fail.
+ mPidToRss.clear();
+ // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
+ Collections.swap(processList, 0, 1);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // Re ranking is the same.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_rssImpactsOrdering_profileRss()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ false,
+ /* rssUpdateRateMs= */ 10000000,
+ /* usesWeight= */ 0.0f,
+ /* pssWeight= */ 1.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord rss10k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 0L, 1000);
+ rss10k.mProfile.setLastRss(10 * 1024L);
+ processList.add(rss10k);
+ ProcessRecord rss20k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 0L, 2000);
+ rss20k.mProfile.setLastRss(20 * 1024L);
+ processList.add(rss20k);
+ ProcessRecord rss1k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(60, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10000);
+ rss1k.mProfile.setLastRss(1024L);
+ processList.add(rss1k);
+ ProcessRecord rss100k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 0L, 10);
+ rss100k.mProfile.setLastRss(100 * 1024L);
+ processList.add(rss100k);
+ ProcessRecord rss2k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 0L, 20);
+ rss2k.mProfile.setLastRss(2 * 1024L);
+ processList.add(rss2k);
+ ProcessRecord rss15k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 20);
+ rss15k.mProfile.setLastRss(15 * 1024L);
+ processList.add(rss15k);
+ // Only re-ranking 6 entries so this should stay in most recent position.
+ ProcessRecord rss16k = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 20);
+ rss16k.mProfile.setLastRss(16 * 1024L);
+ processList.add(rss16k);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ // This should not be used, as RSS values are taken from mProfile.
+ mPidToRss.clear();
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // First 6 ordered by largest pss, then last processes position unchanged.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+
+ // Clear mPidToRss so that Process.getRss calls fail.
+ mPidToRss.clear();
+ // Mix up the process list to ensure that CacheOomRanker actually re-ranks.
+ Collections.swap(processList, 0, 1);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+ // Re ranking is the same.
+ assertThat(processList).containsExactly(rss100k, rss20k, rss15k, rss10k, rss2k, rss1k,
+ rss16k).inOrder();
+ }
+
+
+ @Test
public void reRankLruCachedApps_usesImpactsOrdering() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 1.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.setLruProcessServiceStartLSP(1);
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(used1000);
ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(used2000);
ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(used10);
ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(used20);
// Only re-ranking 6 entries so last two should stay in most recent position.
ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
processList.add(used500);
ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
// First 4 ordered by uses, then last processes position unchanged.
assertThat(processList).containsExactly(used10, used20, used1000, used2000, used500,
- used200);
+ used200).inOrder();
}
@Test
- public void reRankLruCachedApps_notEnoughProcesses() throws InterruptedException {
+ public void reRankLruCachedApps_fewProcesses() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
- /* usesWeight= */ 0.5f,
- /* pssWeight= */ 0.2f,
- /* lruWeight= */ 0.3f);
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
- ProcessRecord unknownAdj1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
- processList.add(unknownAdj1);
- ProcessRecord unknownAdj2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
- processList.add(unknownAdj2);
- ProcessRecord unknownAdj3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
- processList.add(unknownAdj3);
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
ProcessRecord foregroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(foregroundAdj);
ProcessRecord serviceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
processList.add(serviceAdj);
ProcessRecord systemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
processList.add(systemAdj);
+ list.setLruProcessServiceStartLSP(processList.size());
- // 6 Processes but only 3 in eligible for cache so no re-ranking.
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
- // All positions unchanged.
- assertThat(processList).containsExactly(unknownAdj1, unknownAdj2, unknownAdj3,
- foregroundAdj, serviceAdj, systemAdj);
+ // 6 processes, only 3 in eligible for cache, so only those are re-ranked.
+ assertThat(processList).containsExactly(used10, used1000, used2000,
+ foregroundAdj, serviceAdj, systemAdj).inOrder();
}
@Test
- public void reRankLruCachedApps_notEnoughNonServiceProcesses() throws InterruptedException {
+ public void reRankLruCachedApps_fewNonServiceProcesses() throws InterruptedException {
setConfig(/* numberToReRank= */ 4,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
/* usesWeight= */ 1.0f,
/* pssWeight= */ 0.0f,
/* lruWeight= */ 0.0f);
ProcessList list = new ProcessList();
- list.setLruProcessServiceStartLSP(4);
ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(40).toMillis(), 10 * 1024L, 1000);
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
processList.add(used1000);
ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(42).toMillis(), 20 * 1024L, 2000);
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
processList.add(used2000);
ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(15).toMillis(), 100 * 1024L, 10);
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ ProcessRecord service1 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(service1);
+ ProcessRecord service2 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(service2);
+ ProcessRecord service3 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(service3);
+ list.setLruProcessServiceStartLSP(3);
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // Services unchanged, rest re-ranked.
+ assertThat(processList).containsExactly(used10, used1000, used2000, service1, service2,
+ service3).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_manyProcessesThenFew() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 0,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList set1List = new ProcessList();
+ ArrayList<ProcessRecord> set1ProcessList = set1List.getLruProcessesLSP();
+ ProcessRecord set1Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ set1ProcessList.add(set1Used1000);
+ ProcessRecord set1Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ set1ProcessList.add(set1Used2000);
+ ProcessRecord set1Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ set1ProcessList.add(set1Used10);
+ ProcessRecord set1Uses20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ set1ProcessList.add(set1Uses20);
+ ProcessRecord set1Uses500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ set1ProcessList.add(set1Uses500);
+ ProcessRecord set1Uses200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ set1ProcessList.add(set1Uses200);
+ set1List.setLruProcessServiceStartLSP(set1ProcessList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(set1ProcessList,
+ set1List.getLruProcessServiceStartLOSP());
+ assertThat(set1ProcessList).containsExactly(set1Used10, set1Uses20, set1Uses200,
+ set1Uses500, set1Used1000, set1Used2000).inOrder();
+
+ ProcessList set2List = new ProcessList();
+ ArrayList<ProcessRecord> set2ProcessList = set2List.getLruProcessesLSP();
+ ProcessRecord set2Used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ set2ProcessList.add(set2Used1000);
+ ProcessRecord set2Used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ set2ProcessList.add(set2Used2000);
+ ProcessRecord set2Used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ set2ProcessList.add(set2Used10);
+ ProcessRecord set2ForegroundAdj = nextProcessRecord(ProcessList.FOREGROUND_APP_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ set2ProcessList.add(set2ForegroundAdj);
+ ProcessRecord set2ServiceAdj = nextProcessRecord(ProcessList.SERVICE_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ set2ProcessList.add(set2ServiceAdj);
+ ProcessRecord set2SystemAdj = nextProcessRecord(ProcessList.SYSTEM_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ set2ProcessList.add(set2SystemAdj);
+ set2List.setLruProcessServiceStartLSP(set2ProcessList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(set2ProcessList,
+ set2List.getLruProcessServiceStartLOSP());
+ assertThat(set2ProcessList).containsExactly(set2Used10, set2Used1000, set2Used2000,
+ set2ForegroundAdj, set2ServiceAdj, set2SystemAdj).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps() throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 3,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ // Preserving the top 3 processes, so these should not be re-ranked.
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // First 3 ordered by uses, then last processes position unchanged.
+ assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
+ used200).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps_allAppsUnchanged()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ 100,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
processList.add(used10);
ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(17).toMillis(), 2 * 1024L, 20);
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
processList.add(used20);
ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 15 * 1024L, 500);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
processList.add(used500);
ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
- Duration.ofMinutes(30).toMillis(), 16 * 1024L, 200);
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
- // All positions unchanged.
+ // Nothing reordered, as we preserve the top 100 apps.
assertThat(processList).containsExactly(used1000, used2000, used10, used20, used500,
- used200);
+ used200).inOrder();
+ }
+
+ @Test
+ public void reRankLruCachedApps_preservesTopNApps_negativeReplacedWithDefault()
+ throws InterruptedException {
+ setConfig(/* numberToReRank= */ 6,
+ /* preserveTopNApps= */ -100,
+ /* useFrequentRss= */ true,
+ /* rssUpdateRateMs= */ 0,
+ /* usesWeight= */ 1.0f,
+ /* pssWeight= */ 0.0f,
+ /* lruWeight= */ 0.0f);
+
+ ProcessList list = new ProcessList();
+ ArrayList<ProcessRecord> processList = list.getLruProcessesLSP();
+ ProcessRecord used1000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(40, ChronoUnit.MINUTES).toEpochMilli(), 10 * 1024L, 1000);
+ processList.add(used1000);
+ ProcessRecord used2000 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(42, ChronoUnit.MINUTES).toEpochMilli(), 20 * 1024L, 2000);
+ processList.add(used2000);
+ ProcessRecord used10 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(15, ChronoUnit.MINUTES).toEpochMilli(), 100 * 1024L, 10);
+ processList.add(used10);
+ // Negative preserveTopNApps interpreted as the default (3), so the last three are unranked.
+ ProcessRecord used20 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(17, ChronoUnit.MINUTES).toEpochMilli(), 2 * 1024L, 20);
+ processList.add(used20);
+ ProcessRecord used500 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 15 * 1024L, 500);
+ processList.add(used500);
+ ProcessRecord used200 = nextProcessRecord(ProcessList.UNKNOWN_ADJ,
+ NOW.minus(30, ChronoUnit.MINUTES).toEpochMilli(), 16 * 1024L, 200);
+ processList.add(used200);
+ list.setLruProcessServiceStartLSP(processList.size());
+
+ mCacheOomRanker.reRankLruCachedAppsLSP(processList, list.getLruProcessServiceStartLOSP());
+
+ // First 3 apps re-ranked, as preserveTopNApps is interpreted as 3.
+ assertThat(processList).containsExactly(used10, used1000, used2000, used20, used500,
+ used200).inOrder();
}
- private void setConfig(int numberToReRank, float useWeight, float pssWeight, float lruWeight)
+ private void setConfig(int numberToReRank, int preserveTopNApps, boolean useFrequentRss,
+ long rssUpdateRateMs, float usesWeight, float pssWeight, float lruWeight)
throws InterruptedException {
mExecutor.init(4);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -355,6 +707,18 @@ public class CacheOomRankerTest {
Integer.toString(numberToReRank),
false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_PRESERVE_TOP_N_APPS,
+ Integer.toString(preserveTopNApps),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_USE_FREQUENT_RSS,
+ Boolean.toString(useFrequentRss),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CacheOomRanker.KEY_OOM_RE_RANKING_RSS_UPDATE_RATE_MS,
+ Long.toString(rssUpdateRateMs),
+ false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_LRU_WEIGHT,
Float.toString(lruWeight),
false);
@@ -364,17 +728,19 @@ public class CacheOomRankerTest {
false);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
CacheOomRanker.KEY_OOM_RE_RANKING_USES_WEIGHT,
- Float.toString(useWeight),
+ Float.toString(usesWeight),
false);
mExecutor.waitForLatch();
assertThat(mCacheOomRanker.getNumberToReRank()).isEqualTo(numberToReRank);
+ assertThat(mCacheOomRanker.mUseFrequentRss).isEqualTo(useFrequentRss);
+ assertThat(mCacheOomRanker.mRssUpdateRateMs).isEqualTo(rssUpdateRateMs);
assertThat(mCacheOomRanker.mRssWeight).isEqualTo(pssWeight);
- assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(useWeight);
+ assertThat(mCacheOomRanker.mUsesWeight).isEqualTo(usesWeight);
assertThat(mCacheOomRanker.mLruWeight).isEqualTo(lruWeight);
}
private ProcessRecord nextProcessRecord(int setAdj, long lastActivityTime, long lastRss,
- int returnedToCacheCount) {
+ int wentToForegroundCount) {
ApplicationInfo ai = new ApplicationInfo();
ai.packageName = "a.package.name" + mNextPackageName++;
ProcessRecord app = new ProcessRecord(mAms, ai, ai.packageName + ":process", mNextUid++);
@@ -382,14 +748,20 @@ public class CacheOomRankerTest {
app.info.uid = mNextPackageUid++;
// Exact value does not mater, it can be any state for which compaction is allowed.
app.mState.setSetProcState(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- app.mState.setSetAdj(setAdj);
+ app.mState.setCurAdj(setAdj);
app.setLastActivityTime(lastActivityTime);
- app.mProfile.setLastRss(lastRss);
+ mPidToRss.put(app.getPid(), lastRss);
app.mState.setCached(false);
- for (int i = 0; i < returnedToCacheCount; ++i) {
- app.mState.setCached(false);
- app.mState.setCached(true);
+ for (int i = 0; i < wentToForegroundCount; ++i) {
+ app.mState.setSetProcState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ app.mState.setSetProcState(ActivityManager.PROCESS_STATE_CACHED_RECENT);
}
+ // Sets the thread returned by ProcessRecord#getThread, which we use to check whether the
+ // app is currently launching.
+ ProcessStatsService processStatsService = new ProcessStatsService(
+ mock(ActivityManagerService.class), new File(Environment.getDataSystemCeDirectory(),
+ "procstats"));
+ app.makeActive(mock(IApplicationThread.class), processStatsService);
return app;
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 4d86c8716bf1..85ef8f73534c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -182,7 +182,14 @@ public class GameManagerServiceTests {
}
private void mockDeviceConfigPerformance() {
- String configString = "mode=2,downscaleFactor=0.5";
+ String configString = "mode=2,downscaleFactor=0.5,useAngle=false";
+ when(DeviceConfig.getProperty(anyString(), anyString()))
+ .thenReturn(configString);
+ }
+
+ // ANGLE will be disabled for most apps, so treat enabling ANGLE as a special case.
+ private void mockDeviceConfigPerformanceEnableAngle() {
+ String configString = "mode=2,downscaleFactor=0.5,useAngle=true";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
}
@@ -200,7 +207,7 @@ public class GameManagerServiceTests {
}
private void mockDeviceConfigInvalid() {
- String configString = "mode=2,downscaleFactor=0.55";
+ String configString = "";
when(DeviceConfig.getProperty(anyString(), anyString()))
.thenReturn(configString);
}
@@ -212,7 +219,8 @@ public class GameManagerServiceTests {
}
private void mockGameModeOptInAll() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -224,7 +232,8 @@ public class GameManagerServiceTests {
}
private void mockGameModeOptInPerformance() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_PERFORMANCE_MODE_ENABLE, true);
@@ -234,7 +243,8 @@ public class GameManagerServiceTests {
}
private void mockGameModeOptInBattery() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_BATTERY_MODE_ENABLE, true);
@@ -244,7 +254,8 @@ public class GameManagerServiceTests {
}
private void mockInterventionAllowDownscaleTrue() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, true);
@@ -254,7 +265,8 @@ public class GameManagerServiceTests {
}
private void mockInterventionAllowDownscaleFalse() throws Exception {
- final ApplicationInfo applicationInfo = new ApplicationInfo();
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
Bundle metaDataBundle = new Bundle();
metaDataBundle.putBoolean(
GameManagerService.GamePackageConfiguration.METADATA_WM_ALLOW_DOWNSCALE, false);
@@ -263,6 +275,27 @@ public class GameManagerServiceTests {
.thenReturn(applicationInfo);
}
+ private void mockInterventionAllowAngleTrue() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putBoolean(
+ GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, true);
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
+ private void mockInterventionAllowAngleFalse() throws Exception {
+ final ApplicationInfo applicationInfo = mMockPackageManager.getApplicationInfoAsUser(
+ mPackageName, PackageManager.GET_META_DATA, USER_ID_1);
+ Bundle metaDataBundle = new Bundle();
+ metaDataBundle.putBoolean(
+ GameManagerService.GamePackageConfiguration.METADATA_ANGLE_ALLOW_ANGLE, false);
+ applicationInfo.metaData = metaDataBundle;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ }
+
/**
* By default game mode is not supported.
*/
@@ -340,11 +373,12 @@ public class GameManagerServiceTests {
*/
@Test
public void testSetGameModePermissionDenied() {
+ mockModifyGameModeGranted();
+ mockDeviceConfigAll();
GameManagerService gameManagerService = new GameManagerService(mMockContext);
gameManagerService.onUserStarting(USER_ID_1);
// Update the game mode so we can read back something valid.
- mockModifyGameModeGranted();
gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_STANDARD, USER_ID_1);
assertEquals(GameManager.GAME_MODE_STANDARD,
gameManagerService.getGameMode(mPackageName, USER_ID_1));
@@ -427,6 +461,19 @@ public class GameManagerServiceTests {
assertEquals(config.getGameModeConfiguration(gameMode).getScaling(), scaling);
}
+ private void checkAngleEnabled(GameManagerService gameManagerService, int gameMode,
+ boolean angleEnabled) {
+ gameManagerService.updateConfigsForUser(USER_ID_1, mPackageName);
+
+ // Validate GamePackageConfiguration returns the correct value.
+ GameManagerService.GamePackageConfiguration config =
+ gameManagerService.getConfig(mPackageName);
+ assertEquals(config.getGameModeConfiguration(gameMode).getUseAngle(), angleEnabled);
+
+ // Validate GameManagerService.getAngleEnabled() returns the correct value.
+ assertEquals(gameManagerService.getAngleEnabled(mPackageName, USER_ID_1), angleEnabled);
+ }
+
/**
* Phenotype device config exists, but is only propagating the default value.
*/
@@ -592,6 +639,50 @@ public class GameManagerServiceTests {
}
/**
+ * PERFORMANCE game mode is configured through Phenotype. The app hasn't specified any metadata.
+ */
+ @Test
+ public void testInterventionAllowAngleDefault() throws Exception {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockDeviceConfigPerformance();
+ mockModifyGameModeGranted();
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+ }
+
+ /**
+ * PERFORMANCE game mode is configured through Phenotype. The app has opted-out of ANGLE.
+ */
+ @Test
+ public void testInterventionAllowAngleFalse() throws Exception {
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockDeviceConfigPerformanceEnableAngle();
+ mockInterventionAllowAngleFalse();
+ mockModifyGameModeGranted();
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, false);
+ }
+
+ /**
+ * PERFORMANCE game mode is configured through Phenotype. The app has redundantly specified
+ * the ANGLE metadata default value of "true".
+ */
+ @Test
+ public void testInterventionAllowAngleTrue() throws Exception {
+ mockDeviceConfigPerformanceEnableAngle();
+ mockInterventionAllowAngleTrue();
+
+ GameManagerService gameManagerService = new GameManagerService(mMockContext);
+ gameManagerService.onUserStarting(USER_ID_1);
+ mockModifyGameModeGranted();
+ gameManagerService.setGameMode(mPackageName, GameManager.GAME_MODE_PERFORMANCE, USER_ID_1);
+ assertEquals(GameManager.GAME_MODE_PERFORMANCE,
+ gameManagerService.getGameMode(mPackageName, USER_ID_1));
+
+ checkAngleEnabled(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, true);
+ }
+
+ /**
* PERFORMANCE game mode is configured through Phenotype, but the app has also opted into the
* same mode. No interventions for this game mode should be available in this case.
*/
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
index f703e2e59181..d0b2edadc714 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java
@@ -906,6 +906,21 @@ public class LocationProviderManagerTest {
}
@Test
+ public void testProviderRequest_DelayedRequest_Remove() {
+ mProvider.setProviderLocation(createLocation(NAME, mRandom));
+
+ ILocationListener listener1 = createMockLocationListener();
+ LocationRequest request1 = new LocationRequest.Builder(60000)
+ .setWorkSource(WORK_SOURCE)
+ .build();
+ mManager.registerLocationRequest(request1, IDENTITY, PERMISSION_FINE, listener1);
+ mManager.unregisterLocationRequest(listener1);
+
+ mInjector.getAlarmHelper().incrementAlarmTime(60000);
+ assertThat(mProvider.getRequest().isActive()).isFalse();
+ }
+
+ @Test
public void testProviderRequest_SpamRequesting() {
mProvider.setProviderLocation(createLocation(NAME, mRandom));
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
index 63996f0e021c..4d6f49e5d223 100644
--- a/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/StationaryThrottlingLocationProviderTest.java
@@ -90,7 +90,7 @@ public class StationaryThrottlingLocationProviderTest {
}
@Test
- public void testThrottle() {
+ public void testThrottle_stationaryExit() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
mProvider.getController().setRequest(request);
@@ -113,6 +113,29 @@ public class StationaryThrottlingLocationProviderTest {
}
@Test
+ public void testThrottle_idleExit() {
+ ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
+
+ mProvider.getController().setRequest(request);
+ verify(mDelegate).onSetRequest(request);
+
+ mDelegateProvider.reportLocation(createLocationResult("test_provider", mRandom));
+ verify(mListener, times(1)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceIdleHelper().setIdle(true);
+ verify(mDelegate, never()).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+
+ mInjector.getDeviceStationaryHelper().setStationary(true);
+ verify(mDelegate).onSetRequest(ProviderRequest.EMPTY_REQUEST);
+ verify(mListener, timeout(75).times(2)).onReportLocation(any(LocationResult.class));
+ verify(mListener, timeout(75).times(3)).onReportLocation(any(LocationResult.class));
+
+ mInjector.getDeviceIdleHelper().setIdle(false);
+ verify(mDelegate, times(2)).onSetRequest(request);
+ verify(mListener, after(75).times(3)).onReportLocation(any(LocationResult.class));
+ }
+
+ @Test
public void testThrottle_NoInitialLocation() {
ProviderRequest request = new ProviderRequest.Builder().setIntervalMillis(50).build();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
index 7a6110bdbda3..f17fa625d007 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackagesBroadcastTest.kt
@@ -79,8 +79,8 @@ class SuspendPackagesBroadcastTest {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10003))
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -97,8 +97,8 @@ class SuspendPackagesBroadcastTest {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, allowList(10001, 10002, 10007))
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), any(), nullable())
@@ -118,8 +118,8 @@ class SuspendPackagesBroadcastTest {
mockAllowList(packageSetting1, allowList(10001, 10002, 10003))
mockAllowList(packageSetting2, null)
- pms.sendPackagesSuspendedForUser(
- packagesToSuspend, uidsToSuspend, TEST_USER_ID, /* suspended = */ true)
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENDED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
verify(pms, times(2)).sendPackageBroadcast(any(), nullable(), bundleCaptor.capture(),
anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
@@ -133,6 +133,22 @@ class SuspendPackagesBroadcastTest {
}
}
+ @Test
+ @Throws(Exception::class)
+ fun sendPackagesSuspendModifiedForUser() {
+ pms.sendPackagesSuspendedForUser(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED,
+ packagesToSuspend, uidsToSuspend, TEST_USER_ID)
+ verify(pms).sendPackageBroadcast(
+ eq(Intent.ACTION_PACKAGES_SUSPENSION_CHANGED), nullable(), bundleCaptor.capture(),
+ anyInt(), nullable(), nullable(), any(), nullable(), nullable(), nullable())
+
+ var modifiedPackages = bundleCaptor.value.getStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST)
+ var modifiedUids = bundleCaptor.value.getIntArray(Intent.EXTRA_CHANGED_UID_LIST)
+ assertThat(modifiedPackages).asList().containsExactly(TEST_PACKAGE_1, TEST_PACKAGE_2)
+ assertThat(modifiedUids).asList().containsExactly(
+ packageSetting1.appId, packageSetting2.appId)
+ }
+
private fun allowList(vararg uids: Int) = SparseArray<IntArray>().apply {
this.put(TEST_USER_ID, uids)
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
new file mode 100644
index 000000000000..f94377fe51c2
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_DOZE;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.POLICY_VR;
+import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
+
+import static com.android.server.power.ScreenUndimDetector.DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_KEEP_SCREEN_ON_ENABLED;
+import static com.android.server.power.ScreenUndimDetector.KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS;
+import static com.android.server.power.ScreenUndimDetector.KEY_UNDIMS_REQUIRED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.testing.TestableContext;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests for {@link com.android.server.power.ScreenUndimDetector}
+ */
+@RunWith(JUnit4.class)
+public class ScreenUndimDetectorTest {
+ private static final List<Integer> ALL_POLICIES =
+ Arrays.asList(POLICY_OFF,
+ POLICY_DOZE,
+ POLICY_DIM,
+ POLICY_BRIGHT,
+ POLICY_VR);
+
+ @ClassRule
+ public static final TestableContext sContext = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ private ScreenUndimDetector mScreenUndimDetector;
+
+ private final TestClock mClock = new TestClock();
+
+ private static class TestClock extends ScreenUndimDetector.InternalClock {
+ long mCurrentTime = 0;
+ @Override
+ public long getCurrentTime() {
+ return mCurrentTime;
+ }
+
+ public void advanceTime(long millisAdvanced) {
+ mCurrentTime += millisAdvanced;
+ }
+ }
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(1), false /*makeDefault*/);
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_MAX_DURATION_BETWEEN_UNDIMS_MILLIS,
+ Long.toString(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS),
+ false /*makeDefault*/);
+
+ mScreenUndimDetector = new ScreenUndimDetector(mClock);
+ mScreenUndimDetector.systemReady(sContext);
+ }
+
+ @Test
+ public void recordScreenPolicy_disabledByFlag_noop() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
+
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+
+ @Test
+ public void recordScreenPolicy_samePolicy_noop() {
+ for (int policy : ALL_POLICIES) {
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(policy);
+ mScreenUndimDetector.recordScreenPolicy(policy);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_extends() {
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+ }
+
+ @Test
+ public void recordScreenPolicy_otherTransitions_doesNotExtend() {
+ for (int from : ALL_POLICIES) {
+ for (int to : ALL_POLICIES) {
+ if (from == POLICY_DIM && to == POLICY_BRIGHT) {
+ continue;
+ }
+ setup();
+ mScreenUndimDetector.recordScreenPolicy(from);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_twoUndimsNeeded_extends() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isTrue();
+ }
+
+ @Test
+ public void recordScreenPolicy_dimBrightDimOff_resetsCounter_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+
+ @Test
+ public void recordScreenPolicy_undimToOff_resetsCounter() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ }
+
+ @Test
+ public void recordScreenPolicy_undimOffUndim_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ // undim
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ // off
+ mScreenUndimDetector.recordScreenPolicy(POLICY_OFF);
+ // second undim
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToBright_tooFarApart_doesNotExtend() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(2), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ mClock.advanceTime(DEFAULT_MAX_DURATION_BETWEEN_UNDIMS_MILLIS + 5);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(1);
+ }
+
+ @Test
+ public void recordScreenPolicy_dimToNonBright_resets() {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ setup();
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+ mScreenUndimDetector.mWakeLock.acquire();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_DIM);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+
+ }
+
+ @Test
+ public void recordScreenPolicy_brightToNonDim_resets() {
+ for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE, POLICY_VR)) {
+ setup();
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis = 123;
+ mScreenUndimDetector.mWakeLock.acquire();
+
+ mScreenUndimDetector.recordScreenPolicy(POLICY_BRIGHT);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isEqualTo(0);
+ assertThat(mScreenUndimDetector.mWakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @Test
+ public void recordScreenPolicy_otherTransitions_doesNotReset() {
+ DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ KEY_UNDIMS_REQUIRED,
+ Integer.toString(3),
+ false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
+
+ for (int from : ALL_POLICIES) {
+ for (int to : ALL_POLICIES) {
+ if (from == POLICY_DIM && to != POLICY_BRIGHT) {
+ continue;
+ }
+ if (from == POLICY_BRIGHT && to != POLICY_DIM) {
+ continue;
+ }
+ mScreenUndimDetector.mCurrentScreenPolicy = POLICY_OFF;
+ mScreenUndimDetector.mUndimCounter = 1;
+ mScreenUndimDetector.mUndimCounterStartedMillis =
+ SystemClock.currentThreadTimeMillis();
+
+ mScreenUndimDetector.recordScreenPolicy(from);
+ mScreenUndimDetector.recordScreenPolicy(to);
+
+ assertThat(mScreenUndimDetector.mUndimCounter).isNotEqualTo(0);
+ assertThat(mScreenUndimDetector.mUndimCounterStartedMillis).isNotEqualTo(0);
+ }
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
index ba79a764b672..38f01b5acc0c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/sensorprivacy/SensorPrivacyServiceMockingTest.java
@@ -16,12 +16,18 @@
package com.android.server.sensorprivacy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.AppOpsManager;
+import android.app.AppOpsManagerInternal;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Environment;
@@ -33,8 +39,10 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.LocalServices;
import com.android.server.SensorPrivacyService;
+import com.android.server.SystemService;
import com.android.server.pm.UserManagerInternal;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -44,6 +52,7 @@ import org.mockito.quality.Strictness;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
@RunWith(AndroidTestingRunner.class)
public class SensorPrivacyServiceMockingTest {
@@ -63,10 +72,21 @@ public class SensorPrivacyServiceMockingTest {
public static final String PERSISTENCE_FILE6 =
String.format(PERSISTENCE_FILE_PATHS_TEMPLATE, 6);
+ public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micMute_camMute.xml";
+ public static final String PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micMute_camUnmute.xml";
+ public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camMute.xml";
+ public static final String PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE =
+ "SensorPrivacyServiceMockingTest/persisted_file_micUnmute_camUnmute.xml";
+
private Context mContext;
@Mock
private AppOpsManager mMockedAppOpsManager;
@Mock
+ private AppOpsManagerInternal mMockedAppOpsManagerInternal;
+ @Mock
private UserManagerInternal mMockedUserManagerInternal;
@Mock
private ActivityManager mMockedActivityManager;
@@ -134,13 +154,103 @@ public class SensorPrivacyServiceMockingTest {
}
}
+ @Test
+ public void testServiceInit_AppOpsRestricted_micMute_camMute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_MUTE, true, true);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micMute_camUnmute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_MUTE_CAM_UNMUTE, true, false);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micUnmute_camMute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_MUTE, false, true);
+ }
+
+ @Test
+ public void testServiceInit_AppOpsRestricted_micUnmute_camUnmute() throws IOException {
+ testServiceInit_AppOpsRestricted(PERSISTENCE_FILE_MIC_UNMUTE_CAM_UNMUTE, false, false);
+ }
+
+ private void testServiceInit_AppOpsRestricted(String persistenceFileMicMuteCamMute,
+ boolean expectedMicState, boolean expectedCamState)
+ throws IOException {
+ MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.WARN)
+ .spyStatic(LocalServices.class)
+ .spyStatic(Environment.class)
+ .startMocking();
+
+ try {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ spyOn(mContext);
+
+ doReturn(mMockedAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+ doReturn(mMockedAppOpsManagerInternal)
+ .when(() -> LocalServices.getService(AppOpsManagerInternal.class));
+ doReturn(mMockedUserManagerInternal)
+ .when(() -> LocalServices.getService(UserManagerInternal.class));
+ doReturn(mMockedActivityManager).when(mContext).getSystemService(ActivityManager.class);
+ doReturn(mMockedActivityTaskManager)
+ .when(mContext).getSystemService(ActivityTaskManager.class);
+ doReturn(mMockedTelephonyManager).when(mContext).getSystemService(
+ TelephonyManager.class);
+
+ String dataDir = mContext.getApplicationInfo().dataDir;
+ doReturn(new File(dataDir)).when(() -> Environment.getDataSystemDirectory());
+
+ File onDeviceFile = new File(dataDir, "sensor_privacy.xml");
+ onDeviceFile.delete();
+
+ doReturn(new int[]{0}).when(mMockedUserManagerInternal).getUserIds();
+ doReturn(ExtendedMockito.mock(UserInfo.class)).when(mMockedUserManagerInternal)
+ .getUserInfo(0);
+
+ CompletableFuture<Boolean> micState = new CompletableFuture<>();
+ CompletableFuture<Boolean> camState = new CompletableFuture<>();
+ doAnswer(invocation -> {
+ int code = invocation.getArgument(0);
+ boolean restricted = invocation.getArgument(1);
+ if (code == AppOpsManager.OP_RECORD_AUDIO) {
+ micState.complete(restricted);
+ } else if (code == AppOpsManager.OP_CAMERA) {
+ camState.complete(restricted);
+ }
+ return null;
+ }).when(mMockedAppOpsManagerInternal).setGlobalRestriction(anyInt(), anyBoolean(),
+ any());
+
+ initServiceWithPersistenceFile(onDeviceFile, persistenceFileMicMuteCamMute, 0);
+
+ Assert.assertTrue(micState.join() == expectedMicState);
+ Assert.assertTrue(camState.join() == expectedCamState);
+
+ } finally {
+ mockitoSession.finishMocking();
+ }
+ }
+
private void initServiceWithPersistenceFile(File onDeviceFile,
String persistenceFilePath) throws IOException {
+ initServiceWithPersistenceFile(onDeviceFile, persistenceFilePath, -1);
+ }
+
+ private void initServiceWithPersistenceFile(File onDeviceFile,
+ String persistenceFilePath, int startingUserId) throws IOException {
if (persistenceFilePath != null) {
Files.copy(mContext.getAssets().open(persistenceFilePath),
onDeviceFile.toPath());
}
- new SensorPrivacyService(mContext);
+ SensorPrivacyService service = new SensorPrivacyService(mContext);
+ if (startingUserId != -1) {
+ SystemService.TargetUser mockedTargetUser =
+ ExtendedMockito.mock(SystemService.TargetUser.class);
+ doReturn(startingUserId).when(mockedTargetUser).getUserIdentifier();
+ service.onUserStarting(mockedTargetUser);
+ }
onDeviceFile.delete();
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index 53483f6d70bc..a49afc75c739 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -18,13 +18,18 @@ package com.android.server.wallpaper;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_SYSTEM;
+import static android.os.FileObserver.CLOSE_WRITE;
+import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
+import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CROP;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
@@ -34,6 +39,7 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
@@ -41,6 +47,7 @@ import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
@@ -49,8 +56,10 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
+import android.graphics.Color;
import android.hardware.display.DisplayManager;
-import android.os.UserHandle;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
@@ -143,7 +152,6 @@ public class WallpaperManagerServiceTests {
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER,
PackageManager.PERMISSION_GRANTED);
- doNothing().when(sContext).sendBroadcastAsUser(any(), any());
//Wallpaper components
sWallpaperService = mock(IWallpaperConnection.Stub.class);
@@ -180,6 +188,7 @@ public class WallpaperManagerServiceTests {
MockitoAnnotations.initMocks(this);
sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ doNothing().when(sContext).sendBroadcastAsUser(any(), any());
final Display mockDisplay = mock(Display.class);
doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
@@ -242,13 +251,13 @@ public class WallpaperManagerServiceTests {
*/
@Test
public void testDataCorrectAfterBoot() {
- mService.switchUser(UserHandle.USER_SYSTEM, null);
+ mService.switchUser(USER_SYSTEM, null);
final WallpaperData fallbackData = mService.mFallbackWallpaper;
assertEquals("Fallback wallpaper component should be ImageWallpaper.",
sImageWallpaperComponentName, fallbackData.wallpaperComponent);
- verifyLastWallpaperData(UserHandle.USER_SYSTEM, sDefaultWallpaperComponent);
+ verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
verifyDisplayData();
}
@@ -261,7 +270,7 @@ public class WallpaperManagerServiceTests {
assumeThat(sDefaultWallpaperComponent,
not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
- final int testUserId = UserHandle.USER_SYSTEM;
+ final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
@@ -281,7 +290,7 @@ public class WallpaperManagerServiceTests {
*/
@Test
public void testSetCurrentComponent() throws Exception {
- final int testUserId = UserHandle.USER_SYSTEM;
+ final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
@@ -387,6 +396,42 @@ public class WallpaperManagerServiceTests {
assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors);
}
+ @Test
+ public void testWallpaperManagerCallbackInRightOrder() throws RemoteException {
+ WallpaperData wallpaper = new WallpaperData(
+ USER_SYSTEM, mService.getWallpaperDir(USER_SYSTEM), WALLPAPER, WALLPAPER_CROP);
+ wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ spyOn(wallpaper);
+ doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
+ doNothing().when(mService).switchWallpaper(any(), any());
+ doReturn(true).when(mService)
+ .bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
+ doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
+ doNothing().when(mService).generateCrop(wallpaper);
+
+ // timestamps of {ACTION_WALLPAPER_CHANGED, onWallpaperColorsChanged}
+ final long[] timestamps = new long[2];
+ doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime())
+ .when(sContext).sendBroadcastAsUser(any(), any());
+ doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime())
+ .when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
+
+ assertNull(wallpaper.wallpaperObserver);
+ mService.switchUser(wallpaper.userId, null);
+ assertNotNull(wallpaper.wallpaperObserver);
+ // We will call onEvent directly, so stop watching the file.
+ wallpaper.wallpaperObserver.stopWatching();
+
+ spyOn(wallpaper.wallpaperObserver);
+ doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
+ wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
+
+ // ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
+ assertTrue(timestamps[1] > timestamps[0]);
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
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 2892bf58a7d2..b3f7587df612 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -74,6 +74,7 @@ import java.util.function.Consumer;
public class AuthSessionTest {
private static final String TEST_PACKAGE = "test_package";
+ private static final long TEST_REQUEST_ID = 22;
@Mock private Context mContext;
@Mock private ITrustManager mTrustManager;
@@ -112,6 +113,7 @@ public class AuthSessionTest {
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
0 /* operationId */,
0 /* userId */);
@@ -133,6 +135,7 @@ public class AuthSessionTest {
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
operationId,
userId);
assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -153,6 +156,7 @@ public class AuthSessionTest {
eq(userId),
eq(mSensorReceiver),
eq(TEST_PACKAGE),
+ eq(TEST_REQUEST_ID),
eq(sensor.getCookie()),
anyBoolean() /* allowBackgroundAuthentication */);
}
@@ -185,6 +189,33 @@ public class AuthSessionTest {
}
@Test
+ public void testCancelReducesAppetiteForCookies() throws Exception {
+ setupFace(0 /* id */, false /* confirmationAlwaysRequired */,
+ mock(IBiometricAuthenticator.class));
+ setupFingerprint(1 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
+
+ final AuthSession session = createAuthSession(mSensors,
+ false /* checkDevicePolicyManager */,
+ Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
+ 44 /* operationId */,
+ 2 /* userId */);
+
+ session.goToInitialState();
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ assertEquals(BiometricSensor.STATE_WAITING_FOR_COOKIE, sensor.getSensorState());
+ }
+
+ session.onCancelAuthSession(false /* force */);
+
+ for (BiometricSensor sensor : session.mPreAuthInfo.eligibleSensors) {
+ session.onCookieReceived(sensor.getCookie());
+ assertEquals(BiometricSensor.STATE_CANCELING, sensor.getSensorState());
+ }
+ }
+
+ @Test
public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes()
throws Exception {
setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL);
@@ -212,6 +243,7 @@ public class AuthSessionTest {
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
operationId,
userId);
assertEquals(mSensors.size(), session.mPreAuthInfo.eligibleSensors.size());
@@ -238,7 +270,7 @@ public class AuthSessionTest {
// fingerprint sensor does not start even if all cookies are received
assertEquals(STATE_AUTH_STARTED, session.getState());
verify(mStatusBarService).showAuthenticationDialog(any(), any(), any(),
- anyBoolean(), anyBoolean(), anyInt(), any(), anyLong(), anyInt());
+ anyBoolean(), anyBoolean(), anyInt(), anyLong(), any(), anyLong(), anyInt());
// Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started.
session.onDialogAnimatedIn();
@@ -277,6 +309,7 @@ public class AuthSessionTest {
final AuthSession session = createAuthSession(mSensors,
false /* checkDevicePolicyManager */,
Authenticators.BIOMETRIC_STRONG,
+ TEST_REQUEST_ID,
0 /* operationId */,
0 /* userId */);
@@ -285,7 +318,8 @@ public class AuthSessionTest {
sessionConsumer.accept(session);
- verify(faceAuthenticator).cancelAuthenticationFromService(eq(mToken), eq(TEST_PACKAGE));
+ verify(faceAuthenticator).cancelAuthenticationFromService(
+ eq(mToken), eq(TEST_PACKAGE), eq(TEST_REQUEST_ID));
}
private PreAuthInfo createPreAuthInfo(List<BiometricSensor> sensors, int userId,
@@ -302,14 +336,14 @@ public class AuthSessionTest {
private AuthSession createAuthSession(List<BiometricSensor> sensors,
boolean checkDevicePolicyManager, @Authenticators.Types int authenticators,
- long operationId, int userId) throws RemoteException {
+ long requestId, long operationId, int userId) throws RemoteException {
final PromptInfo promptInfo = createPromptInfo(authenticators);
final PreAuthInfo preAuthInfo = createPreAuthInfo(sensors, userId, promptInfo,
checkDevicePolicyManager);
return new AuthSession(mContext, mStatusBarService, mSysuiReceiver, mKeyStore,
- mRandom, mClientDeathReceiver, preAuthInfo, mToken, operationId, userId,
+ mRandom, mClientDeathReceiver, preAuthInfo, mToken, requestId, operationId, userId,
mSensorReceiver, mClientReceiver, TEST_PACKAGE, promptInfo,
false /* debugEnabled */, mFingerprintSensorProps);
}
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 7c7afb7dde36..69d8e89ac3b3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -85,6 +85,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
@Presubmit
@SmallTest
@@ -93,6 +94,7 @@ public class BiometricServiceTest {
private static final String TAG = "BiometricServiceTest";
private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final long TEST_REQUEST_ID = 44;
private static final String ERROR_HW_UNAVAILABLE = "hw_unavailable";
private static final String ERROR_NOT_RECOGNIZED = "not_recognized";
@@ -151,6 +153,7 @@ public class BiometricServiceTest {
.thenReturn(mock(BiometricStrengthController.class));
when(mInjector.getTrustManager()).thenReturn(mTrustManager);
when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager);
+ when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1));
when(mResources.getString(R.string.biometric_error_hw_unavailable))
.thenReturn(ERROR_HW_UNAVAILABLE);
@@ -215,8 +218,7 @@ public class BiometricServiceTest {
mBiometricService.mCurrentAuthSession.getState());
verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl)
- .cancelAuthenticationFromService(any(),
- any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
// Simulate ERROR_CANCELED received from HAL
mBiometricService.mBiometricSensorReceiver.onError(
@@ -272,8 +274,9 @@ public class BiometricServiceTest {
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -357,8 +360,9 @@ public class BiometricServiceTest {
eq(false) /* credentialAllowed */,
eq(false) /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -467,6 +471,7 @@ public class BiometricServiceTest {
anyInt() /* userId */,
any(IBiometricSensorReceiver.class),
anyString() /* opPackageName */,
+ eq(TEST_REQUEST_ID),
cookieCaptor.capture() /* cookie */,
anyBoolean() /* allowBackgroundAuthentication */);
@@ -488,8 +493,9 @@ public class BiometricServiceTest {
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Hardware authenticated
@@ -543,8 +549,9 @@ public class BiometricServiceTest {
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -705,8 +712,9 @@ public class BiometricServiceTest {
anyBoolean() /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
anyString(),
- anyLong() /* sessionId */,
+ anyLong() /* requestId */,
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -805,8 +813,9 @@ public class BiometricServiceTest {
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -885,8 +894,9 @@ public class BiometricServiceTest {
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(TEST_REQUEST_ID),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -1030,8 +1040,7 @@ public class BiometricServiceTest {
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
eq(0 /* vendorCode */));
verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService(
- any(),
- any());
+ any(), any(), anyLong());
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -1051,7 +1060,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
}
@Test
@@ -1071,7 +1080,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
}
@Test
@@ -1088,7 +1097,7 @@ public class BiometricServiceTest {
waitForIdle();
verify(mBiometricService.mSensors.get(0).impl)
- .cancelAuthenticationFromService(any(), any());
+ .cancelAuthenticationFromService(any(), any(), anyLong());
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FACE),
eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED),
@@ -1126,7 +1135,7 @@ public class BiometricServiceTest {
false /* requireConfirmation */, null /* authenticators */);
mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken,
- TEST_PACKAGE_NAME);
+ TEST_PACKAGE_NAME, TEST_REQUEST_ID);
waitForIdle();
// Pretend that the HAL has responded to cancel with ERROR_CANCELED
@@ -1353,8 +1362,8 @@ public class BiometricServiceTest {
int authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
- authenticators);
+ long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mReceiver1).onError(
eq(BiometricAuthenticator.TYPE_FINGERPRINT),
@@ -1366,7 +1375,7 @@ public class BiometricServiceTest {
authenticators = Authenticators.BIOMETRIC_WEAK;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
authenticators);
waitForIdle();
@@ -1377,8 +1386,9 @@ public class BiometricServiceTest {
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Requesting strong and credential, when credential is setup
@@ -1387,7 +1397,7 @@ public class BiometricServiceTest {
when(mTrustManager.isDeviceSecure(anyInt())).thenReturn(true);
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */,
authenticators);
waitForIdle();
@@ -1399,8 +1409,9 @@ public class BiometricServiceTest {
eq(true) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
// Un-downgrading the authenticator allows successful strong auth
@@ -1414,7 +1425,7 @@ public class BiometricServiceTest {
authenticators = Authenticators.BIOMETRIC_STRONG;
assertEquals(BiometricManager.BIOMETRIC_SUCCESS,
invokeCanAuthenticate(mBiometricService, authenticators));
- invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
+ requestId = invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1,
false /* requireConfirmation */, authenticators);
waitForIdle();
verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1424,8 +1435,9 @@ public class BiometricServiceTest {
eq(false) /* credentialAllowed */,
anyBoolean() /* requireConfirmation */,
anyInt() /* userId */,
+ anyLong() /* operationId */,
eq(TEST_PACKAGE_NAME),
- anyLong() /* sessionId */,
+ eq(requestId),
eq(BIOMETRIC_MULTI_SENSOR_DEFAULT));
}
@@ -1617,11 +1629,12 @@ public class BiometricServiceTest {
mBiometricService.mStatusBarService = mock(IStatusBarService.class);
}
- private void invokeAuthenticateAndStart(IBiometricService.Stub service,
+ private long invokeAuthenticateAndStart(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
Integer authenticators) throws Exception {
// Request auth, creates a pending session
- invokeAuthenticate(service, receiver, requireConfirmation, authenticators);
+ final long requestId = invokeAuthenticate(
+ service, receiver, requireConfirmation, authenticators);
waitForIdle();
startPendingAuthSession(mBiometricService);
@@ -1629,6 +1642,8 @@ public class BiometricServiceTest {
assertNotNull(mBiometricService.mCurrentAuthSession);
assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState());
+
+ return requestId;
}
private static void startPendingAuthSession(BiometricService service) throws Exception {
@@ -1644,10 +1659,10 @@ public class BiometricServiceTest {
service.mImpl.onReadyForAuthentication(cookie);
}
- private static void invokeAuthenticate(IBiometricService.Stub service,
+ private static long invokeAuthenticate(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, boolean requireConfirmation,
Integer authenticators) throws Exception {
- service.authenticate(
+ return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
0 /* userId */,
@@ -1657,9 +1672,9 @@ public class BiometricServiceTest {
false /* checkDevicePolicy */));
}
- private static void invokeAuthenticateForWorkApp(IBiometricService.Stub service,
+ private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
IBiometricServiceReceiver receiver, Integer authenticators) throws Exception {
- service.authenticate(
+ return service.authenticate(
new Binder() /* token */,
0 /* operationId */,
0 /* userId */,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index f4d14995f7c7..e3e3900c47e0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -40,6 +40,7 @@ import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -193,7 +194,7 @@ public class BiometricSchedulerTest {
// Request it to be canceled. The operation can be canceled immediately, and the scheduler
// should go back to idle, since in this case the framework has not even requested the HAL
// to authenticate yet.
- mScheduler.cancelAuthenticationOrDetection(mToken);
+ mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
assertNull(mScheduler.mCurrentOperation);
}
@@ -303,7 +304,7 @@ public class BiometricSchedulerTest {
mScheduler.mPendingOperations.getFirst().mState);
// Request cancel before the authentication client has started
- mScheduler.cancelAuthenticationOrDetection(mToken);
+ mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */);
waitForIdle();
assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
mScheduler.mPendingOperations.getFirst().mState);
@@ -318,6 +319,107 @@ public class BiometricSchedulerTest {
}
@Test
+ public void testCancels_whenAuthRequestIdNotSet() {
+ testCancelsWhenRequestId(null /* requestId */, 2, true /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdNotSet_notStarted() {
+ testCancelsWhenRequestId(null /* requestId */, 2, false /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdMatches() {
+ testCancelsWhenRequestId(200L, 200, true /* started */);
+ }
+
+ @Test
+ public void testCancels_whenAuthRequestIdMatches_noStarted() {
+ testCancelsWhenRequestId(200L, 200, false /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenAuthRequestIdMismatched() {
+ testCancelsWhenRequestId(10L, 20, true /* started */);
+ }
+
+ @Test
+ public void testDoesNotCancel_whenAuthRequestIdMismatched_notStarted() {
+ testCancelsWhenRequestId(10L, 20, false /* started */);
+ }
+
+ private void testCancelsWhenRequestId(@Nullable Long requestId, long cancelRequestId,
+ boolean started) {
+ final boolean matches = requestId == null || requestId == cancelRequestId;
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ if (requestId != null) {
+ client.setRequestId(requestId);
+ }
+
+ mScheduler.scheduleClientMonitor(client);
+ if (started) {
+ mScheduler.startPreparedClient(client.getCookie());
+ }
+ waitForIdle();
+ mScheduler.cancelAuthenticationOrDetection(mToken, cancelRequestId);
+ waitForIdle();
+
+ assertEquals(matches && started ? 1 : 0, client.mNumCancels);
+
+ if (matches) {
+ if (started) {
+ assertEquals(Operation.STATE_STARTED_CANCELING,
+ mScheduler.mCurrentOperation.mState);
+ }
+ } else {
+ if (started) {
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ } else {
+ assertEquals(Operation.STATE_WAITING_FOR_COOKIE,
+ mScheduler.mCurrentOperation.mState);
+ }
+ }
+ }
+
+ @Test
+ public void testCancelsPending_whenAuthRequestIdsSet() {
+ final long requestId1 = 10;
+ final long requestId2 = 20;
+ final HalClientMonitor.LazyDaemon<Object> lazyDaemon = () -> mock(Object.class);
+ final ClientMonitorCallbackConverter callback = mock(ClientMonitorCallbackConverter.class);
+ final TestAuthenticationClient client1 = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ client1.setRequestId(requestId1);
+ final TestAuthenticationClient client2 = new TestAuthenticationClient(
+ mContext, lazyDaemon, mToken, callback);
+ client2.setRequestId(requestId2);
+
+ mScheduler.scheduleClientMonitor(client1);
+ mScheduler.scheduleClientMonitor(client2);
+ mScheduler.startPreparedClient(client1.getCookie());
+ waitForIdle();
+ mScheduler.cancelAuthenticationOrDetection(mToken, 9999);
+ waitForIdle();
+
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ assertEquals(Operation.STATE_WAITING_IN_QUEUE,
+ mScheduler.mPendingOperations.getFirst().mState);
+
+ mScheduler.cancelAuthenticationOrDetection(mToken, requestId2);
+ waitForIdle();
+
+ assertEquals(Operation.STATE_STARTED,
+ mScheduler.mCurrentOperation.mState);
+ assertEquals(Operation.STATE_WAITING_IN_QUEUE_CANCELING,
+ mScheduler.mPendingOperations.getFirst().mState);
+ }
+
+ @Test
public void testInterruptPrecedingClients_whenExpected() {
final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
withSettings().extraInterfaces(Interruptable.class));
@@ -348,6 +450,17 @@ public class BiometricSchedulerTest {
verify((Interruptable) interruptableMonitor, never()).cancel();
}
+ @Test
+ public void testClientDestroyed_afterFinish() {
+ final HalClientMonitor.LazyDaemon<Object> nonNullDaemon = () -> mock(Object.class);
+ final TestClientMonitor client =
+ new TestClientMonitor(mContext, mToken, nonNullDaemon);
+ mScheduler.scheduleClientMonitor(client);
+ client.mCallback.onClientFinished(client, true /* success */);
+ waitForIdle();
+ assertTrue(client.wasDestroyed());
+ }
+
private BiometricSchedulerProto getDump(boolean clearSchedulerBuffer) throws Exception {
return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer));
}
@@ -366,12 +479,10 @@ public class BiometricSchedulerTest {
@Override
protected void stopHalOperation() {
-
}
@Override
protected void startHalOperation() {
-
}
@Override
@@ -386,6 +497,7 @@ public class BiometricSchedulerTest {
}
private static class TestAuthenticationClient extends AuthenticationClient<Object> {
+ int mNumCancels = 0;
public TestAuthenticationClient(@NonNull Context context,
@NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token,
@@ -417,6 +529,11 @@ public class BiometricSchedulerTest {
public boolean wasUserDetected() {
return false;
}
+
+ public void cancel() {
+ mNumCancels++;
+ super.cancel();
+ }
}
private static class TestClientMonitor2 extends TestClientMonitor {
@@ -437,6 +554,7 @@ public class BiometricSchedulerTest {
private static class TestClientMonitor extends HalClientMonitor<Object> {
private boolean mUnableToStart;
private boolean mStarted;
+ private boolean mDestroyed;
public TestClientMonitor(@NonNull Context context, @NonNull IBinder token,
@NonNull LazyDaemon<Object> lazyDaemon) {
@@ -475,6 +593,11 @@ public class BiometricSchedulerTest {
}
+ @Override
+ public void destroy() {
+ mDestroyed = true;
+ }
+
public boolean wasUnableToStart() {
return mUnableToStart;
}
@@ -482,6 +605,11 @@ public class BiometricSchedulerTest {
public boolean hasStarted() {
return mStarted;
}
+
+ public boolean wasDestroyed() {
+ return mDestroyed;
+ }
+
}
private static void waitForIdle() {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
new file mode 100644
index 000000000000..09b5c5cac466
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CompositeCallbackTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@Presubmit
+@SmallTest
+public class CompositeCallbackTest {
+
+ @Test
+ public void testNullCallback() {
+ BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback2 = mock(BaseClientMonitor.Callback.class);
+ BaseClientMonitor.Callback callback3 = null;
+
+ BaseClientMonitor.CompositeCallback callback = new BaseClientMonitor.CompositeCallback(
+ callback1, callback2, callback3);
+
+ BaseClientMonitor clientMonitor = mock(BaseClientMonitor.class);
+
+ callback.onClientStarted(clientMonitor);
+ verify(callback1).onClientStarted(eq(clientMonitor));
+ verify(callback2).onClientStarted(eq(clientMonitor));
+
+ callback.onClientFinished(clientMonitor, true /* success */);
+ verify(callback1).onClientFinished(eq(clientMonitor), eq(true));
+ verify(callback2).onClientFinished(eq(clientMonitor), eq(true));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
new file mode 100644
index 000000000000..38e8dfa45262
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/FingerprintStateCallbackTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors.fingerprint;
+
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.hardware.fingerprint.FingerprintStateListener;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.EnrollClient;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@SmallTest
+public class FingerprintStateCallbackTest {
+
+ private FingerprintStateCallback mCallback;
+
+ @Mock
+ FingerprintStateListener mFingerprintStateListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ mCallback = new FingerprintStateCallback();
+ mCallback.registerFingerprintStateListener(mFingerprintStateListener);
+ }
+
+ @Test
+ public void testNoEnrollmentsToEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, true /* isNowEnrolled */,
+ true /* expectCallback */, true /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToNoEnrollments_callbackNotified() {
+ testEnrollmentCallback(true /* changed */, false /* isNowEnrolled */,
+ true /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ @Test
+ public void testEnrollmentsToEnrollments_callbackNotNotified() {
+ testEnrollmentCallback(false /* changed */, true /* isNowEnrolled */,
+ false /* expectCallback */, false /* expectedCallbackValue */);
+ }
+
+ private void testEnrollmentCallback(boolean changed, boolean isNowEnrolled,
+ boolean expectCallback, boolean expectedCallbackValue) {
+ EnrollClient<?> client = mock(EnrollClient.class);
+
+ final int userId = 10;
+ final int sensorId = 100;
+
+ when(client.hasEnrollmentStateChanged()).thenReturn(changed);
+ when(client.hasEnrollments()).thenReturn(isNowEnrolled);
+ when(client.getTargetUserId()).thenReturn(userId);
+ when(client.getSensorId()).thenReturn(sensorId);
+
+ mCallback.onClientFinished(client, true /* success */);
+ if (expectCallback) {
+ verify(mFingerprintStateListener).onEnrollmentsChanged(eq(userId), eq(sensorId),
+ eq(expectedCallbackValue));
+ } else {
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+ }
+
+ @Test
+ public void testAuthentication_enrollmentCallbackNeverNotified() {
+ AuthenticationClient<?> client = mock(AuthenticationClient.class);
+ mCallback.onClientFinished(client, true /* success */);
+ verify(mFingerprintStateListener, never()).onEnrollmentsChanged(anyInt(), anyInt(),
+ anyBoolean());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 35c37efa16b4..b51918e24b13 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -37,6 +37,7 @@ import androidx.test.filters.SmallTest;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.HalClientMonitor;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
import org.junit.Before;
@@ -58,6 +59,8 @@ public class FingerprintProviderTest {
private UserManager mUserManager;
@Mock
private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private SensorProps[] mSensorProps;
private LockoutResetDispatcher mLockoutResetDispatcher;
@@ -87,8 +90,8 @@ public class FingerprintProviderTest {
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
- mFingerprintProvider = new TestableFingerprintProvider(mContext, mSensorProps, TAG,
- mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+ mFingerprintProvider = new TestableFingerprintProvider(mContext, mFingerprintStateCallback,
+ mSensorProps, TAG, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
}
@SuppressWarnings("rawtypes")
@@ -133,11 +136,12 @@ public class FingerprintProviderTest {
private static class TestableFingerprintProvider extends FingerprintProvider {
public TestableFingerprintProvider(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull SensorProps[] props,
@NonNull String halInstanceName,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher) {
- super(context, props, halInstanceName, lockoutResetDispatcher,
+ super(context, fingerprintStateCallback, props, halInstanceName, lockoutResetDispatcher,
gestureAvailabilityDispatcher);
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index 0a0dcc97272b..f6b92097a9fb 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -41,6 +41,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.LockoutResetDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.FingerprintStateCallback;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +68,8 @@ public class Fingerprint21Test {
Fingerprint21.HalResultController mHalResultController;
@Mock
private BiometricScheduler mScheduler;
+ @Mock
+ private FingerprintStateCallback mFingerprintStateCallback;
private LockoutResetDispatcher mLockoutResetDispatcher;
private Fingerprint21 mFingerprint21;
@@ -96,8 +99,9 @@ public class Fingerprint21Test {
componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
resetLockoutRequiresHardwareAuthToken);
- mFingerprint21 = new TestableFingerprint21(mContext, sensorProps, mScheduler,
- new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController);
+ mFingerprint21 = new TestableFingerprint21(mContext, mFingerprintStateCallback, sensorProps,
+ mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
+ mHalResultController);
}
@Test
@@ -118,11 +122,13 @@ public class Fingerprint21Test {
private static class TestableFingerprint21 extends Fingerprint21 {
TestableFingerprint21(@NonNull Context context,
+ @NonNull FingerprintStateCallback fingerprintStateCallback,
@NonNull FingerprintSensorPropertiesInternal sensorProps,
@NonNull BiometricScheduler scheduler, @NonNull Handler handler,
@NonNull LockoutResetDispatcher lockoutResetDispatcher,
@NonNull HalResultController controller) {
- super(context, sensorProps, scheduler, handler, lockoutResetDispatcher, controller);
+ super(context, fingerprintStateCallback, sensorProps, scheduler, handler,
+ lockoutResetDispatcher, controller);
}
@Override
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 7b20bf0f6bc7..3ac30d0258a5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2960,9 +2960,6 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertThat(intent.getAction()).isEqualTo(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, -1))
.isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID));
- assertThat(
- (ComponentName) intent.getParcelableExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN))
- .isEqualTo(admin1);
assertThat(intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION))
.isEqualTo(UserManager.DISALLOW_ADJUST_VOLUME);
@@ -2999,7 +2996,7 @@ public class DevicePolicyManagerTest extends DpmTestBase {
assertThat(intent.getStringExtra(DevicePolicyManager.EXTRA_RESTRICTION))
.isEqualTo(DevicePolicyManager.POLICY_DISABLE_CAMERA);
assertThat(intent.getIntExtra(Intent.EXTRA_USER_ID, -1))
- .isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_SYSTEM_USER_UID));
+ .isEqualTo(UserHandle.getUserId(DpmMockContext.CALLER_UID));
// ScreenCapture should not be disabled by device owner
intent = dpm.createAdminSupportIntent(DevicePolicyManager.POLICY_DISABLE_SCREEN_CAPTURE);
assertThat(intent).isNull();
@@ -7753,6 +7750,12 @@ public class DevicePolicyManagerTest extends DpmTestBase {
DpmMockContext.CALLER_SYSTEM_USER_UID, admin1.getPackageName(), MODE_DEFAULT);
}
+ @Test
+ public void testGetOrganizationNameForUser_calledByNonPrivilegedApp_throwsException() {
+ assertExpectException(SecurityException.class, "Calling identity is not authorized",
+ () -> dpm.getOrganizationNameForUser(UserHandle.USER_SYSTEM));
+ }
+
private void setupVpnAuthorization(String userVpnPackage, int userVpnUid) {
final AppOpsManager.PackageOps vpnOp = new AppOpsManager.PackageOps(userVpnPackage,
userVpnUid, List.of(new AppOpsManager.OpEntry(
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 1ac28abb4c2f..418831f47c1a 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -20,10 +20,13 @@ import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REF
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE;
import static com.android.server.display.DisplayModeDirector.Vote.INVALID_SIZE;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static com.google.common.truth.Truth.assertThat;
@@ -56,7 +59,11 @@ import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
import android.hardware.display.DisplayManagerInternal.RefreshRateRange;
import android.hardware.fingerprint.IUdfpsHbmListener;
import android.os.Handler;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
import android.os.Looper;
+import android.os.RemoteException;
+import android.os.Temperature;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
@@ -68,6 +75,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.util.Preconditions;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.util.test.FakeSettingsProviderRule;
@@ -104,6 +112,7 @@ public class DisplayModeDirectorTest {
private static final boolean DEBUG = false;
private static final float FLOAT_TOLERANCE = 0.01f;
private static final int DISPLAY_ID = 0;
+ private static final float TRANSITION_POINT = 0.763f;
private Context mContext;
private FakesInjector mInjector;
@@ -116,6 +125,8 @@ public class DisplayModeDirectorTest {
public SensorManagerInternal mSensorManagerInternalMock;
@Mock
public DisplayManagerInternal mDisplayManagerInternalMock;
+ @Mock
+ public IThermalService mThermalServiceMock;
@Before
public void setUp() throws Exception {
@@ -124,6 +135,7 @@ public class DisplayModeDirectorTest {
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
mInjector = spy(new FakesInjector());
+ when(mInjector.getThermalService()).thenReturn(mThermalServiceMock);
mHandler = new Handler(Looper.getMainLooper());
LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
@@ -742,19 +754,27 @@ public class DisplayModeDirectorTest {
director.start(sensorManager);
- ArgumentCaptor<SensorEventListener> listenerCaptor =
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
+ ArgumentCaptor<SensorEventListener> sensorListenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
Mockito.verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
.registerListener(
- listenerCaptor.capture(),
+ sensorListenerCaptor.capture(),
eq(lightSensor),
anyInt(),
any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
+ SensorEventListener sensorListener = sensorListenerCaptor.getValue();
- setBrightness(10);
+ setBrightness(10, 10, displayListener);
// Sensor reads 20 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 20 /*lux*/));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertVoteForRefreshRate(vote, 90 /*fps*/);
@@ -762,9 +782,11 @@ public class DisplayModeDirectorTest {
assertThat(vote).isNotNull();
assertThat(vote.disableRefreshRateSwitching).isTrue();
- setBrightness(125);
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(10, 125, displayListener);
// Sensor reads 1000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 1000 /*lux*/));
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertThat(vote).isNull();
@@ -790,6 +812,14 @@ public class DisplayModeDirectorTest {
director.start(sensorManager);
+ ArgumentCaptor<DisplayListener> displayListenerCaptor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(displayListenerCaptor.capture(),
+ any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_CHANGED
+ | DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS));
+ DisplayListener displayListener = displayListenerCaptor.getValue();
+
ArgumentCaptor<SensorEventListener> listenerCaptor =
ArgumentCaptor.forClass(SensorEventListener.class);
verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
@@ -798,20 +828,22 @@ public class DisplayModeDirectorTest {
eq(lightSensor),
anyInt(),
any(Handler.class));
- SensorEventListener listener = listenerCaptor.getValue();
+ SensorEventListener sensorListener = listenerCaptor.getValue();
- setBrightness(100);
+ setBrightness(100, 100, displayListener);
// Sensor reads 2000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 2000));
Vote vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertThat(vote).isNull();
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE_SWITCH);
assertThat(vote).isNull();
- setBrightness(255);
+ // We expect DisplayModeDirector to act on BrightnessInfo.adjustedBrightness; set only this
+ // parameter to the necessary threshold
+ setBrightness(100, 255, displayListener);
// Sensor reads 9000 lux,
- listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
+ sensorListener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, 9000));
vote = director.getVote(Display.DEFAULT_DISPLAY, Vote.PRIORITY_FLICKER_REFRESH_RATE);
assertVoteForRefreshRate(vote, 60 /*fps*/);
@@ -1401,6 +1433,12 @@ public class DisplayModeDirectorTest {
public void testHbmVoting_forHdr() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ final int hbmRefreshRate = 72;
+
+ // Specify limitation before starting DisplayModeDirector to avoid waiting on property
+ // propagation
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hbmRefreshRate);
+
director.start(createMockSensorManager());
ArgumentCaptor<DisplayListener> captor =
@@ -1420,19 +1458,99 @@ public class DisplayModeDirectorTest {
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
- // Turn on HBM
+ // Turn on HBM, with brightness in the HBM range
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR));
+ new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
- assertVoteForRefreshRate(vote, 60.f);
+ assertVoteForRefreshRate(vote, hbmRefreshRate);
+
+ // Turn on HBM, with brightness below the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM, with brightness in the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT + FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, hbmRefreshRate);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
+
+ // Turn on HBM, with brightness below the HBM range
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(TRANSITION_POINT - FLOAT_TOLERANCE, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.45f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+ }
+
+ @Test
+ public void testHbmObserverGetsUpdatedRefreshRateInHbmSunlight() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+ final int initialRefreshRate = 60;
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(initialRefreshRate);
+ director.start(createMockSensorManager());
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+ .isEqualTo(initialRefreshRate);
+
+ final int updatedRefreshRate = 90;
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+ .isEqualTo(updatedRefreshRate);
+ }
+
+ @Test
+ public void testHbmObserverGetsUpdatedRefreshRateInHbmHdr() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, /* baseModeId= */ 0);
+
+ final int initialRefreshRate = 60;
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(initialRefreshRate);
+ director.start(createMockSensorManager());
+ assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+ .isEqualTo(initialRefreshRate);
+
+ final int updatedRefreshRate = 90;
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(updatedRefreshRate);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmHdr())
+ .isEqualTo(updatedRefreshRate);
}
@Test
@@ -1448,11 +1566,12 @@ public class DisplayModeDirectorTest {
| DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
DisplayListener listener = captor.getValue();
+ final int initialRefreshRate = 60;
// Specify Limitation
when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
List.of(new RefreshRateLimitation(
DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
- 60.f, 60.f)));
+ initialRefreshRate, initialRefreshRate)));
// Verify that there is no HBM vote initially
Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
@@ -1460,14 +1579,50 @@ public class DisplayModeDirectorTest {
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
- assertVoteForRefreshRate(vote, 60.f);
+ assertVoteForRefreshRate(vote, initialRefreshRate);
+
+ // Change refresh rate vote value through DeviceConfig, ensure it takes precedence
+ final int updatedRefreshRate = 90;
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(updatedRefreshRate);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight())
+ .isEqualTo(updatedRefreshRate);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, updatedRefreshRate);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn HBM on again and ensure the updated vote value stuck
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, updatedRefreshRate);
+
+ // Reset DeviceConfig refresh rate, ensure vote falls back to the initial value
+ mInjector.getDeviceConfig().setRefreshRateInHbmSunlight(0);
+ // Need to wait for the property change to propagate to the main thread.
+ waitForIdleSync();
+ assertThat(director.getHbmObserver().getRefreshRateInHbmSunlight()).isEqualTo(0);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, initialRefreshRate);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
@@ -1498,20 +1653,145 @@ public class DisplayModeDirectorTest {
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
// Turn off HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF));
+ new BrightnessInfo(0.43f, 0.1f, 0.8f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertNull(vote);
}
@Test
+ public void testHbmVoting_HbmUnsupported() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<DisplayListener> captor =
+ ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ DisplayListener listener = captor.getValue();
+
+ // Specify Limitation
+ when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID)).thenReturn(
+ List.of(new RefreshRateLimitation(
+ DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+ 60.0f, 60.0f)));
+
+ // Verify that there is no HBM vote initially
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HBM when HBM is supported; expect a valid transition point and a vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertVoteForRefreshRate(vote, 60.0f);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on Sunlight HBM when HBM is unsupported; expect an invalid transition point and
+ // no vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ HBM_TRANSITION_POINT_INVALID));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn on HDR HBM when HBM is unsupported; expect an invalid transition point and
+ // no vote.
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR,
+ HBM_TRANSITION_POINT_INVALID));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Turn off HBM
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF,
+ TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+ }
+
+ private void setHbmAndAssertRefreshRate(
+ DisplayModeDirector director, DisplayListener listener, int mode, float rr) {
+ when(mInjector.getBrightnessInfo(DISPLAY_ID))
+ .thenReturn(new BrightnessInfo(1.0f, 0.0f, 1.0f, mode, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
+
+ final Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ if (Float.isNaN(rr)) {
+ assertNull(vote);
+ } else {
+ assertVoteForRefreshRate(vote, rr);
+ }
+ }
+
+ @Test
+ public void testHbmVoting_forSunlightAndHdr() {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+
+ // Specify HDR limitation before starting DisplayModeDirector to avoid waiting on property
+ // propagation
+ final int hdrRr = 60;
+ mInjector.getDeviceConfig().setRefreshRateInHbmHdr(hdrRr);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<DisplayListener> captor = ArgumentCaptor.forClass(DisplayListener.class);
+ verify(mInjector).registerDisplayListener(captor.capture(), any(Handler.class),
+ eq(DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS
+ | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED));
+ DisplayListener listener = captor.getValue();
+
+ // Specify Sunlight limitations
+ final float sunlightRr = 90.0f;
+ when(mDisplayManagerInternalMock.getRefreshRateLimitations(DISPLAY_ID))
+ .thenReturn(List.of(new RefreshRateLimitation(
+ DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE, sunlightRr,
+ sunlightRr)));
+
+ // Verify that there is no HBM vote initially
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
+ assertNull(vote);
+
+ // Verify all state transitions
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR, hdrRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT, sunlightRr);
+ setHbmAndAssertRefreshRate(
+ director, listener, BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, Float.NaN);
+ }
+
+ @Test
public void testHbmVoting_RemovedDisplay() {
DisplayModeDirector director =
createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
@@ -1536,7 +1816,8 @@ public class DisplayModeDirectorTest {
// Turn on HBM
when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
- new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT));
+ new BrightnessInfo(1.0f, 0.0f, 1.0f, BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT,
+ TRANSITION_POINT));
listener.onDisplayChanged(DISPLAY_ID);
vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_HIGH_BRIGHTNESS_MODE);
assertVoteForRefreshRate(vote, 60.f);
@@ -1547,12 +1828,52 @@ public class DisplayModeDirectorTest {
assertNull(vote);
}
+ @Test
+ public void testSkinTemperature() throws RemoteException {
+ DisplayModeDirector director =
+ createDirectorFromRefreshRateArray(new float[] {60.0f, 90.0f}, 0);
+ director.start(createMockSensorManager());
+
+ ArgumentCaptor<IThermalEventListener> thermalEventListener =
+ ArgumentCaptor.forClass(IThermalEventListener.class);
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ thermalEventListener.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = thermalEventListener.getValue();
+
+ // Verify that there is no skin temperature vote initially.
+ Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+ assertNull(vote);
+
+ // Set the skin temperature to critical and verify that we added a vote.
+ listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_CRITICAL));
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+ assertVoteForRefreshRateRange(vote, 0f, 60.f);
+
+ // Set the skin temperature to severe and verify that the vote is gone.
+ listener.notifyThrottling(getSkinTemp(Temperature.THROTTLING_SEVERE));
+ vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_SKIN_TEMPERATURE);
+ assertNull(vote);
+ }
+
+ private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
+ return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
+ }
+
private void assertVoteForRefreshRate(Vote vote, float refreshRate) {
assertThat(vote).isNotNull();
final RefreshRateRange expectedRange = new RefreshRateRange(refreshRate, refreshRate);
assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
}
+ private void assertVoteForRefreshRateRange(
+ Vote vote, float refreshRateLow, float refreshRateHigh) {
+ assertThat(vote).isNotNull();
+ final RefreshRateRange expectedRange =
+ new RefreshRateRange(refreshRateLow, refreshRateHigh);
+ assertThat(vote.refreshRateRange).isEqualTo(expectedRange);
+ }
+
public static class FakeDeviceConfig extends FakeDeviceConfigInterface {
@Override
public String getProperty(String namespace, String name) {
@@ -1575,6 +1896,16 @@ public class DisplayModeDirectorTest {
String.valueOf(fps));
}
+ void setRefreshRateInHbmSunlight(int fps) {
+ putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, String.valueOf(fps));
+ }
+
+ void setRefreshRateInHbmHdr(int fps) {
+ putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
+ }
+
void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
@@ -1641,11 +1972,14 @@ public class DisplayModeDirectorTest {
}
}
- private void setBrightness(int brightness) {
- Settings.System.putInt(mContext.getContentResolver(), Settings.System.SCREEN_BRIGHTNESS,
- brightness);
- mInjector.notifyBrightnessChanged();
- waitForIdleSync();
+ private void setBrightness(int brightness, int adjustedBrightness, DisplayListener listener) {
+ float floatBri = BrightnessSynchronizer.brightnessIntToFloat(brightness);
+ float floatAdjBri = BrightnessSynchronizer.brightnessIntToFloat(adjustedBrightness);
+
+ when(mInjector.getBrightnessInfo(DISPLAY_ID)).thenReturn(
+ new BrightnessInfo(floatBri, floatAdjBri, 0.0f, 1.0f,
+ BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF, TRANSITION_POINT));
+ listener.onDisplayChanged(DISPLAY_ID);
}
private void setPeakRefreshRate(float fps) {
@@ -1709,27 +2043,6 @@ public class DisplayModeDirectorTest {
}
@Override
- public void registerBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- if (mBrightnessObserver != null) {
- throw new IllegalStateException("Tried to register a second brightness observer");
- }
- mBrightnessObserver = observer;
- }
-
- @Override
- public void unregisterBrightnessObserver(@NonNull ContentResolver cr,
- @NonNull ContentObserver observer) {
- mBrightnessObserver = null;
- }
-
- void notifyBrightnessChanged() {
- if (mBrightnessObserver != null) {
- mBrightnessObserver.dispatchChange(false /*selfChange*/, DISPLAY_BRIGHTNESS_URI);
- }
- }
-
- @Override
public void registerPeakRefreshRateObserver(@NonNull ContentResolver cr,
@NonNull ContentObserver observer) {
mPeakRefreshRateObserver = observer;
@@ -1748,6 +2061,11 @@ public class DisplayModeDirectorTest {
return false;
}
+ @Override
+ public IThermalService getThermalService() {
+ return null;
+ }
+
void notifyPeakRefreshRateChanged() {
if (mPeakRefreshRateObserver != null) {
mPeakRefreshRateObserver.dispatchChange(false /*selfChange*/,
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index cc3591c89560..aca863299b33 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -20,6 +20,8 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
+import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.spy;
@@ -124,6 +126,7 @@ public class HighBrightnessModeControllerTest {
mInjectorMock, mHandler, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayToken, DEFAULT_MIN,
DEFAULT_MAX, null, () -> {}, mContextSpy);
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+ assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@Test
@@ -135,6 +138,7 @@ public class HighBrightnessModeControllerTest {
hbmc.setAutoBrightnessEnabled(true);
hbmc.onAmbientLuxChange(MINIMUM_LUX - 1); // below allowed range
assertState(hbmc, DEFAULT_MIN, DEFAULT_MAX, HIGH_BRIGHTNESS_MODE_OFF);
+ assertEquals(hbmc.getTransitionPoint(), HBM_TRANSITION_POINT_INVALID, 0.0f);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
index acd3fcab5e52..3261dfaa95c9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/permission/LegacyPermissionManagerServiceTest.java
@@ -125,7 +125,7 @@ public class LegacyPermissionManagerServiceTest {
public void checkDeviceIdentifierAccess_hasPrivilegedPermission_returnsGranted() {
// Apps with the READ_PRIVILEGED_PHONE_STATE permission should have access to device
// identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
APP_PID, APP_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
@@ -140,7 +140,7 @@ public class LegacyPermissionManagerServiceTest {
public void checkDeviceIdentifierAccess_hasAppOp_returnsGranted() {
// Apps that have been granted the READ_DEVICE_IDENTIFIERS appop should have access to
// device identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mAppOpsManager.noteOpNoThrow(eq(AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS),
eq(APP_UID), eq(mPackageName), any(), any())).thenReturn(
AppOpsManager.MODE_ALLOWED);
@@ -156,7 +156,7 @@ public class LegacyPermissionManagerServiceTest {
public void checkDeviceIdentifierAccess_hasDpmAccess_returnsGranted() {
// Apps that pass a DevicePolicyManager device / profile owner check should have access to
// device identifiers.
- setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckDeviceIdentifierAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
when(mDevicePolicyManager.hasDeviceIdentifierAccess(mPackageName, APP_PID,
APP_UID)).thenReturn(true);
@@ -236,7 +236,7 @@ public class LegacyPermissionManagerServiceTest {
// both the permission and the appop must be granted. If the permission is granted but the
// appop is not then AppOpsManager#MODE_IGNORED should be returned to indicate that this
// should be a silent failure.
- setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
setPackageTargetSdk(Build.VERSION_CODES.Q);
grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
@@ -256,7 +256,7 @@ public class LegacyPermissionManagerServiceTest {
// Apps targeting R+ with just the READ_PHONE_STATE permission granted should not have
// access to the phone number; PERMISSION_DENIED should be returned both with and without
// the appop granted since this check should be skipped for target SDK R+.
- setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID);
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID);
grantPermissionAndAppop(android.Manifest.permission.READ_PHONE_STATE, null);
int resultWithoutAppop = mLegacyPermissionManagerService.checkPhoneNumberAccess(
@@ -319,12 +319,79 @@ public class LegacyPermissionManagerServiceTest {
assertEquals(PackageManager.PERMISSION_GRANTED, resultWithAppop);
}
+ @Test
+ public void checkPhoneNumberAccess_providedUidDoesNotMatchPackageUid_throwsException()
+ throws Exception {
+ // An app can directly interact with one of the services that accepts a package name and
+ // returns a protected resource via a direct binder transact. This app could then provide
+ // the name of another app that targets pre-R, then determine if the app is installed based
+ // on whether the service throws an exception or not. While the app can provide the package
+ // name of another app, it cannot specify the package uid which is passed to the
+ // LegacyPermissionManager using Binder#getCallingUid. Ultimately this uid should then be
+ // compared against the actual uid of the package to ensure information about packages
+ // installed on the device is not leaked.
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, APP_UID + 1);
+
+ assertThrows(SecurityException.class,
+ () -> mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+ CHECK_PHONE_NUMBER_MESSAGE, null, APP_PID, APP_UID));
+ }
+
+ @Test
+ public void checkPhoneNumberAccess_nullPackageNameSystemUid_returnsGranted() throws Exception {
+ // The platform can pass a null package name when checking if the platform itself has
+ // access to the device phone number(s) / identifier(s). This test ensures if a null package
+ // is provided, then the package uid check is skipped and the test is based on whether the
+ // the provided uid / pid has been granted the privileged permission.
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+ when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ SYSTEM_PID, SYSTEM_UID)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(null,
+ CHECK_PHONE_NUMBER_MESSAGE, null, SYSTEM_PID, SYSTEM_UID);
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, result);
+ }
+
+ @Test
+ public void checkPhoneNumberAccess_systemUidMismatchPackageUid_returnsGranted()
+ throws Exception {
+ // When the platform is checking device phone number / identifier access checks for other
+ // components on the platform, a uid less than the first application UID is provided; this
+ // test verifies the package uid check is skipped and access is still granted with the
+ // privileged permission.
+ int telephonyUid = SYSTEM_UID + 1;
+ int telephonyPid = SYSTEM_PID + 1;
+ setupCheckPhoneNumberAccessTest(SYSTEM_PID, SYSTEM_UID, -1);
+ when(mInjector.checkPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ telephonyPid, telephonyUid)).thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ int result = mLegacyPermissionManagerService.checkPhoneNumberAccess(mPackageName,
+ CHECK_PHONE_NUMBER_MESSAGE, null, telephonyPid, telephonyUid);
+
+ assertEquals(PackageManager.PERMISSION_GRANTED, result);
+ }
+
/**
* Configures device identifier access tests to fail; tests verifying access should individually
* set an access check to succeed to verify access when that condition is met.
*/
private void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid) {
- setupAccessTest(callingPid, callingUid);
+ setupCheckDeviceIdentifierAccessTest(callingPid, callingUid, callingUid);
+ }
+
+ /**
+ * Configures device identifier access tests to fail; tests verifying access should individually
+ * set an access check to succeed to verify access when that condition is met.
+ *
+ * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+ * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+ * the package being checked; to ensure this check is successful, this method accepts the
+ * {@code packageUid} to be used for the package being checked.
+ */
+ public void setupCheckDeviceIdentifierAccessTest(int callingPid, int callingUid,
+ int packageUid) {
+ setupAccessTest(callingPid, callingUid, packageUid);
when(mDevicePolicyManager.hasDeviceIdentifierAccess(anyString(), anyInt(),
anyInt())).thenReturn(false);
@@ -333,11 +400,26 @@ public class LegacyPermissionManagerServiceTest {
}
/**
+ * Configures phone number access tests to fail; tests verifying access should individually
+ * set an access check to succeed to verify access when that condition is set.
+ *
+ */
+ private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid) throws Exception {
+ setupCheckPhoneNumberAccessTest(callingPid, callingUid, callingUid);
+ }
+
+ /**
* Configures phone number access tests to fail; tests verifying access should individually set
* an access check to succeed to verify access when that condition is met.
+ *
+ * <p>To prevent leaking package information, access checks for package UIDs >= {@link
+ * android.os.Process#FIRST_APPLICATION_UID} must ensure the provided uid matches the uid of
+ * the package being checked; to ensure this check is successful, this method accepts the
+ * {@code packageUid} to be used for the package being checked.
*/
- private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid) throws Exception {
- setupAccessTest(callingPid, callingUid);
+ private void setupCheckPhoneNumberAccessTest(int callingPid, int callingUid, int packageUid)
+ throws Exception {
+ setupAccessTest(callingPid, callingUid, packageUid);
setPackageTargetSdk(Build.VERSION_CODES.R);
}
@@ -345,9 +427,10 @@ public class LegacyPermissionManagerServiceTest {
* Configures the common mocks for any access tests using the provided {@code callingPid}
* and {@code callingUid}.
*/
- private void setupAccessTest(int callingPid, int callingUid) {
+ private void setupAccessTest(int callingPid, int callingUid, int packageUid) {
when(mInjector.getCallingPid()).thenReturn(callingPid);
when(mInjector.getCallingUid()).thenReturn(callingUid);
+ when(mInjector.getPackageUidForUser(anyString(), anyInt())).thenReturn(packageUid);
when(mInjector.checkPermission(anyString(), anyInt(), anyInt())).thenReturn(
PackageManager.PERMISSION_DENIED);
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
index 5012ca9ab420..6e3f754e4882 100644
--- a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -210,7 +210,7 @@ public class NotifierTest {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return mNotifierMock;
}
@@ -298,6 +298,7 @@ public class NotifierTest {
BatteryStats.SERVICE_NAME)),
mInjector.createSuspendBlocker(mService, "testBlocker"),
null,
+ null,
null);
}
}
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 5eabc1bea148..3d64d4b5abb9 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -215,7 +215,7 @@ public class PowerManagerServiceTest {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
- FaceDownDetector faceDownDetector) {
+ FaceDownDetector faceDownDetector, ScreenUndimDetector screenUndimDetector) {
return mNotifierMock;
}
diff --git a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
index 11554c7a7dc5..6334e9d3cdc5 100644
--- a/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/uwb/UwbServiceImplTest.java
@@ -82,6 +82,8 @@ public class UwbServiceImplTest {
MockitoAnnotations.initMocks(this);
when(mUwbInjector.getVendorService()).thenReturn(mVendorService);
when(mUwbInjector.checkUwbRangingPermissionForDataDelivery(any(), any())).thenReturn(true);
+ when(mUwbInjector.isPersistedUwbStateEnabled()).thenReturn(true);
+ when(mUwbInjector.isAirplaneModeOn()).thenReturn(false);
when(mVendorService.asBinder()).thenReturn(mVendorServiceBinder);
mUwbServiceImpl = new UwbServiceImpl(mContext, mUwbInjector);
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 71c05b5c46f7..ea46eab6e8f9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -32,6 +32,7 @@ import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
@@ -72,6 +73,7 @@ import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IAccessibilityManager;
@@ -1182,6 +1184,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
mService.buzzBeepBlinkLocked(r);
+ verifyDelayedVibrate(mService.getVibratorHelper().createFallbackVibration(false));
// quiet update should stop making noise
mService.buzzBeepBlinkLocked(s);
@@ -1564,6 +1567,32 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase {
}
@Test
+ public void testRingtoneInsistentBeep_canUpdate() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.fromParts("a", "b", "c"),
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+ verifyDelayedVibrateLooped();
+ Mockito.reset(mVibrator);
+ Mockito.reset(mRingtonePlayer);
+
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+ // beep wasn't reset
+ verifyNeverBeep();
+ verifyNeverVibrate();
+ verify(mRingtonePlayer, never()).stopAsync();
+ verify(mVibrator, never()).cancel();
+ }
+
+ @Test
public void testCannotInterruptRingtoneInsistentBuzz() {
NotificationChannel ringtoneChannel =
new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index f9663f200b56..987236c7c98c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -65,6 +65,7 @@ import com.google.android.collect.Lists;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -1320,16 +1321,15 @@ public class ManagedServicesTest extends UiServiceTestCase {
APPROVAL_BY_COMPONENT);
ComponentName cn = ComponentName.unflattenFromString("a/a");
- service.registerSystemService(cn, 0);
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onNullBinding(cn);
- return true;
- });
+ ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+ when(context.bindServiceAsUser(any(), captor.capture(), anyInt(), any()))
+ .thenAnswer(invocation -> {
+ captor.getValue().onNullBinding(cn);
+ return true;
+ });
service.registerSystemService(cn, 0);
- assertFalse(service.isBound(cn, 0));
+ verify(context).unbindService(captor.getValue());
}
@Test
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 f57c416e4a97..1ae219db7726 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -516,7 +516,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
- mWorkerHandler = mService.new WorkerHandler(mTestableLooper.getLooper());
+ mWorkerHandler = spy(mService.new WorkerHandler(mTestableLooper.getLooper()));
mService.init(mWorkerHandler, mRankingHandler, mPackageManager, mPackageManagerClient,
mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr,
mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, mGroupHelper, mAm, mAtm,
@@ -2703,6 +2703,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testCrossUserSnooze() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+ mService.addNotification(r);
+ NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+ mService.addNotification(r2);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(r.getKey(), 1000, null, mListener);
+
+ verify(mWorkerHandler, never()).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
+ public void testSameUserSnooze() {
+ NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 10);
+ mService.addNotification(r);
+ NotificationRecord r2 = generateNotificationRecord(mTestNotificationChannel, 0);
+ mService.addNotification(r2);
+
+ mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
+ when(mListener.enabledAndUserMatches(anyInt())).thenReturn(true);
+ when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
+
+ mService.snoozeNotificationInt(r2.getKey(), 1000, null, mListener);
+
+ verify(mWorkerHandler).post(
+ any(NotificationManagerService.SnoozeNotificationRunnable.class));
+ }
+
+ @Test
public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
@@ -3975,6 +4011,80 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testApplyAdjustmentsLogged() throws Exception {
+ NotificationManagerService.WorkerHandler handler = mock(
+ NotificationManagerService.WorkerHandler.class);
+ mService.setHandler(handler);
+ when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true);
+
+ // Set up notifications that will be adjusted
+ final NotificationRecord r1 = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ r1.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r1);
+ final NotificationRecord r2 = generateNotificationRecord(
+ mTestNotificationChannel, 2, null, true);
+ r2.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r2);
+
+ // Third notification that's NOT adjusted, just to make sure that doesn't get spuriously
+ // logged.
+ final NotificationRecord r3 = generateNotificationRecord(
+ mTestNotificationChannel, 3, null, true);
+ r3.getSbn().setInstanceId(mNotificationInstanceIdSequence.newInstanceId());
+ mService.addNotification(r3);
+
+ List<Adjustment> adjustments = new ArrayList<>();
+
+ // Test an adjustment that's associated with a ranking change and one that's not
+ Bundle signals1 = new Bundle();
+ signals1.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_HIGH);
+ Adjustment adjustment1 = new Adjustment(
+ r1.getSbn().getPackageName(), r1.getKey(), signals1, "",
+ r1.getUser().getIdentifier());
+ adjustments.add(adjustment1);
+
+ // This one wouldn't trigger a ranking change, but should still trigger a log.
+ Bundle signals2 = new Bundle();
+ signals2.putFloat(Adjustment.KEY_RANKING_SCORE, -0.5f);
+ Adjustment adjustment2 = new Adjustment(
+ r2.getSbn().getPackageName(), r2.getKey(), signals2, "",
+ r2.getUser().getIdentifier());
+ adjustments.add(adjustment2);
+
+ mBinderService.applyAdjustmentsFromAssistant(null, adjustments);
+ verify(mRankingHandler, times(1)).requestSort();
+
+ // Actually apply the adjustments & recalculate importance when run
+ doAnswer(invocationOnMock -> {
+ ((NotificationRecord) invocationOnMock.getArguments()[0])
+ .applyAdjustments();
+ ((NotificationRecord) invocationOnMock.getArguments()[0])
+ .calculateImportance();
+ return null;
+ }).when(mRankingHelper).extractSignals(any(NotificationRecord.class));
+
+ // Now make sure that when the sort happens, we actually log the changes.
+ mService.handleRankingSort();
+
+ // Even though the ranking score change is not meant to trigger a ranking update,
+ // during this process the package visibility & canShowBadge values are changing
+ // in all notifications, so all 3 seem to trigger a ranking change. Here we check instead
+ // that scheduleSendRankingUpdate is sent and that the relevant fields have been changed
+ // accordingly to confirm the adjustments happened to the 2 relevant notifications.
+ verify(handler, times(3)).scheduleSendRankingUpdate();
+ assertEquals(IMPORTANCE_HIGH, r1.getImportance());
+ assertTrue(r2.rankingScoreMatches(-0.5f));
+ assertEquals(2, mNotificationRecordLogger.numCalls());
+ assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ mNotificationRecordLogger.event(0));
+ assertEquals(NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED,
+ mNotificationRecordLogger.event(1));
+ assertEquals(1, mNotificationRecordLogger.get(0).getInstanceId());
+ assertEquals(2, mNotificationRecordLogger.get(1).getInstanceId());
+ }
+
+ @Test
public void testEnqueuedAdjustmentAppliesAdjustments() throws Exception {
final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
mService.addEnqueuedNotification(r);
@@ -4793,6 +4903,52 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testSetNotificationsShownFromListener_protectsCrossUserInformation()
+ throws RemoteException {
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+ null, 0);
+ final NotificationRecord r =
+ new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ // no security exception!
+ mBinderService.setNotificationsShownFromListener(null, new String[] {r.getKey()});
+
+ verify(mAppUsageStats, never()).reportInterruptiveNotification(
+ anyString(), anyString(), anyInt());
+ }
+
+ @Test
+ public void testCancelNotificationsFromListener_protectsCrossUserInformation()
+ throws RemoteException {
+ Notification.Builder nb = new Notification.Builder(
+ mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag" + System.currentTimeMillis(), UserHandle.PER_USER_RANGE, 0,
+ nb.build(), UserHandle.getUserHandleForUid(mUid + UserHandle.PER_USER_RANGE),
+ null, 0);
+ final NotificationRecord r =
+ new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ r.setTextChanged(true);
+ mService.addNotification(r);
+
+ // no security exception!
+ mBinderService.cancelNotificationsFromListener(null, new String[] {r.getKey()});
+
+ waitForIdle();
+ assertEquals(1, mService.getNotificationRecordCount());
+ }
+
+ @Test
public void testMaybeRecordInterruptionLocked_doesNotRecordTwice()
throws RemoteException {
final NotificationRecord r = generateNotificationRecord(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
index 64fd19e69009..8a11798bbf19 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerFake.java
@@ -45,6 +45,15 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger {
groupInstanceId = groupId;
}
+ CallRecord(NotificationRecord r, int position, int buzzBeepBlink, InstanceId groupId) {
+ super(r, null);
+ this.position = position;
+ this.buzzBeepBlink = buzzBeepBlink;
+ wasLogged = true;
+ event = NotificationReportedEvent.NOTIFICATION_ADJUSTED;
+ groupInstanceId = groupId;
+ }
+
CallRecord(NotificationRecord r, UiEventLogger.UiEventEnum event) {
super(r, null);
wasLogged = true;
@@ -75,6 +84,12 @@ class NotificationRecordLoggerFake implements NotificationRecordLogger {
}
@Override
+ public void logNotificationAdjusted(NotificationRecord r, int position, int buzzBeepBlink,
+ InstanceId groupId) {
+ mCalls.add(new CallRecord(r, position, buzzBeepBlink, groupId));
+ }
+
+ @Override
public void log(UiEventLogger.UiEventEnum event, NotificationRecord r) {
mCalls.add(new CallRecord(r, event));
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index bf0ed713e4c3..66d157708332 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -91,6 +91,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
import android.service.notification.ConversationChannelWrapper;
@@ -376,27 +377,19 @@ public class PreferencesHelperTest extends UiServiceTestCase {
when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid);
}
- private static NotificationChannel createNotificationChannel(String id, String name,
- int importance) {
- NotificationChannel channel = new NotificationChannel(id, name, importance);
- channel.setSound(SOUND_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT);
- return channel;
- }
-
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
// Setup package notifications.
String package0 = "test.package.user0";
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
- NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+ NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
String package10 = "test.package.user10";
int uid10 = 1001001;
setUpPackageWithUid(package10, uid10);
- NotificationChannel channel10 = createNotificationChannel("id10", "name10",
- IMPORTANCE_HIGH);
+ NotificationChannel channel10 = new NotificationChannel("id10", "name10", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package10, uid10, channel10, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package10, uid10, true, 10);
@@ -421,7 +414,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
String package0 = "test.package.user0";
int uid0 = 1001;
setUpPackageWithUid(package0, uid0);
- NotificationChannel channel0 = createNotificationChannel("id0", "name0", IMPORTANCE_HIGH);
+ NotificationChannel channel0 = new NotificationChannel("id0", "name0", IMPORTANCE_HIGH);
assertTrue(mHelper.createNotificationChannel(package0, uid0, channel0, true, false));
ByteArrayOutputStream baos = writeXmlAndPurge(package0, uid0, true, 0);
@@ -514,8 +507,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 = createNotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
channel2.setDescription("descriptions for all");
channel2.setSound(SOUND_URI, mAudioAttributes);
channel2.enableLights(true);
@@ -524,7 +518,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel2.enableVibration(false);
channel2.setGroup(ncg.getId());
channel2.setLightColor(Color.BLUE);
- NotificationChannel channel3 = createNotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+ NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
channel3.enableVibration(true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -631,8 +625,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
@Test
- public void testRestoreXml_withNonExistentCanonicalizedSoundUri_ignoreChannel()
- throws Exception {
+ public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
Thread.sleep(3000);
doReturn(null)
.when(mTestIContentProvider).canonicalize(any(), eq(CANONICAL_SOUND_URI));
@@ -650,7 +643,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel actualChannel = mHelper.getNotificationChannel(
PKG_N_MR1, UID_N_MR1, channel.getId(), false);
- assertNull(actualChannel);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
}
@@ -659,8 +652,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
* handle its restore properly.
*/
@Test
- public void testRestoreXml_withUncanonicalizedNonLocalSoundUri_ignoreChannel()
- throws Exception {
+ public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
// Not a local uncanonicalized uri, simulating that it fails to exist locally
doReturn(null)
.when(mTestIContentProvider).canonicalize(any(), eq(SOUND_URI));
@@ -679,7 +671,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
backupWithUncanonicalizedSoundUri.getBytes(), true, UserHandle.USER_SYSTEM);
NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, id, false);
- assertNull(actualChannel);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
}
@Test
@@ -703,11 +695,11 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
NotificationChannel channel2 =
- createNotificationChannel("id2", "name2", IMPORTANCE_HIGH);
+ new NotificationChannel("id2", "name2", IMPORTANCE_HIGH);
NotificationChannel channel3 =
- createNotificationChannel("id3", "name3", IMPORTANCE_LOW);
+ new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
channel3.setGroup(ncg.getId());
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
@@ -3062,7 +3054,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
public void testChannelXml_backupDefaultApp() throws Exception {
NotificationChannel channel1 =
- createNotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
mHelper.createNotificationChannel(PKG_O, UID_O, channel1, true, false);
@@ -3343,7 +3335,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
- PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
assertTrue(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
assertFalse(mHelper.deleteNotificationChannel(PKG_P, UID_P, "id"));
}
@@ -3354,7 +3346,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mAppOpsManager, mStatsEventBuilderFactory);
mHelper.createNotificationChannel(
- PKG_P, UID_P, createNotificationChannel("id", "id", 2), true, false);
+ PKG_P, UID_P, new NotificationChannel("id", "id", 2), true, false);
mHelper.deleteNotificationChannel(PKG_P, UID_P, "id");
NotificationChannel nc1 = mHelper.getNotificationChannel(PKG_P, UID_P, "id", true);
assertTrue(DateUtils.isToday(nc1.getDeletedTimeMs()));
diff --git a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
index 222c692a9778..6b36fe808a2e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/SingleKeyGestureTests.java
@@ -69,7 +69,7 @@ public class SingleKeyGestureTests {
@Before
public void setUp() {
- mDetector = new SingleKeyGestureDetector(mContext);
+ mDetector = new SingleKeyGestureDetector();
initSingleKeyGestureRules();
mWaitTimeout = ViewConfiguration.getMultiPressTimeout() + 50;
mLongPressTime = ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout() + 50;
@@ -78,7 +78,7 @@ public class SingleKeyGestureTests {
}
private void initSingleKeyGestureRules() {
- mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(KEYCODE_POWER,
+ mDetector.addRule(new SingleKeyGestureDetector.SingleKeyRule(mContext, KEYCODE_POWER,
KEY_LONGPRESS | KEY_VERYLONGPRESS) {
@Override
int getMaxMultiPressCount() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 0d177c14427b..19f9b758811f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -30,6 +30,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -40,8 +41,10 @@ import static org.mockito.Mockito.timeout;
import android.app.WaitResult;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.ConditionVariable;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -187,6 +190,24 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
verify(taskChangeNotifier, never()).notifyActivityDismissingDockedRootTask();
}
+ /** Ensures that the calling package name passed to client complies with package visibility. */
+ @Test
+ public void testFilteredReferred() {
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setLaunchedFromPackage("other.package").setCreateTask(true).build();
+ assertNotNull(activity.launchedFromPackage);
+ try {
+ mSupervisor.realStartActivityLocked(activity, activity.app, false /* andResume */,
+ false /* checkConfig */);
+ } catch (RemoteException ignored) {
+ }
+ verify(activity).getFilteredReferrer(eq(activity.launchedFromPackage));
+
+ activity.deliverNewIntentLocked(ActivityBuilder.DEFAULT_FAKE_UID,
+ new Intent(), null /* intentGrants */, "other.package2");
+ verify(activity).getFilteredReferrer(eq("other.package2"));
+ }
+
/**
* Ensures that notify focus task changes.
*/
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 12fc2f4ea1f4..51e289f4d833 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1565,6 +1565,12 @@ public class DisplayContentTests extends WindowTestsBase {
mDisplayContent.mFixedRotationTransitionListener.onFinishRecentsAnimation();
assertTrue(displayRotation.updateRotationUnchecked(false));
+ // Rotation can be updated if the policy is not ok to animate (e.g. going to sleep).
+ mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
+ displayRotation.setRotation((displayRotation.getRotation() + 1) % 4);
+ ((TestWindowManagerPolicy) mWm.mPolicy).mOkToAnimate = false;
+ assertTrue(displayRotation.updateRotationUnchecked(false));
+
// Rotation can be updated if the recents animation is animating but it is not on top, e.g.
// switching activities in different orientations by quickstep gesture.
mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recentsActivity);
@@ -2166,6 +2172,21 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
+ public void testKeyguardGoingAwayWhileAodShown() {
+ mDisplayContent.getDisplayPolicy().setAwake(true);
+
+ final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin");
+ final ActivityRecord activity = appWin.mActivityRecord;
+
+ mAtm.mKeyguardController.setKeyguardShown(true /* keyguardShowing */,
+ true /* aodShowing */);
+ assertFalse(activity.isVisibleRequested());
+
+ mAtm.mKeyguardController.keyguardGoingAway(0 /* flags */);
+ assertTrue(activity.isVisibleRequested());
+ }
+
+ @Test
public void testRemoveRootTaskInWindowingModes() {
removeRootTaskTests(() -> mRootWindowContainer.removeRootTasksInWindowingModes(
WINDOWING_MODE_FULLSCREEN));
diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
index f2418c68358d..a8ede13e5de6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java
@@ -82,7 +82,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase {
mWm.mWindowContextListenerController.registerWindowContainerListener(clientToken,
dc.getImeContainer(), 1000 /* ownerUid */, TYPE_INPUT_METHOD_DIALOG,
null /* options */);
- return true;
+ return dc.getImeContainer().getConfiguration();
}).when(wms).attachWindowContextToDisplayArea(any(), eq(TYPE_INPUT_METHOD_DIALOG),
anyInt(), any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index d6a8401f5b18..ab496cf34acc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -797,6 +797,9 @@ public class WindowOrganizerTests extends WindowTestsBase {
public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
}
@Override
+ public void onImeDrawnOnTask(int taskId) throws RemoteException {
+ }
+ @Override
public void onAppSplashScreenViewRemoved(int taskId) {
}
};
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 588089996d6c..611b3f5e62a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -801,6 +801,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
private int mConfigChanges;
private int mLaunchedFromPid;
private int mLaunchedFromUid;
+ private String mLaunchedFromPackage;
private WindowProcessController mWpc;
private Bundle mIntentExtras;
private boolean mOnTop = false;
@@ -911,6 +912,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
return this;
}
+ ActivityBuilder setLaunchedFromPackage(String packageName) {
+ mLaunchedFromPackage = packageName;
+ return this;
+ }
+
ActivityBuilder setUseProcess(WindowProcessController wpc) {
mWpc = wpc;
return this;
@@ -1000,6 +1006,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
final ActivityRecord activity = new ActivityRecord.Builder(mService)
.setLaunchedFromPid(mLaunchedFromPid)
.setLaunchedFromUid(mLaunchedFromUid)
+ .setLaunchedFromPackage(mLaunchedFromPackage)
.setIntent(intent)
.setActivityInfo(aInfo)
.setActivityOptions(options)
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1579aaf9a571..8cb0909def5d 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -1195,7 +1195,8 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser
} else if (mCurrentFunctions == UsbManager.FUNCTION_MIDI) {
titleRes = com.android.internal.R.string.usb_midi_notification_title;
id = SystemMessage.NOTE_USB_MIDI;
- } else if (mCurrentFunctions == UsbManager.FUNCTION_RNDIS) {
+ } else if ((mCurrentFunctions == UsbManager.FUNCTION_RNDIS)
+ || (mCurrentFunctions == UsbManager.FUNCTION_NCM)) {
titleRes = com.android.internal.R.string.usb_tether_notification_title;
id = SystemMessage.NOTE_USB_TETHER;
} else if (mCurrentFunctions == UsbManager.FUNCTION_ACCESSORY) {
diff --git a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
index 7b6ccd31adcc..0c65cc40bd82 100644
--- a/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
+++ b/services/usb/java/com/android/server/usb/UsbUserPermissionManager.java
@@ -45,6 +45,7 @@ import android.service.usb.UsbUidPermissionProto;
import android.service.usb.UsbUserPermissionsManagerProto;
import android.util.ArrayMap;
import android.util.AtomicFile;
+import android.util.EventLog;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.TypedXmlPullParser;
@@ -74,6 +75,8 @@ class UsbUserPermissionManager {
private static final String TAG = UsbUserPermissionManager.class.getSimpleName();
private static final boolean DEBUG = false;
+ private static final int SNET_EVENT_LOG_ID = 0x534e4554;
+
@GuardedBy("mLock")
/** Mapping of USB device name to list of UIDs with permissions for the device
* Each entry lasts until device is disconnected*/
@@ -689,8 +692,11 @@ class UsbUserPermissionManager {
try {
ApplicationInfo aInfo = mContext.getPackageManager().getApplicationInfo(packageName, 0);
if (aInfo.uid != uid) {
- throw new IllegalArgumentException("package " + packageName
+ Slog.w(TAG, "package " + packageName
+ " does not match caller's uid " + uid);
+ EventLog.writeEvent(SNET_EVENT_LOG_ID, "180104273", -1, "");
+ throw new IllegalArgumentException("package " + packageName
+ + " not found");
}
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalArgumentException("package " + packageName + " not found");
diff --git a/services/uwb/java/com/android/server/uwb/UwbInjector.java b/services/uwb/java/com/android/server/uwb/UwbInjector.java
index 64f1da1c8e16..a7a0500483c2 100644
--- a/services/uwb/java/com/android/server/uwb/UwbInjector.java
+++ b/services/uwb/java/com/android/server/uwb/UwbInjector.java
@@ -21,10 +21,13 @@ import static android.content.PermissionChecker.PERMISSION_GRANTED;
import android.annotation.NonNull;
import android.content.AttributionSource;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.PermissionChecker;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.provider.Settings;
+import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
@@ -80,4 +83,23 @@ public class UwbInjector {
mContext, UWB_RANGING, -1, attributionSource, message);
return permissionCheckResult == PERMISSION_GRANTED;
}
+
+ /** Returns true if UWB state saved in Settings is enabled. */
+ public boolean isPersistedUwbStateEnabled() {
+ final ContentResolver cr = mContext.getContentResolver();
+ try {
+ return Settings.Global.getInt(cr, Settings.Global.UWB_ENABLED)
+ == AdapterState.STATE_ENABLED_ACTIVE;
+ } catch (Settings.SettingNotFoundException e) {
+ Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED,
+ AdapterState.STATE_ENABLED_ACTIVE);
+ return true;
+ }
+ }
+
+ /** Returns true if airplane mode is turned on. */
+ public boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
}
diff --git a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
index 4dd26a66cf0e..70d6aab542a2 100644
--- a/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
+++ b/services/uwb/java/com/android/server/uwb/UwbServiceImpl.java
@@ -18,13 +18,19 @@ package com.android.server.uwb;
import android.annotation.NonNull;
import android.content.AttributionSource;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.os.Binder;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
+import android.uwb.AdapterState;
import android.uwb.IUwbAdapter;
import android.uwb.IUwbAdapterStateCallbacks;
import android.uwb.IUwbRangingCallbacks;
@@ -225,13 +231,20 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
mVendorUwbAdapter = null;
}
- private synchronized IUwbAdapter getVendorUwbAdapter() throws IllegalStateException {
+ private synchronized IUwbAdapter getVendorUwbAdapter()
+ throws IllegalStateException, RemoteException {
if (mVendorUwbAdapter != null) return mVendorUwbAdapter;
mVendorUwbAdapter = mUwbInjector.getVendorService();
if (mVendorUwbAdapter == null) {
throw new IllegalStateException("No vendor service found!");
}
Log.i(TAG, "Retrieved vendor service");
+ long token = Binder.clearCallingIdentity();
+ try {
+ mVendorUwbAdapter.setEnabled(isEnabled());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
linkToVendorServiceDeath();
return mVendorUwbAdapter;
}
@@ -239,6 +252,7 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
UwbServiceImpl(@NonNull Context context, @NonNull UwbInjector uwbInjector) {
mContext = context;
mUwbInjector = uwbInjector;
+ registerAirplaneModeReceiver();
}
private void enforceUwbPrivilegedPermission() {
@@ -320,6 +334,34 @@ public class UwbServiceImpl extends IUwbAdapter.Stub implements IBinder.DeathRec
@Override
public synchronized void setEnabled(boolean enabled) throws RemoteException {
- getVendorUwbAdapter().setEnabled(enabled);
+ persistUwbState(enabled);
+ getVendorUwbAdapter().setEnabled(isEnabled());
+ }
+
+ private void persistUwbState(boolean enabled) {
+ final ContentResolver cr = mContext.getContentResolver();
+ int state = enabled ? AdapterState.STATE_ENABLED_ACTIVE : AdapterState.STATE_DISABLED;
+ Settings.Global.putInt(cr, Settings.Global.UWB_ENABLED, state);
+ }
+
+ private void registerAirplaneModeReceiver() {
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ handleAirplaneModeEvent();
+ }
+ }, new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ }
+
+ private void handleAirplaneModeEvent() {
+ try {
+ getVendorUwbAdapter().setEnabled(isEnabled());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to set UWB Adapter state.", e);
+ }
+ }
+
+ private boolean isEnabled() {
+ return mUwbInjector.isPersistedUwbStateEnabled() && !mUwbInjector.isAirplaneModeOn();
}
}
diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp
index 702ddb1b98fe..af0eca955242 100644
--- a/services/voiceinteraction/Android.bp
+++ b/services/voiceinteraction/Android.bp
@@ -18,5 +18,9 @@ java_library_static {
name: "services.voiceinteraction",
defaults: ["platform_service_defaults"],
srcs: [":services.voiceinteraction-sources"],
- libs: ["services.core"],
+ libs: [
+ "services.core",
+ "android.hardware.power-V1-java",
+ "android.hardware.power-V1.0-java",
+ ],
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index a9aeb985d115..4dc83ae98d89 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -269,13 +269,11 @@ final class HotwordDetectionConnection {
Slog.v(TAG, "cancelLocked");
clearDebugHotwordLoggingTimeoutLocked();
mDebugHotwordLogging = false;
- if (mRemoteHotwordDetectionService.isBound()) {
- mRemoteHotwordDetectionService.unbind();
- LocalServices.getService(PermissionManagerServiceInternal.class)
- .setHotwordDetectionServiceProvider(null);
- mIdentity = null;
- updateServiceUidForAudioPolicy(Process.INVALID_UID);
- }
+ mRemoteHotwordDetectionService.unbind();
+ LocalServices.getService(PermissionManagerServiceInternal.class)
+ .setHotwordDetectionServiceProvider(null);
+ mIdentity = null;
+ updateServiceUidForAudioPolicy(Process.INVALID_UID);
mCancellationTaskFuture.cancel(/* may interrupt */ true);
if (mAudioFlinger != null) {
mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index cc021a9acfe2..08e9703124ab 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
@@ -43,11 +43,13 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
+import android.hardware.power.Boost;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PowerManagerInternal;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -62,6 +64,7 @@ import android.view.IWindowManager;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
+import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.am.AssistDataRequester;
import com.android.server.am.AssistDataRequester.AssistDataRequesterCallbacks;
@@ -70,13 +73,20 @@ import com.android.server.uri.UriGrantsManagerInternal;
import com.android.server.wm.ActivityAssistInfo;
import java.io.PrintWriter;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
final class VoiceInteractionSessionConnection implements ServiceConnection,
AssistDataRequesterCallbacks {
- final static String TAG = "VoiceInteractionServiceManager";
+ static final String TAG = "VoiceInteractionServiceManager";
+ static final int POWER_BOOST_TIMEOUT_MS = Integer.parseInt(
+ System.getProperty("vendor.powerhal.interaction.max", "200"));
+ static final int BOOST_TIMEOUT_MS = 300;
+ // TODO: To avoid ap doesn't call hide, only 10 secs for now, need a better way to manage it
+ // in the future.
+ static final int MAX_POWER_BOOST_TIMEOUT = 10_000;
final IBinder mToken = new Binder();
final Object mLock;
@@ -104,6 +114,45 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
ArrayList<IVoiceInteractionSessionShowCallback> mPendingShowCallbacks = new ArrayList<>();
private List<ActivityAssistInfo> mPendingHandleAssistWithoutData = new ArrayList<>();
AssistDataRequester mAssistDataRequester;
+ private final PowerManagerInternal mPowerManagerInternal;
+ private PowerBoostSetter mSetPowerBoostRunnable;
+ private final Handler mFgHandler;
+
+ class PowerBoostSetter implements Runnable {
+
+ private boolean mCanceled;
+ private final Instant mExpiryTime;
+
+ PowerBoostSetter(Instant expiryTime) {
+ mExpiryTime = expiryTime;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mLock) {
+ if (mCanceled) {
+ return;
+ }
+ // To avoid voice interaction service does not call hide to cancel setting
+ // power boost. We will cancel set boost when reaching the max timeout.
+ if (Instant.now().isBefore(mExpiryTime)) {
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, BOOST_TIMEOUT_MS);
+ if (mSetPowerBoostRunnable != null) {
+ mFgHandler.postDelayed(mSetPowerBoostRunnable, POWER_BOOST_TIMEOUT_MS);
+ }
+ } else {
+ Slog.w(TAG, "Reset power boost INTERACTION because reaching max timeout.");
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
+ }
+ }
+ }
+
+ void cancel() {
+ synchronized (mLock) {
+ mCanceled = true;
+ }
+ }
+ }
IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -152,7 +201,9 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mAppOps = context.getSystemService(AppOpsManager.class);
+ mFgHandler = FgThread.getHandler();
mAssistDataRequester = new AssistDataRequester(mContext, mIWindowManager,
(AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE),
this, mLock, OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT);
@@ -255,6 +306,13 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
mPendingHandleAssistWithoutData = topActivities;
}
}
+ // remove if already existing one.
+ if (mSetPowerBoostRunnable != null) {
+ mSetPowerBoostRunnable.cancel();
+ }
+ mSetPowerBoostRunnable = new PowerBoostSetter(
+ Instant.now().plusMillis(MAX_POWER_BOOST_TIMEOUT));
+ mFgHandler.post(mSetPowerBoostRunnable);
mCallback.onSessionShown(this);
return true;
}
@@ -420,6 +478,12 @@ final class VoiceInteractionSessionConnection implements ServiceConnection,
} catch (RemoteException e) {
}
}
+ if (mSetPowerBoostRunnable != null) {
+ mSetPowerBoostRunnable.cancel();
+ mSetPowerBoostRunnable = null;
+ }
+ // A negative value indicates canceling previous boost.
+ mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, /* durationMs */ -1);
mCallback.onSessionHidden(this);
}
if (mFullyBound) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index d06fe45a104c..9fec96b7f549 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -781,6 +781,21 @@ public abstract class Connection extends Conferenceable {
"android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE";
/**
+ * The Telecom call ID of the conference an existing connection should be added to. This is
+ * required when {@link com.android.services.telephony.TelephonyConnectionService} adds a
+ * {@link Conference} to Telecom using the
+ * {@link ConnectionService#addExistingConnection(PhoneAccountHandle, Connection, Conference)}
+ * API. That API specifies a parent conference associated with the new existing connection
+ * being added, and there is no equivalent as part of the {@link RemoteConnectionService} API.
+ * This extra key is used to stack the ID of the conference to which the existing connection
+ * will be added so that Telecom can link it up correctly when the {@link RemoteConference}
+ * is added to Telecom by the connection manager.
+ * @hide
+ */
+ public static final String EXTRA_ADD_TO_CONFERENCE_ID =
+ "android.telecom.extra.ADD_TO_CONFERENCE_ID";
+
+ /**
* Extra key set from a {@link ConnectionService} when using the remote connection APIs
* (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle,
* ConnectionRequest, boolean)}) to create a remote connection. Provides the receiving
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 02bd0010de99..bc0a14667307 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -23,6 +23,8 @@ import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
+import com.android.internal.annotations.GuardedBy;
+
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -115,6 +117,7 @@ public final class Phone {
public static final int SDK_VERSION_R = 30;
// A Map allows us to track each Call by its Telecom-specified call ID
+ @GuardedBy("mLock")
private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
// A List allows us to keep the Calls in a stable iteration order so that casually developed
@@ -154,7 +157,7 @@ public final class Phone {
return;
}
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call == null) {
call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
@@ -191,14 +194,14 @@ public final class Phone {
if (mTargetSdkVersion < SDK_VERSION_R
&& parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
Log.i(this, "removing audio processing call during update for sdk compatibility");
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call != null) {
internalRemoveCall(call);
}
return;
}
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ Call call = getCallById(parcelableCall.getId());
if (call != null) {
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
@@ -215,8 +218,14 @@ public final class Phone {
}
}
+ Call getCallById(String callId) {
+ synchronized (mLock) {
+ return mCallByTelecomCallId.get(callId);
+ }
+ }
+
final void internalSetPostDialWait(String telecomId, String remaining) {
- Call call = mCallByTelecomCallId.get(telecomId);
+ Call call = getCallById(telecomId);
if (call != null) {
call.internalSetPostDialWait(remaining);
}
@@ -230,7 +239,7 @@ public final class Phone {
}
final Call internalGetCallByTelecomId(String telecomId) {
- return mCallByTelecomCallId.get(telecomId);
+ return getCallById(telecomId);
}
final void internalBringToForeground(boolean showDialpad) {
@@ -249,35 +258,35 @@ public final class Phone {
}
final void internalOnConnectionEvent(String telecomId, String event, Bundle extras) {
- Call call = mCallByTelecomCallId.get(telecomId);
+ Call call = getCallById(telecomId);
if (call != null) {
call.internalOnConnectionEvent(event, extras);
}
}
final void internalOnRttUpgradeRequest(String callId, int requestId) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnRttUpgradeRequest(requestId);
}
}
final void internalOnRttInitiationFailure(String callId, int reason) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnRttInitiationFailure(reason);
}
}
final void internalOnHandoverFailed(String callId, int error) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnHandoverFailed(error);
}
}
final void internalOnHandoverComplete(String callId) {
- Call call = mCallByTelecomCallId.get(callId);
+ Call call = getCallById(callId);
if (call != null) {
call.internalOnHandoverComplete();
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index bf6a6ef793ff..efe35d21c003 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -239,13 +239,9 @@ final class RemoteConnectionService {
conference.addConnection(c);
}
}
- if (conference.getConnections().size() == 0) {
- // A conference was created, but none of its connections are ones that have been
- // created by, and therefore being tracked by, this remote connection service. It
- // is of no interest to us.
- Log.d(this, "addConferenceCall - skipping");
- return;
- }
+ // We used to skip adding empty conferences; however in the world of IMS conference
+ // calls we need to add them to the remote connection service because they will always
+ // start with no participants.
conference.setState(parcel.getState());
conference.setConnectionCapabilities(parcel.getConnectionCapabilities());
@@ -379,6 +375,8 @@ final class RemoteConnectionService {
@Override
public void addExistingConnection(String callId, ParcelableConnection connection,
Session.Info sessionInfo) {
+ Log.i(RemoteConnectionService.this, "addExistingConnection: callId=%s, conn=%s", callId,
+ connection);
String callingPackage = mOurConnectionServiceImpl.getApplicationContext().
getOpPackageName();
int callingTargetSdkVersion = mOurConnectionServiceImpl.getApplicationInfo()
@@ -390,6 +388,20 @@ final class RemoteConnectionService {
Bundle newExtras = new Bundle();
newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
connection.getPhoneAccount());
+ if (connection.getParentCallId() != null) {
+ RemoteConference parentConf = mConferenceById.get(connection.getParentCallId());
+ // If there is a parent being set, we need to stash the conference ID here.
+ // Telephony can add an existing connection while specifying a parent conference.
+ // There is no equivalent version of that operation as part of the remote connection
+ // API, so we will stash the pre-defined parent's ID in the extras. When the
+ // connectionmanager copies over the extras from the remote connection to the
+ // actual one, it'll get passed to Telecom so that it can make the association.
+ if (parentConf != null) {
+ newExtras.putString(Connection.EXTRA_ADD_TO_CONFERENCE_ID, parentConf.getId());
+ Log.i(this, "addExistingConnection: stash parent of %s as %s",
+ connection.getParentCallId(), parentConf.getId());
+ }
+ }
remoteConnection.putExtras(newExtras);
mConnectionById.put(callId, remoteConnection);
remoteConnection.registerCallback(new RemoteConnection.Callback() {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index a1d68b215ba5..6e4d33701638 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3494,7 +3494,8 @@ public class TelephonyManager {
*/
private int getLogicalSlotIndex(int physicalSlotIndex) {
UiccSlotInfo[] slotInfos = getUiccSlotsInfo();
- if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length) {
+ if (slotInfos != null && physicalSlotIndex >= 0 && physicalSlotIndex < slotInfos.length
+ && slotInfos[physicalSlotIndex] != null) {
return slotInfos[physicalSlotIndex].getLogicalSlotIdx();
}
@@ -8864,7 +8865,8 @@ public class TelephonyManager {
}
/**
- * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ * Set the preferred network type to global mode which includes NR, LTE, CDMA, EvDo
+ * and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
@@ -8875,7 +8877,8 @@ public class TelephonyManager {
}
/**
- * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA.
+ * Set the preferred network type to global mode which includes NR, LTE, CDMA, EvDo
+ * and GSM/WCDMA.
*
* <p>Requires that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
*
@@ -8883,7 +8886,7 @@ public class TelephonyManager {
* @hide
*/
public boolean setPreferredNetworkTypeToGlobal(int subId) {
- return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+ return setPreferredNetworkType(subId, RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA);
}
/**
@@ -14295,7 +14298,8 @@ public class TelephonyManager {
if (callForwardingInfo.getNumber() == null) {
throw new IllegalArgumentException("callForwarding number is null");
}
- if (callForwardingInfo.getTimeoutSeconds() <= 0) {
+ if (callForwardingReason == CallForwardingInfo.REASON_NO_REPLY
+ && callForwardingInfo.getTimeoutSeconds() <= 0) {
throw new IllegalArgumentException("callForwarding timeout isn't positive");
}
}
diff --git a/tests/Codegen/OWNERS b/tests/Codegen/OWNERS
index da723b3b67da..e69de29bb2d1 100644
--- a/tests/Codegen/OWNERS
+++ b/tests/Codegen/OWNERS
@@ -1 +0,0 @@
-eugenesusla@google.com \ No newline at end of file
diff --git a/tools/aapt2/OWNERS b/tools/aapt2/OWNERS
index 4f655e54a7c2..a96d15ea8233 100644
--- a/tools/aapt2/OWNERS
+++ b/tools/aapt2/OWNERS
@@ -1,4 +1,3 @@
set noparent
-toddke@google.com
zyy@google.com
patb@google.com \ No newline at end of file
diff --git a/tools/codegen/OWNERS b/tools/codegen/OWNERS
index da723b3b67da..e69de29bb2d1 100644
--- a/tools/codegen/OWNERS
+++ b/tools/codegen/OWNERS
@@ -1 +0,0 @@
-eugenesusla@google.com \ No newline at end of file