summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp21
-rw-r--r--apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java2
-rw-r--r--apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java2
-rw-r--r--apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java2
-rw-r--r--apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java2
-rw-r--r--apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java2
-rw-r--r--api/current.txt49
-rw-r--r--api/system-current.txt1
-rw-r--r--api/test-current.txt1
-rw-r--r--cmds/content/src/com/android/commands/content/Content.java2
-rw-r--r--cmds/statsd/src/atoms.proto39
-rw-r--r--cmds/statsd/src/external/StatsPullerManager.cpp9
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.cpp1
-rw-r--r--cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java2
-rw-r--r--core/java/android/app/Activity.java30
-rw-r--r--core/java/android/app/ActivityManager.java20
-rw-r--r--core/java/android/app/ActivityThread.java9
-rw-r--r--core/java/android/app/Application.java32
-rw-r--r--core/java/android/app/ClientTransactionHandler.java5
-rw-r--r--core/java/android/app/IActivityManager.aidl2
-rw-r--r--core/java/android/app/Instrumentation.java34
-rw-r--r--core/java/android/app/Notification.java27
-rw-r--r--core/java/android/app/NotificationChannel.java27
-rw-r--r--core/java/android/app/servertransaction/ClientTransaction.java25
-rw-r--r--core/java/android/app/servertransaction/DestroyActivityItem.java5
-rw-r--r--core/java/android/app/servertransaction/TransactionExecutor.java21
-rw-r--r--core/java/android/content/ContentProviderClient.java17
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java3
-rw-r--r--core/java/android/hardware/face/FaceManager.java38
-rw-r--r--core/java/android/hardware/location/NanoAppFilter.java3
-rw-r--r--core/java/android/hardware/radio/RadioManager.aidl3
-rw-r--r--core/java/android/os/Binder.java14
-rw-r--r--core/java/android/os/FileUtils.java256
-rw-r--r--core/java/android/print/PrintFileDocumentAdapter.java2
-rw-r--r--core/java/android/provider/DocumentsContract.java3
-rw-r--r--core/java/android/provider/Settings.java8
-rw-r--r--core/java/android/service/notification/Adjustment.java6
-rw-r--r--core/java/android/service/notification/NotificationListenerService.java32
-rw-r--r--core/java/android/service/notification/NotificationRankingUpdate.java10
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java18
-rw-r--r--core/java/android/text/TextUtils.java19
-rw-r--r--core/java/android/text/util/Linkify.java136
-rw-r--r--core/java/android/util/MathUtils.java15
-rw-r--r--core/java/android/util/Xml.java46
-rw-r--r--core/java/android/view/RenderNode.java25
-rw-r--r--core/java/android/view/View.java5
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java71
-rw-r--r--core/java/android/view/autofill/AutofillManager.java159
-rw-r--r--core/java/android/view/autofill/IAutoFillManager.aidl50
-rw-r--r--core/java/com/android/internal/app/procstats/AssociationState.java233
-rw-r--r--core/java/com/android/internal/app/procstats/ProcessState.java64
-rw-r--r--core/java/com/android/internal/app/procstats/ProcessStats.java124
-rw-r--r--core/java/com/android/internal/app/procstats/ServiceState.java2
-rw-r--r--core/java/com/android/internal/os/BinderCallsStats.java64
-rw-r--r--core/java/com/android/internal/util/ContrastColorUtil.java36
-rw-r--r--core/jni/Android.bp3
-rw-r--r--core/jni/android/graphics/PaintFilter.cpp (renamed from core/jni/android/graphics/DrawFilter.cpp)59
-rw-r--r--core/jni/android_graphics_Canvas.cpp9
-rw-r--r--core/jni/android_media_MediaMetricsJNI.h1
-rw-r--r--core/jni/android_text_MeasuredParagraph.cpp1
-rw-r--r--core/jni/android_text_StaticLayout.cpp1
-rw-r--r--core/jni/android_view_RenderNode.cpp6
-rw-r--r--core/res/AndroidManifest.xml3
-rw-r--r--core/res/res/drawable-hdpi/ic_grayedout_printer.pngbin3418 -> 0 bytes
-rw-r--r--core/res/res/drawable-mdpi/ic_grayedout_printer.pngbin3418 -> 0 bytes
-rw-r--r--core/res/res/drawable-xhdpi/ic_grayedout_printer.pngbin3418 -> 0 bytes
-rw-r--r--core/res/res/values-land/dimens_package_installer.xml22
-rw-r--r--core/res/res/values-mcc334-mnc03/config.xml (renamed from packages/PrintSpooler/res/drawable/print_warning.xml)15
-rw-r--r--core/res/res/values-mcc334-mnc030/config.xml20
-rw-r--r--core/res/res/values-mcc704-mnc03/config.xml20
-rw-r--r--core/res/res/values-mcc706-mnc04/config.xml20
-rw-r--r--core/res/res/values-mcc712-mnc04/config.xml20
-rw-r--r--core/res/res/values-mcc716-mnc06/config.xml20
-rw-r--r--core/res/res/values-mcc716-mnc10/config.xml20
-rw-r--r--core/res/res/values-mcc716-mnc17/config.xml20
-rw-r--r--core/res/res/values-mcc722-mnc07/config.xml20
-rw-r--r--core/res/res/values-mcc732-mnc123/config.xml20
-rw-r--r--core/res/res/values-mcc740-mnc00/config.xml20
-rw-r--r--core/res/res/values-night/colors.xml31
-rw-r--r--core/res/res/values-night/values.xml5
-rw-r--r--core/res/res/values-port/dimens_package_installer.xml22
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/styles_package_installer.xml97
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/res/res/values/themes_package_installer.xml35
-rw-r--r--core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java12
-rw-r--r--core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java10
-rw-r--r--core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java40
-rw-r--r--core/tests/coretests/src/android/os/FileUtilsTest.java2
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java3
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java7
-rw-r--r--core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java56
-rw-r--r--data/sounds/AllAudio.mk5
-rw-r--r--data/sounds/AudioPackage14.mk32
-rw-r--r--data/sounds/README.txt10
-rw-r--r--data/sounds/effects/ogg/NFCFailure.oggbin0 -> 31870 bytes
-rw-r--r--data/sounds/effects/ogg/NFCInitiated.oggbin0 -> 25194 bytes
-rw-r--r--data/sounds/effects/ogg/NFCSuccess.oggbin0 -> 27201 bytes
-rw-r--r--data/sounds/effects/ogg/NFCTransferComplete.oggbin0 -> 33297 bytes
-rw-r--r--data/sounds/effects/ogg/NFCTransferInitiated.oggbin0 -> 28204 bytes
-rw-r--r--libs/hwui/Android.bp6
-rw-r--r--libs/hwui/CanvasTransform.cpp113
-rw-r--r--libs/hwui/CanvasTransform.h40
-rw-r--r--libs/hwui/Properties.cpp3
-rw-r--r--libs/hwui/Properties.h3
-rw-r--r--libs/hwui/RenderNode.h11
-rw-r--r--libs/hwui/RenderProperties.cpp6
-rw-r--r--libs/hwui/RenderProperties.h7
-rw-r--r--libs/hwui/SkiaCanvas.cpp111
-rw-r--r--libs/hwui/SkiaCanvas.h60
-rw-r--r--libs/hwui/VectorDrawable.h40
-rw-r--r--libs/hwui/hwui/Canvas.cpp8
-rw-r--r--libs/hwui/hwui/Canvas.h5
-rw-r--r--libs/hwui/hwui/PaintFilter.h19
-rw-r--r--libs/hwui/pipeline/skia/RenderNodeDrawable.cpp4
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp73
-rw-r--r--libs/hwui/pipeline/skia/SkiaRecordingCanvas.h40
-rw-r--r--libs/hwui/tests/macrobench/main.cpp2
-rw-r--r--media/java/android/media/MediaPlayer2.java9
-rw-r--r--media/java/android/media/MediaPlayer2Impl.java47
-rw-r--r--media/java/android/media/tv/TvInputManager.java4
-rw-r--r--media/jni/android_media_Media2DataSource.cpp16
-rw-r--r--media/jni/android_media_Media2HTTPConnection.cpp18
-rw-r--r--media/jni/android_media_Media2HTTPService.cpp8
-rw-r--r--media/jni/android_media_MediaPlayer2.cpp14
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java49
-rw-r--r--media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java7
-rw-r--r--packages/CarSystemUI/Android.mk86
-rw-r--r--packages/CarSystemUI/AndroidManifest.xml24
-rw-r--r--packages/CarSystemUI/proguard.flags1
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_apps.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_apps_black.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml20
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_music.xml34
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_music_black.xml34
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_music_selected.xml20
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_navigation.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml21
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_notification.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_notification_black.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml21
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_overview.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_overview_black.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml20
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_phone.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_phone_black.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml20
-rw-r--r--packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml28
-rw-r--r--packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml37
-rw-r--r--packages/CarSystemUI/res/drawable/nav_button_background.xml26
-rw-r--r--packages/CarSystemUI/res/drawable/notification_material_bg.xml26
-rw-r--r--packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml26
-rw-r--r--packages/CarSystemUI/res/drawable/volume_dialog_background.xml26
-rw-r--r--packages/CarSystemUI/res/layout/car_navigation_bar.xml129
-rw-r--r--packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml44
-rw-r--r--packages/CarSystemUI/res/layout/car_status_bar_header.xml29
-rw-r--r--packages/CarSystemUI/res/layout/car_top_navigation_bar.xml151
-rw-r--r--packages/CarSystemUI/res/layout/car_volume_dialog.xml30
-rw-r--r--packages/CarSystemUI/res/layout/status_bar_wifi_group.xml85
-rw-r--r--packages/CarSystemUI/res/layout/super_status_bar.xml91
-rw-r--r--packages/CarSystemUI/res/layout/system_icons.xml40
-rw-r--r--packages/CarSystemUI/res/values-night/colors.xml24
-rw-r--r--packages/CarSystemUI/res/values/attrs.xml35
-rw-r--r--packages/CarSystemUI/res/values/colors.xml54
-rw-r--r--packages/CarSystemUI/res/values/config.xml31
-rw-r--r--packages/CarSystemUI/res/values/dimens.xml62
-rw-r--r--packages/CarSystemUI/res/values/integers.xml20
-rw-r--r--packages/CarSystemUI/res/values/strings.xml23
-rw-r--r--packages/CarSystemUI/res/values/styles.xml54
-rw-r--r--packages/CarSystemUI/res/xml/car_volume_items.xml55
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java51
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java276
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java338
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java202
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java164
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/Assistant.java27
-rw-r--r--packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java202
-rw-r--r--packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.pngbin632 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.pngbin656 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.pngbin3418 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-hdpi/ic_restart.pngbin3138 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.pngbin790 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.pngbin532 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.pngbin217 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.pngbin536 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.pngbin221 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.pngbin3418 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/ic_restart.pngbin3033 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.pngbin683 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.pngbin747 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.pngbin778 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.pngbin3418 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xhdpi/ic_restart.pngbin3219 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.pngbin986 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.pngbin1060 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.pngbin1068 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.pngbin1752 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.pngbin1859 -> 0 bytes
-rw-r--r--packages/PrintSpooler/res/drawable/ic_clear.xml26
-rw-r--r--packages/PrintSpooler/res/drawable/ic_expand_less.xml67
-rw-r--r--packages/PrintSpooler/res/drawable/ic_expand_more.xml67
-rw-r--r--packages/PrintSpooler/res/layout/preview_page.xml2
-rw-r--r--packages/PrintSpooler/res/layout/preview_page_error.xml13
-rw-r--r--packages/PrintSpooler/res/layout/preview_page_loading.xml15
-rw-r--r--packages/PrintSpooler/res/layout/preview_page_selected.xml2
-rw-r--r--packages/PrintSpooler/res/layout/print_activity.xml5
-rw-r--r--packages/PrintSpooler/res/layout/print_activity_controls.xml2
-rw-r--r--packages/PrintSpooler/res/layout/print_error_fragment.xml36
-rw-r--r--packages/PrintSpooler/res/layout/print_progress_fragment.xml41
-rw-r--r--packages/PrintSpooler/res/layout/select_printer_activity.xml45
-rw-r--r--packages/PrintSpooler/res/values/colors.xml2
-rw-r--r--packages/PrintSpooler/res/values/themes.xml7
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java6
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java4
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml4
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml2
-rw-r--r--packages/SettingsLib/res/values-ta/arrays.xml40
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java129
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java136
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java13
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java7
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java265
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java436
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java111
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java146
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java81
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java143
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java54
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java111
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java72
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java77
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java66
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java271
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java108
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java127
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java74
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java100
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java10
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java33
-rw-r--r--packages/SystemUI/res/color/notification_guts_buttons.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_snooze.xml2
-rw-r--r--packages/SystemUI/res/values-night/colors.xml44
-rw-r--r--packages/SystemUI/res/values-night/dimens.xml21
-rw-r--r--packages/SystemUI/res/values/colors.xml3
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/styles.xml5
-rw-r--r--packages/SystemUI/src/com/android/systemui/OverviewProxyService.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java151
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java97
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java180
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java14
-rw-r--r--proto/src/metrics_constants.proto8
-rw-r--r--sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java125
-rw-r--r--services/Android.bp1
-rw-r--r--services/autofill/java/com/android/server/autofill/AutofillManagerService.java131
-rw-r--r--services/core/java/com/android/server/LocationManagerService.java18
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java152
-rw-r--r--services/core/java/com/android/server/am/AppTaskImpl.java2
-rw-r--r--services/core/java/com/android/server/am/ConnectionRecord.java19
-rw-r--r--services/core/java/com/android/server/am/ContentProviderConnection.java19
-rw-r--r--services/core/java/com/android/server/am/ContentProviderRecord.java83
-rw-r--r--services/core/java/com/android/server/am/OomAdjProfiler.java185
-rw-r--r--services/core/java/com/android/server/am/ProcessMemInfo.java1
-rw-r--r--services/core/java/com/android/server/am/ProcessStatsService.java6
-rw-r--r--services/core/java/com/android/server/am/ServiceRecord.java18
-rw-r--r--services/core/java/com/android/server/am/TEST_MAPPING70
-rw-r--r--services/core/java/com/android/server/content/SyncJobService.java171
-rw-r--r--services/core/java/com/android/server/content/SyncManager.java229
-rw-r--r--services/core/java/com/android/server/content/SyncStorageEngine.java8
-rw-r--r--services/core/java/com/android/server/display/AutomaticBrightnessController.java1
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java5
-rw-r--r--services/core/java/com/android/server/hdmi/Constants.java2
-rw-r--r--services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java6
-rwxr-xr-xservices/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java1
-rwxr-xr-xservices/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java44
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java133
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessage.java23
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java82
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiLogger.java5
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiUtils.java2
-rw-r--r--services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java4
-rw-r--r--services/core/java/com/android/server/hdmi/RequestArcAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioAction.java2
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java2
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java2
-rw-r--r--services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java2
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java2
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java129
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java12
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java1373
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java1305
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java19
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java54
-rw-r--r--services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java19
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java63
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java6
-rw-r--r--services/core/java/com/android/server/stats/StatsCompanionService.java27
-rw-r--r--services/core/java/com/android/server/wm/SurfaceAnimationRunner.java13
-rw-r--r--services/core/java/com/android/server/wm/TEST_MAPPING42
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java10
-rw-r--r--services/core/java/com/android/server/wm/utils/InsetUtils.java29
-rw-r--r--services/print/java/com/android/server/print/UserState.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java121
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java40
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java28
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java14
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java32
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java105
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java22
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java1742
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java1602
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java3
-rw-r--r--telecomm/java/android/telecom/Connection.java8
-rw-r--r--telephony/java/android/telephony/CellIdentity.java7
-rw-r--r--telephony/java/android/telephony/CellIdentityTdscdma.java56
-rw-r--r--telephony/java/android/telephony/CellIdentityWcdma.java6
-rw-r--r--telephony/java/android/telephony/CellInfo.java4
-rw-r--r--telephony/java/android/telephony/CellInfoCdma.java2
-rw-r--r--telephony/java/android/telephony/CellInfoGsm.java2
-rw-r--r--telephony/java/android/telephony/CellInfoLte.java2
-rw-r--r--telephony/java/android/telephony/CellInfoTdscdma.java151
-rw-r--r--telephony/java/android/telephony/CellInfoWcdma.java6
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthCdma.java5
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthGsm.java5
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthLte.java5
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthTdscdma.java228
-rw-r--r--telephony/java/android/telephony/CellSignalStrengthWcdma.java34
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationState.java30
-rw-r--r--telephony/java/android/telephony/ServiceState.java5
-rw-r--r--telephony/java/android/telephony/SubscriptionInfo.java56
-rw-r--r--telephony/java/android/telephony/SubscriptionManager.java14
-rw-r--r--test-base/Android.bp3
-rw-r--r--test-mock/Android.bp3
-rw-r--r--test-runner/Android.bp2
-rw-r--r--vr/Android.bp1
362 files changed, 14507 insertions, 4706 deletions
diff --git a/Android.bp b/Android.bp
index db0f0eae6658..694bf26d6fba 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,7 @@
java_library {
name: "framework",
+ installable: true,
srcs: [
// From build/make/core/pathmap.mk FRAMEWORK_BASE_SUBDIRS
@@ -706,6 +707,7 @@ java_library {
// specified on the build command line.
java_library {
name: "framework-oahl-backward-compatibility",
+ installable: true,
srcs: [
"core/java/android/content/pm/OrgApacheHttpLegacyUpdater.java",
],
@@ -761,6 +763,7 @@ gensrcs {
// ============================================================
java_library {
name: "ext",
+ installable: true,
no_framework_libs: true,
static_libs: [
"libphonenumber-platform",
@@ -1192,6 +1195,24 @@ droiddoc {
" -showAnnotation android.annotation.TestApi",
}
+droiddoc {
+ name: "hiddenapi-mappings",
+ defaults: ["framework-docs-default"],
+ arg_files: [
+ "core/res/AndroidManifest.xml",
+ ":api-version-xml",
+ "core/java/overview.html",
+ ":current-support-api",
+ ],
+ dex_mapping_filename: "dex-mapping.txt",
+ args: framework_docs_args +
+ " -referenceonly" +
+ " -nodocs" +
+ " -showUnannotated" +
+ " -showAnnotation android.annotation.SystemApi" +
+ " -showAnnotation android.annotation.TestApi",
+}
+
filegroup {
name: "apache-http-stubs-sources",
srcs: [
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
index 586c3852325a..64f2800ee112 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutCreateDrawPerfTest.java
@@ -46,7 +46,7 @@ public class BoringLayoutCreateDrawPerfTest {
private static final float SPACING_ADD = 10f;
private static final float SPACING_MULT = 1.5f;
- @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
index 9d11f29557d2..194a88c36bba 100644
--- a/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/BoringLayoutIsBoringPerfTest.java
@@ -40,7 +40,7 @@ public class BoringLayoutIsBoringPerfTest {
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={4},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {4} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
index 676879857491..ad5a34e44997 100644
--- a/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/PaintMeasureDrawPerfTest.java
@@ -42,7 +42,7 @@ public class PaintMeasureDrawPerfTest {
private static final boolean[] BOOLEANS = new boolean[]{false, true};
- @Parameterized.Parameters(name = "cached={1},{0}chars")
+ @Parameterized.Parameters(name = "cached {1} {0}chars")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
index bfdb7589bdff..deb2b0a74aaa 100644
--- a/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/StaticLayoutCreateDrawPerfTest.java
@@ -50,7 +50,7 @@ public class StaticLayoutCreateDrawPerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
index ff2d57edb11b..c2898fa05c81 100644
--- a/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/TextViewSetTextMeasurePerfTest.java
@@ -51,7 +51,7 @@ public class TextViewSetTextMeasurePerfTest {
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "cached={3},{1}chars,{0}")
+ @Parameterized.Parameters(name = "cached {3} {1}chars {0}")
public static Collection cases() {
final List<Object[]> params = new ArrayList<>();
for (int length : new int[]{128}) {
diff --git a/api/current.txt b/api/current.txt
index bad0e79cbc54..e471086488fe 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9137,6 +9137,7 @@ package android.content {
method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
method public void close();
+ method public static void closeQuietly(android.content.ContentProviderClient);
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
method public android.content.ContentProvider getLocalContentProvider();
method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -32547,6 +32548,21 @@ package android.os {
ctor public FileUriExposedException(java.lang.String);
}
+ public class FileUtils {
+ method public static void closeQuietly(java.lang.AutoCloseable);
+ method public static void closeQuietly(java.io.FileDescriptor);
+ method public static long copy(java.io.File, java.io.File) throws java.io.IOException;
+ method public static long copy(java.io.File, java.io.File, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+ method public static long copy(java.io.InputStream, java.io.OutputStream) throws java.io.IOException;
+ method public static long copy(java.io.InputStream, java.io.OutputStream, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+ method public static long copy(java.io.FileDescriptor, java.io.FileDescriptor) throws java.io.IOException;
+ method public static long copy(java.io.FileDescriptor, java.io.FileDescriptor, android.os.CancellationSignal, java.util.concurrent.Executor, android.os.FileUtils.ProgressListener) throws java.io.IOException;
+ }
+
+ public static abstract interface FileUtils.ProgressListener {
+ method public abstract void onProgress(long);
+ }
+
public class Handler {
ctor public Handler();
ctor public Handler(android.os.Handler.Callback);
@@ -42414,8 +42430,10 @@ package android.telephony {
method public java.lang.CharSequence getDisplayName();
method public java.lang.String getIccId();
method public int getIconTint();
- method public int getMcc();
- method public int getMnc();
+ method public deprecated int getMcc();
+ method public java.lang.String getMccString();
+ method public deprecated int getMnc();
+ method public java.lang.String getMncString();
method public java.lang.String getNumber();
method public int getSimSlotIndex();
method public int getSubscriptionId();
@@ -42508,13 +42526,11 @@ package android.telephony {
method public java.lang.String getIccAuthentication(int, int, java.lang.String);
method public java.lang.String getImei();
method public java.lang.String getImei(int);
- method public java.lang.String getTypeAllocationCode();
- method public java.lang.String getTypeAllocationCode(int);
method public java.lang.String getLine1Number();
- method public java.lang.String getMeid();
- method public java.lang.String getMeid(int);
method public java.lang.String getManufacturerCode();
method public java.lang.String getManufacturerCode(int);
+ method public java.lang.String getMeid();
+ method public java.lang.String getMeid(int);
method public java.lang.String getMmsUAProfUrl();
method public java.lang.String getMmsUserAgent();
method public java.lang.String getNai();
@@ -42537,6 +42553,8 @@ package android.telephony {
method public int getSimState();
method public int getSimState(int);
method public java.lang.String getSubscriberId();
+ method public java.lang.String getTypeAllocationCode();
+ method public java.lang.String getTypeAllocationCode(int);
method public java.lang.String getVisualVoicemailPackageName();
method public java.lang.String getVoiceMailAlphaTag();
method public java.lang.String getVoiceMailNumber();
@@ -44749,6 +44767,7 @@ package android.text.util {
public class Linkify {
ctor public Linkify();
method public static final boolean addLinks(android.text.Spannable, int);
+ method public static final boolean addLinks(android.text.Spannable, int, android.text.util.Linkify.UrlSpanFactory);
method public static final boolean addLinks(android.widget.TextView, int);
method public static final void addLinks(android.widget.TextView, java.util.regex.Pattern, java.lang.String);
method public static final void addLinks(android.widget.TextView, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
@@ -44756,6 +44775,7 @@ package android.text.util {
method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String);
method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter);
+ method public static final boolean addLinks(android.text.Spannable, java.util.regex.Pattern, java.lang.String, java.lang.String[], android.text.util.Linkify.MatchFilter, android.text.util.Linkify.TransformFilter, android.text.util.Linkify.UrlSpanFactory);
field public static final int ALL = 15; // 0xf
field public static final int EMAIL_ADDRESSES = 2; // 0x2
field public static final deprecated int MAP_ADDRESSES = 8; // 0x8
@@ -44774,6 +44794,11 @@ package android.text.util {
method public abstract java.lang.String transformUrl(java.util.regex.Matcher, java.lang.String);
}
+ public static class Linkify.UrlSpanFactory {
+ ctor public Linkify.UrlSpanFactory();
+ method public android.text.style.URLSpan create(java.lang.String);
+ }
+
public class Rfc822Token {
ctor public Rfc822Token(java.lang.String, java.lang.String, java.lang.String);
method public java.lang.String getAddress();
@@ -49395,18 +49420,18 @@ package android.view.accessibility {
method public void setCheckable(boolean);
method public void setChecked(boolean);
method public void setClassName(java.lang.CharSequence);
- method public void setClickable(boolean);
+ method public deprecated void setClickable(boolean);
method public void setCollectionInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionInfo);
method public void setCollectionItemInfo(android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo);
method public void setContentDescription(java.lang.CharSequence);
method public void setContentInvalid(boolean);
- method public void setContextClickable(boolean);
- method public void setDismissable(boolean);
+ method public deprecated void setContextClickable(boolean);
+ method public deprecated void setDismissable(boolean);
method public void setDrawingOrder(int);
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(java.lang.CharSequence);
- method public void setFocusable(boolean);
+ method public deprecated void setFocusable(boolean);
method public void setFocused(boolean);
method public void setHeading(boolean);
method public void setHintText(java.lang.CharSequence);
@@ -49417,7 +49442,7 @@ package android.view.accessibility {
method public void setLabeledBy(android.view.View);
method public void setLabeledBy(android.view.View, int);
method public void setLiveRegion(int);
- method public void setLongClickable(boolean);
+ method public deprecated void setLongClickable(boolean);
method public void setMaxTextLength(int);
method public void setMovementGranularities(int);
method public void setMultiLine(boolean);
@@ -49428,7 +49453,7 @@ package android.view.accessibility {
method public void setPassword(boolean);
method public void setRangeInfo(android.view.accessibility.AccessibilityNodeInfo.RangeInfo);
method public void setScreenReaderFocusable(boolean);
- method public void setScrollable(boolean);
+ method public deprecated void setScrollable(boolean);
method public void setSelected(boolean);
method public void setShowingHintText(boolean);
method public void setSource(android.view.View);
diff --git a/api/system-current.txt b/api/system-current.txt
index 049a9d2bf9ed..7c0d95859275 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4664,6 +4664,7 @@ package android.service.notification {
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final java.lang.String KEY_PEOPLE = "key_people";
+ field public static final java.lang.String KEY_SMART_ACTIONS = "key_smart_actions";
field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index e5061ed32a9e..b8acfdb7a7e4 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1043,6 +1043,7 @@ package android.service.notification {
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.service.notification.Adjustment> CREATOR;
field public static final java.lang.String KEY_PEOPLE = "key_people";
+ field public static final java.lang.String KEY_SMART_ACTIONS = "key_smart_actions";
field public static final java.lang.String KEY_SNOOZE_CRITERIA = "key_snooze_criteria";
field public static final java.lang.String KEY_USER_SENTIMENT = "key_user_sentiment";
}
diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java
index 6e0bd3a81d84..36e51b9703c9 100644
--- a/cmds/content/src/com/android/commands/content/Content.java
+++ b/cmds/content/src/com/android/commands/content/Content.java
@@ -462,7 +462,7 @@ public class Content {
IBinder token = new Binder();
try {
ContentProviderHolder holder = activityManager.getContentProviderExternal(
- providerName, mUserId, token);
+ providerName, mUserId, token, "*cmd*");
if (holder == null) {
throw new IllegalStateException("Could not find provider: " + providerName);
}
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index cdb72ab7f174..e23676f96151 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -126,7 +126,7 @@ message Atom {
}
// Pulled events will start at field 10000.
- // Next: 10022
+ // Next: 10023
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -150,6 +150,7 @@ message Atom {
RemainingBatteryCapacity remaining_battery_capacity = 10019;
FullBatteryCapacity full_battery_capacity = 10020;
Temperature temperature = 10021;
+ BinderCalls binder_calls = 10022;
}
// DO NOT USE field numbers above 100,000 in AOSP. Field numbers above
@@ -1975,3 +1976,39 @@ message Temperature {
// Temperature in tenths of a degree C.
optional int32 temperature_dC = 3;
}
+
+/**
+ * Pulls the statistics of calls to Binder.
+ *
+ * Binder stats are cumulative from boot unless somebody reset the data using
+ * > adb shell dumpsys binder_calls_stats --reset
+ */
+message BinderCalls {
+ // TODO(gaillard): figure out if binder call stats includes data from isolated uids, if a uid
+ // gets recycled and we have isolated uids, we might attribute the data incorrectly.
+ // TODO(gaillard): there is a high dimensions cardinality, figure out if we should drop the less
+ // commonly used APIs.
+ optional int32 uid = 1 [(is_uid) = true];
+ // Fully qualified class name of the API call.
+ optional string service_class_name = 2;
+ // Method name of the API call. It can also be a transaction code if we cannot resolve it to a
+ // name. See Binder#getTransactionName.
+ optional string service_method_name = 3;
+ // Total number of API calls.
+ optional int64 call_count = 4;
+ // Number of exceptions thrown by the API.
+ optional int64 exception_count = 5;
+ // Total latency of all API calls.
+ // Average can be computed using total_latency_micros / call_count.
+ optional int64 total_latency_micros = 6;
+ // Maximum latency of one API call.
+ optional int64 max_latency_micros = 7;
+ // Total CPU usage of all API calls.
+ optional int64 total_cpu_micros = 8;
+ // Maximum CPU usage of one API call.
+ optional int64 max_cpu_micros = 9;
+ // Maximum parcel reply size of one API call.
+ optional int64 max_reply_size_bytes = 10;
+ // Maximum parcel request size of one API call.
+ optional int64 max_request_size_bytes = 11;
+}
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 06edff9e498c..160d6e86c55f 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -171,7 +171,14 @@ const std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
1 * NS_PER_SEC,
new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
// temperature
- {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}}};
+ {android::util::TEMPERATURE, {{}, {}, 1, new ResourceThermalManagerPuller()}},
+ // binder_calls
+ {android::util::BINDER_CALLS,
+ {{4, 5, 6, 8},
+ {2, 3, 7, 9, 10, 11},
+ 1 * NS_PER_SEC,
+ new StatsCompanionServicePuller(android::util::BINDER_CALLS)}}
+ };
StatsPullerManager::StatsPullerManager() : mNextPullTimeNs(NO_ALARM_UPDATE) {
}
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index 038cb954f578..a955511e198c 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -100,6 +100,7 @@ const int FIELD_ID_UID_MAP_DROPPED_CHANGES = 3;
const int FIELD_ID_UID_MAP_DELETED_APPS = 4;
const std::map<int, std::pair<size_t, size_t>> StatsdStats::kAtomDimensionKeySizeLimitMap = {
+ {android::util::BINDER_CALLS, {6000, 10000}},
{android::util::CPU_TIME_PER_UID_FREQ, {6000, 10000}},
};
diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
index 653851546d01..950a258d123d 100644
--- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
+++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java
@@ -62,7 +62,7 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge {
IBinder token = new Binder();
try {
ContentProviderHolder holder = activityManager.getContentProviderExternal(
- providerName, UserHandle.USER_SYSTEM, token);
+ providerName, UserHandle.USER_SYSTEM, token, "*uiautomator*");
if (holder == null) {
throw new IllegalStateException("Could not find provider: " + providerName);
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6bac52d58c1d..6638dd94c6e4 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1086,7 +1086,7 @@ public class Activity extends ContextThemeWrapper
*
* @param savedInstanceState contains the saved state
*/
- final void performRestoreInstanceState(Bundle savedInstanceState) {
+ final void performRestoreInstanceState(@NonNull Bundle savedInstanceState) {
onRestoreInstanceState(savedInstanceState);
restoreManagedDialogs(savedInstanceState);
}
@@ -1100,8 +1100,8 @@ public class Activity extends ContextThemeWrapper
* @param savedInstanceState contains the saved state
* @param persistentState contains the persistable saved state
*/
- final void performRestoreInstanceState(Bundle savedInstanceState,
- PersistableBundle persistentState) {
+ final void performRestoreInstanceState(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
onRestoreInstanceState(savedInstanceState, persistentState);
if (savedInstanceState != null) {
restoreManagedDialogs(savedInstanceState);
@@ -1128,7 +1128,7 @@ public class Activity extends ContextThemeWrapper
* @see #onResume
* @see #onSaveInstanceState
*/
- protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
if (mWindow != null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
@@ -1149,8 +1149,12 @@ public class Activity extends ContextThemeWrapper
*
* <p>If this method is called {@link #onRestoreInstanceState(Bundle)} will not be called.
*
- * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
- * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}.
+ * <p>At least one of {@code savedInstanceState} or {@code persistentState} will not be null.
+ *
+ * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}
+ * or null.
+ * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}
+ * or null.
*
* @see #onRestoreInstanceState(Bundle)
* @see #onCreate
@@ -1158,8 +1162,8 @@ public class Activity extends ContextThemeWrapper
* @see #onResume
* @see #onSaveInstanceState
*/
- public void onRestoreInstanceState(Bundle savedInstanceState,
- PersistableBundle persistentState) {
+ public void onRestoreInstanceState(@Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
if (savedInstanceState != null) {
onRestoreInstanceState(savedInstanceState);
}
@@ -1545,7 +1549,7 @@ public class Activity extends ContextThemeWrapper
*
* @param outState The bundle to save the state to.
*/
- final void performSaveInstanceState(Bundle outState) {
+ final void performSaveInstanceState(@NonNull Bundle outState) {
onSaveInstanceState(outState);
saveManagedDialogs(outState);
mActivityTransitionState.saveState(outState);
@@ -1562,7 +1566,8 @@ public class Activity extends ContextThemeWrapper
* @param outState The bundle to save the state to.
* @param outPersistentState The bundle to save persistent state to.
*/
- final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ final void performSaveInstanceState(@NonNull Bundle outState,
+ @NonNull PersistableBundle outPersistentState) {
onSaveInstanceState(outState, outPersistentState);
saveManagedDialogs(outState);
storeHasCurrentPermissionRequest(outState);
@@ -1618,7 +1623,7 @@ public class Activity extends ContextThemeWrapper
* @see #onRestoreInstanceState
* @see #onPause
*/
- protected void onSaveInstanceState(Bundle outState) {
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
@@ -1648,7 +1653,8 @@ public class Activity extends ContextThemeWrapper
* @see #onRestoreInstanceState(Bundle, PersistableBundle)
* @see #onPause
*/
- public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ public void onSaveInstanceState(@NonNull Bundle outState,
+ @NonNull PersistableBundle outPersistentState) {
onSaveInstanceState(outState);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3f579bc442b7..f27b286f9efd 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3080,32 +3080,16 @@ public class ActivityManager {
*/
public int processState;
- /**
- * Whether the app is focused in multi-window environment.
- * @hide
- */
- public boolean isFocused;
-
- /**
- * Copy of {@link com.android.server.am.ProcessRecord#lastActivityTime} of the process.
- * @hide
- */
- public long lastActivityTime;
-
public RunningAppProcessInfo() {
importance = IMPORTANCE_FOREGROUND;
importanceReasonCode = REASON_UNKNOWN;
processState = PROCESS_STATE_IMPORTANT_FOREGROUND;
- isFocused = false;
- lastActivityTime = 0;
}
public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
processName = pProcessName;
pid = pPid;
pkgList = pArr;
- isFocused = false;
- lastActivityTime = 0;
}
public int describeContents() {
@@ -3126,8 +3110,6 @@ public class ActivityManager {
ComponentName.writeToParcel(importanceReasonComponent, dest);
dest.writeInt(importanceReasonImportance);
dest.writeInt(processState);
- dest.writeInt(isFocused ? 1 : 0);
- dest.writeLong(lastActivityTime);
}
public void readFromParcel(Parcel source) {
@@ -3144,8 +3126,6 @@ public class ActivityManager {
importanceReasonComponent = ComponentName.readFromParcel(source);
importanceReasonImportance = source.readInt();
processState = source.readInt();
- isFocused = source.readInt() != 0;
- lastActivityTime = source.readLong();
}
public static final Creator<RunningAppProcessInfo> CREATOR =
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f3c67310c05d..2daa5779bde7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -35,6 +35,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.PendingTransactionActions.StopInfo;
import android.app.servertransaction.TransactionExecutor;
@@ -176,6 +177,7 @@ import java.net.InetAddress;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -257,6 +259,8 @@ public final class ActivityThread extends ClientTransactionHandler {
final H mH = new H();
final Executor mExecutor = new HandlerExecutor(mH);
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
+ final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
+ Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
// List of new activities (via ActivityRecord.nextIdle) that should
// be reported when next we idle.
ActivityClientRecord mNewActivities = null;
@@ -4474,6 +4478,11 @@ public final class ActivityThread extends ClientTransactionHandler {
}
@Override
+ public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+ return mActivitiesToBeDestroyed;
+ }
+
+ @Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason) {
ActivityClientRecord r = performDestroyActivity(token, finishing,
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 4531f53bd86f..6a58d9b0de36 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -17,6 +17,8 @@
package android.app;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
@@ -58,13 +60,13 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
public LoadedApk mLoadedApk;
public interface ActivityLifecycleCallbacks {
- void onActivityCreated(Activity activity, Bundle savedInstanceState);
- void onActivityStarted(Activity activity);
- void onActivityResumed(Activity activity);
- void onActivityPaused(Activity activity);
- void onActivityStopped(Activity activity);
- void onActivitySaveInstanceState(Activity activity, Bundle outState);
- void onActivityDestroyed(Activity activity);
+ void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
+ void onActivityStarted(@NonNull Activity activity);
+ void onActivityResumed(@NonNull Activity activity);
+ void onActivityPaused(@NonNull Activity activity);
+ void onActivityStopped(@NonNull Activity activity);
+ void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
+ void onActivityDestroyed(@NonNull Activity activity);
}
/**
@@ -213,7 +215,8 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
- /* package */ void dispatchActivityCreated(Activity activity, Bundle savedInstanceState) {
+ /* package */ void dispatchActivityCreated(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -223,7 +226,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- /* package */ void dispatchActivityStarted(Activity activity) {
+ /* package */ void dispatchActivityStarted(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -232,7 +235,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- /* package */ void dispatchActivityResumed(Activity activity) {
+ /* package */ void dispatchActivityResumed(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -241,7 +244,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- /* package */ void dispatchActivityPaused(Activity activity) {
+ /* package */ void dispatchActivityPaused(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -250,7 +253,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- /* package */ void dispatchActivityStopped(Activity activity) {
+ /* package */ void dispatchActivityStopped(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -259,7 +262,8 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- /* package */ void dispatchActivitySaveInstanceState(Activity activity, Bundle outState) {
+ /* package */ void dispatchActivitySaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
@@ -269,7 +273,7 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
- /* package */ void dispatchActivityDestroyed(Activity activity) {
+ /* package */ void dispatchActivityDestroyed(@NonNull Activity activity) {
Object[] callbacks = collectActivityLifecycleCallbacks();
if (callbacks != null) {
for (int i=0; i<callbacks.length; i++) {
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d9c7cf3ccc74..193f933df782 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -16,6 +16,7 @@
package android.app;
import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.PendingTransactionActions;
import android.app.servertransaction.TransactionExecutor;
import android.content.Intent;
@@ -29,6 +30,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
import java.util.List;
+import java.util.Map;
/**
* Defines operations that a {@link android.app.servertransaction.ClientTransaction} or its items
@@ -78,6 +80,9 @@ public abstract class ClientTransactionHandler {
// Execute phase related logic and handlers. Methods here execute actual lifecycle transactions
// and deliver callbacks.
+ /** Get activity and its corresponding transaction item which are going to destroy. */
+ public abstract Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed();
+
/** Destroy the activity. */
public abstract void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
boolean getNonConfigInstance, String reason);
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index f9c39165b2c8..19d7c83818f6 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -268,7 +268,7 @@ interface IActivityManager {
void showBootMessage(in CharSequence msg, boolean always);
void killAllBackgroundProcesses();
ContentProviderHolder getContentProviderExternal(in String name, int userId,
- in IBinder token);
+ in IBinder token, String tag);
void removeContentProviderExternal(in String name, in IBinder token);
// Get memory information about the calling process.
void getMyMemoryState(out ActivityManager.RunningAppProcessInfo outInfo);
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 34be41b63233..d9969a77b88a 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1313,7 +1313,8 @@ public class Instrumentation {
* @param activity The activity being restored.
* @param savedInstanceState The previously saved state being restored.
*/
- public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) {
+ public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
+ @NonNull Bundle savedInstanceState) {
activity.performRestoreInstanceState(savedInstanceState);
}
@@ -1322,11 +1323,12 @@ public class Instrumentation {
* method. The default implementation simply calls through to that method.
*
* @param activity The activity being restored.
- * @param savedInstanceState The previously saved state being restored.
+ * @param savedInstanceState The previously saved state being restored (or null).
* @param persistentState The previously persisted state (or null)
*/
- public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState,
- PersistableBundle persistentState) {
+ public void callActivityOnRestoreInstanceState(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
activity.performRestoreInstanceState(savedInstanceState, persistentState);
}
@@ -1335,11 +1337,12 @@ public class Instrumentation {
* The default implementation simply calls through to that method.
*
* @param activity The activity being created.
- * @param icicle The previously frozen state (or null) to pass through to
+ * @param savedInstanceState The previously saved state (or null) to pass through to
* onPostCreate().
*/
- public void callActivityOnPostCreate(Activity activity, Bundle icicle) {
- activity.onPostCreate(icicle);
+ public void callActivityOnPostCreate(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState) {
+ activity.onPostCreate(savedInstanceState);
}
/**
@@ -1347,12 +1350,14 @@ public class Instrumentation {
* The default implementation simply calls through to that method.
*
* @param activity The activity being created.
- * @param icicle The previously frozen state (or null) to pass through to
+ * @param savedInstanceState The previously frozen state (or null) to pass through to
* onPostCreate().
+ * @param persistentState The previously persisted state (or null)
*/
- public void callActivityOnPostCreate(Activity activity, Bundle icicle,
- PersistableBundle persistentState) {
- activity.onPostCreate(icicle, persistentState);
+ public void callActivityOnPostCreate(@NonNull Activity activity,
+ @Nullable Bundle savedInstanceState,
+ @Nullable PersistableBundle persistentState) {
+ activity.onPostCreate(savedInstanceState, persistentState);
}
/**
@@ -1439,7 +1444,8 @@ public class Instrumentation {
* @param activity The activity being saved.
* @param outState The bundle to pass to the call.
*/
- public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
+ public void callActivityOnSaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState) {
activity.performSaveInstanceState(outState);
}
@@ -1450,8 +1456,8 @@ public class Instrumentation {
* @param outState The bundle to pass to the call.
* @param outPersistentState The persistent bundle to pass to the call.
*/
- public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
- PersistableBundle outPersistentState) {
+ public void callActivityOnSaveInstanceState(@NonNull Activity activity,
+ @NonNull Bundle outState, @NonNull PersistableBundle outPersistentState) {
activity.performSaveInstanceState(outState, outPersistentState);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 05bf6bfa3a8f..74d3c0df87ad 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -202,6 +202,11 @@ public class Notification implements Parcelable
*/
private static final int MAX_REPLY_HISTORY = 5;
+ /**
+ * Maximum numbers of action buttons in a notification.
+ * @hide
+ */
+ public static final int MAX_ACTION_BUTTONS = 3;
/**
* If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
@@ -3151,8 +3156,6 @@ public class Notification implements Parcelable
public static final String EXTRA_REBUILD_HEADS_UP_CONTENT_VIEW_ACTION_COUNT
= "android.rebuild.hudViewActionCount";
- private static final int MAX_ACTION_BUTTONS = 3;
-
private static final boolean USE_ONLY_TITLE_IN_LOW_PRIORITY_SUMMARY =
SystemProperties.getBoolean("notifications.only_title", true);
@@ -4473,9 +4476,9 @@ public class Notification implements Parcelable
mTextColorsAreForBackground = backgroundColor;
if (!hasForegroundColor() || !isColorized()) {
mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- backgroundColor);
+ backgroundColor, mInNightMode);
mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
- backgroundColor);
+ backgroundColor, mInNightMode);
if (backgroundColor != COLOR_DEFAULT && isColorized()) {
mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
mPrimaryTextColor, backgroundColor, 4.5);
@@ -5260,7 +5263,7 @@ public class Notification implements Parcelable
// background color
background = outResultColor[0].getDefaultColor();
int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
- background);
+ background, mInNightMode);
button.setTextColor(R.id.action0, textColor);
rippleColor = textColor;
} else if (mN.color != COLOR_DEFAULT && !isColorized() && mTintActionButtons) {
@@ -5440,7 +5443,7 @@ public class Notification implements Parcelable
com.android.internal.R.color.notification_material_background_color);
if (mN.color == COLOR_DEFAULT) {
ensureColors();
- color = ContrastColorUtil.resolveDefaultColor(mContext, background);
+ color = ContrastColorUtil.resolveDefaultColor(mContext, background, mInNightMode);
} else {
color = ContrastColorUtil.resolveContrastColor(mContext, mN.color,
background, mInNightMode);
@@ -5459,7 +5462,8 @@ public class Notification implements Parcelable
}
int background = mContext.getColor(
com.android.internal.R.color.notification_material_background_color);
- mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background);
+ mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background,
+ mInNightMode);
if (Color.alpha(mNeutralColor) < 255) {
// alpha doesn't go well for color filters, so let's blend it manually
mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
@@ -7162,8 +7166,8 @@ public class Notification implements Parcelable
}
public static final class Message {
-
- static final String KEY_TEXT = "text";
+ /** @hide */
+ public static final String KEY_TEXT = "text";
static final String KEY_TIMESTAMP = "time";
static final String KEY_SENDER = "sender";
static final String KEY_SENDER_PERSON = "sender_person";
@@ -7830,10 +7834,13 @@ public class Notification implements Parcelable
// If the action buttons should not be tinted, then just use the default
// notification color. Otherwise, just use the passed-in color.
+ Configuration currentConfig = mBuilder.mContext.getResources().getConfiguration();
+ boolean inNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized()
? color
: ContrastColorUtil.resolveColor(mBuilder.mContext,
- Notification.COLOR_DEFAULT);
+ Notification.COLOR_DEFAULT, inNightMode);
button.setDrawableTint(R.id.action0, false, tintColor,
PorterDuff.Mode.SRC_ATOP);
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 1ad305441539..03fd139e12ff 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -38,6 +38,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.Arrays;
/**
@@ -942,6 +943,32 @@ public final class NotificationChannel implements Parcelable {
return result;
}
+ /** @hide */
+ public void dump(PrintWriter pw, String prefix, boolean redacted) {
+ String redactedName = redacted ? TextUtils.trimToLengthWithEllipsis(mName, 3) : mName;
+ String output = "NotificationChannel{"
+ + "mId='" + mId + '\''
+ + ", mName=" + redactedName
+ + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ + ", mImportance=" + mImportance
+ + ", mBypassDnd=" + mBypassDnd
+ + ", mLockscreenVisibility=" + mLockscreenVisibility
+ + ", mSound=" + mSound
+ + ", mLights=" + mLights
+ + ", mLightColor=" + mLightColor
+ + ", mVibration=" + Arrays.toString(mVibration)
+ + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
+ + ", mFgServiceShown=" + mFgServiceShown
+ + ", mVibrationEnabled=" + mVibrationEnabled
+ + ", mShowBadge=" + mShowBadge
+ + ", mDeleted=" + mDeleted
+ + ", mGroup='" + mGroup + '\''
+ + ", mAudioAttributes=" + mAudioAttributes
+ + ", mBlockableSystem=" + mBlockableSystem
+ + '}';
+ pw.println(prefix + output);
+ }
+
@Override
public String toString() {
return "NotificationChannel{"
diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java
index 08ad2f055774..2c1e59bfc066 100644
--- a/core/java/android/app/servertransaction/ClientTransaction.java
+++ b/core/java/android/app/servertransaction/ClientTransaction.java
@@ -164,6 +164,31 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem {
ObjectPool.recycle(this);
}
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder(64);
+ sb.append("ClientTransaction{");
+ if (mActivityToken != null) {
+ sb.append(" a:").append(Integer.toHexString(System.identityHashCode(mActivityToken)));
+ }
+ if (mActivityCallbacks != null && !mActivityCallbacks.isEmpty()) {
+ sb.append(" c:");
+ final int size = mActivityCallbacks.size();
+ for (int i = 0; i < size; i++) {
+ sb.append(mActivityCallbacks.get(i).getClass().getSimpleName());
+ if (i < size - 1) {
+ sb.append(",");
+ }
+ }
+ }
+ if (mLifecycleStateRequest != null) {
+ sb.append(" s:");
+ sb.append(mLifecycleStateRequest.getClass().getSimpleName());
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
// Parcelable implementation
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index b443166d151c..5941486c099d 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -33,6 +33,11 @@ public class DestroyActivityItem extends ActivityLifecycleItem {
private int mConfigChanges;
@Override
+ public void preExecute(ClientTransactionHandler client, IBinder token) {
+ client.getActivitiesToBeDestroyed().put(token, this);
+ }
+
+ @Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 43a2b4cc43f8..503e18b62cae 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -35,6 +35,7 @@ import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import java.util.List;
+import java.util.Map;
/**
* Class that manages transaction execution in the correct order.
@@ -63,6 +64,24 @@ public class TransactionExecutor {
*/
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
+ if (token != null) {
+ final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed =
+ mTransactionHandler.getActivitiesToBeDestroyed();
+ final ClientTransactionItem destroyItem = activitiesToBeDestroyed.get(token);
+ if (destroyItem != null) {
+ if (transaction.getLifecycleStateRequest() == destroyItem) {
+ // It is going to execute the transaction that will destroy activity with the
+ // token, so the corresponding to-be-destroyed record can be removed.
+ activitiesToBeDestroyed.remove(token);
+ }
+ if (mTransactionHandler.getActivityClient(token) == null) {
+ // The activity has not been created but has been requested to destroy, so all
+ // transactions for the token are just like being cancelled.
+ Slog.w(TAG, "Skip pre-destroyed " + transaction);
+ return;
+ }
+ }
+ }
log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);
executeCallbacks(transaction);
@@ -76,7 +95,7 @@ public class TransactionExecutor {
@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
- if (callbacks == null) {
+ if (callbacks == null || callbacks.isEmpty()) {
// No callbacks to execute, return early.
return;
}
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 2d490a03bd76..9d8c318f8092 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -39,6 +39,8 @@ import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
+import libcore.io.IoUtils;
+
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -560,14 +562,17 @@ public class ContentProviderClient implements AutoCloseable {
return ContentProvider.coerceToLocalContentProvider(mContentProvider);
}
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ */
+ public static void closeQuietly(ContentProviderClient client) {
+ IoUtils.closeQuietly(client);
+ }
+
/** {@hide} */
public static void releaseQuietly(ContentProviderClient client) {
- if (client != null) {
- try {
- client.release();
- } catch (Exception ignored) {
- }
- }
+ IoUtils.closeQuietly(client);
}
private class NotRespondingRunnable implements Runnable {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 4baf2638d888..86bd30c19ca3 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -747,6 +747,9 @@ public class CameraMetadataNative implements Parcelable {
if (faceDetectMode == null) {
Log.w(TAG, "Face detect mode metadata is null, assuming the mode is SIMPLE");
faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_SIMPLE;
+ } else if (faceDetectMode > CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL) {
+ // Face detect mode is larger than FULL, assuming the mode is FULL
+ faceDetectMode = CaptureResult.STATISTICS_FACE_DETECT_MODE_FULL;
} else {
if (faceDetectMode == CaptureResult.STATISTICS_FACE_DETECT_MODE_OFF) {
return new Face[0];
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0a66e914c9b6..3de9de3ed627 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -836,10 +836,8 @@ public class FaceManager implements BiometricFaceConstants {
* {@link EnrollmentCallback#onEnrollmentError(int, CharSequence)}
*
* @param remaining The number of remaining steps
- * @param vendorMsg Vendor feedback about the current enroll attempt. Use it to customize
- * the GUI according to vendor's requirements.
*/
- public void onEnrollmentProgress(int remaining, long vendorMsg) {
+ public void onEnrollmentProgress(int remaining) {
}
}
@@ -920,7 +918,7 @@ public class FaceManager implements BiometricFaceConstants {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case MSG_ENROLL_RESULT:
- sendEnrollResult((EnrollResultMsg) msg.obj);
+ sendEnrollResult((Face) msg.obj, msg.arg1 /* remaining */);
break;
case MSG_ACQUIRED:
sendAcquiredResult((Long) msg.obj /* deviceId */, msg.arg1 /* acquire info */,
@@ -951,8 +949,6 @@ public class FaceManager implements BiometricFaceConstants {
Log.e(TAG, "Received MSG_REMOVED, but face is null");
return;
}
-
-
mRemovalCallback.onRemovalSucceeded(face, remaining);
}
@@ -972,11 +968,9 @@ public class FaceManager implements BiometricFaceConstants {
}
}
- private void sendEnrollResult(EnrollResultMsg faceWrapper) {
+ private void sendEnrollResult(Face face, int remaining) {
if (mEnrollmentCallback != null) {
- int remaining = faceWrapper.getRemaining();
- long vendorMsg = faceWrapper.getVendorMsg();
- mEnrollmentCallback.onEnrollmentProgress(remaining, vendorMsg);
+ mEnrollmentCallback.onEnrollmentProgress(remaining);
}
}
@@ -1010,28 +1004,4 @@ public class FaceManager implements BiometricFaceConstants {
mAuthenticationCallback.onAuthenticationHelp(clientInfo, msg);
}
}
-
- private class EnrollResultMsg {
- private final Face mFace;
- private final int mRemaining;
- private final long mVendorMsg;
-
- EnrollResultMsg(Face face, int remaining, long vendorMsg) {
- mFace = face;
- mRemaining = remaining;
- mVendorMsg = vendorMsg;
- }
-
- Face getFace() {
- return mFace;
- }
-
- long getVendorMsg() {
- return mVendorMsg;
- }
-
- int getRemaining() {
- return mRemaining;
- }
- }
}
diff --git a/core/java/android/hardware/location/NanoAppFilter.java b/core/java/android/hardware/location/NanoAppFilter.java
index 4d8e7344cd75..562065e20815 100644
--- a/core/java/android/hardware/location/NanoAppFilter.java
+++ b/core/java/android/hardware/location/NanoAppFilter.java
@@ -85,7 +85,7 @@ public class NanoAppFilter implements Parcelable {
mAppId = in.readLong();
mAppVersion = in.readInt();
mVersionRestrictionMask = in.readInt();
- mAppIdVendorMask = in.readInt();
+ mAppIdVendorMask = in.readLong();
}
public int describeContents() {
@@ -93,7 +93,6 @@ public class NanoAppFilter implements Parcelable {
}
public void writeToParcel(Parcel out, int flags) {
-
out.writeLong(mAppId);
out.writeInt(mAppVersion);
out.writeInt(mVersionRestrictionMask);
diff --git a/core/java/android/hardware/radio/RadioManager.aidl b/core/java/android/hardware/radio/RadioManager.aidl
index 8a39388482fd..34c05d89d715 100644
--- a/core/java/android/hardware/radio/RadioManager.aidl
+++ b/core/java/android/hardware/radio/RadioManager.aidl
@@ -20,6 +20,9 @@ package android.hardware.radio;
parcelable RadioManager.BandConfig;
/** @hide */
+parcelable RadioManager.BandDescriptor;
+
+/** @hide */
parcelable RadioManager.ModuleProperties;
/** @hide */
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index f31130f8645d..ab2cf86fab29 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -552,6 +552,20 @@ public class Binder implements IBinder {
}
/**
+ * Resolves a transaction code to a human readable name.
+ *
+ * <p>Default implementation is a stub that returns null.
+ * <p>AIDL generated code will return the original method name.
+ *
+ * @param transactionCode The code to resolve.
+ * @return A human readable name.
+ * @hide
+ */
+ public @Nullable String getTransactionName(int transactionCode) {
+ return null;
+ }
+
+ /**
* Implemented to call the more convenient version
* {@link #dump(FileDescriptor, PrintWriter, String[])}.
*/
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 88d6e847b644..9fccd1ec7b43 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -53,32 +53,35 @@ import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;
/**
- * Tools for managing files. Not for public consumption.
- * @hide
+ * Utility methods useful for working with files.
*/
public class FileUtils {
private static final String TAG = "FileUtils";
- public static final int S_IRWXU = 00700;
- public static final int S_IRUSR = 00400;
- public static final int S_IWUSR = 00200;
- public static final int S_IXUSR = 00100;
+ /** {@hide} */ public static final int S_IRWXU = 00700;
+ /** {@hide} */ public static final int S_IRUSR = 00400;
+ /** {@hide} */ public static final int S_IWUSR = 00200;
+ /** {@hide} */ public static final int S_IXUSR = 00100;
- public static final int S_IRWXG = 00070;
- public static final int S_IRGRP = 00040;
- public static final int S_IWGRP = 00020;
- public static final int S_IXGRP = 00010;
+ /** {@hide} */ public static final int S_IRWXG = 00070;
+ /** {@hide} */ public static final int S_IRGRP = 00040;
+ /** {@hide} */ public static final int S_IWGRP = 00020;
+ /** {@hide} */ public static final int S_IXGRP = 00010;
- public static final int S_IRWXO = 00007;
- public static final int S_IROTH = 00004;
- public static final int S_IWOTH = 00002;
- public static final int S_IXOTH = 00001;
+ /** {@hide} */ public static final int S_IRWXO = 00007;
+ /** {@hide} */ public static final int S_IROTH = 00004;
+ /** {@hide} */ public static final int S_IWOTH = 00002;
+ /** {@hide} */ public static final int S_IXOTH = 00001;
+
+ private FileUtils() {
+ }
/** Regular expression for safe filenames: no spaces or metacharacters.
*
@@ -94,6 +97,9 @@ public class FileUtils {
private static final long COPY_CHECKPOINT_BYTES = 524288;
+ /**
+ * Listener that is called periodically as progress is made.
+ */
public interface ProgressListener {
public void onProgress(long progress);
}
@@ -105,6 +111,7 @@ public class FileUtils {
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
+ * @hide
*/
public static int setPermissions(File path, int mode, int uid, int gid) {
return setPermissions(path.getAbsolutePath(), mode, uid, gid);
@@ -117,6 +124,7 @@ public class FileUtils {
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
+ * @hide
*/
public static int setPermissions(String path, int mode, int uid, int gid) {
try {
@@ -145,6 +153,7 @@ public class FileUtils {
* @param uid to apply through {@code chown}, or -1 to leave unchanged
* @param gid to apply through {@code chown}, or -1 to leave unchanged
* @return 0 on success, otherwise errno.
+ * @hide
*/
public static int setPermissions(FileDescriptor fd, int mode, int uid, int gid) {
try {
@@ -166,7 +175,14 @@ public class FileUtils {
return 0;
}
- public static void copyPermissions(File from, File to) throws IOException {
+ /**
+ * Copy the owner UID, owner GID, and mode bits from one file to another.
+ *
+ * @param from File where attributes should be copied from.
+ * @param to File where attributes should be copied to.
+ * @hide
+ */
+ public static void copyPermissions(@NonNull File from, @NonNull File to) throws IOException {
try {
final StructStat stat = Os.stat(from.getAbsolutePath());
Os.chmod(to.getAbsolutePath(), stat.st_mode);
@@ -177,8 +193,10 @@ public class FileUtils {
}
/**
- * Return owning UID of given path, otherwise -1.
+ * @deprecated use {@link Os#stat(String)} instead.
+ * @hide
*/
+ @Deprecated
public static int getUid(String path) {
try {
return Os.stat(path).st_uid;
@@ -190,6 +208,8 @@ public class FileUtils {
/**
* Perform an fsync on the given FileOutputStream. The stream at this
* point must be flushed but not yet closed.
+ *
+ * @hide
*/
public static boolean sync(FileOutputStream stream) {
try {
@@ -204,6 +224,7 @@ public class FileUtils {
/**
* @deprecated use {@link #copy(File, File)} instead.
+ * @hide
*/
@Deprecated
public static boolean copyFile(File srcFile, File destFile) {
@@ -217,6 +238,7 @@ public class FileUtils {
/**
* @deprecated use {@link #copy(File, File)} instead.
+ * @hide
*/
@Deprecated
public static void copyFileOrThrow(File srcFile, File destFile) throws IOException {
@@ -227,6 +249,7 @@ public class FileUtils {
/**
* @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ * @hide
*/
@Deprecated
public static boolean copyToFile(InputStream inputStream, File destFile) {
@@ -240,6 +263,7 @@ public class FileUtils {
/**
* @deprecated use {@link #copy(InputStream, OutputStream)} instead.
+ * @hide
*/
@Deprecated
public static void copyToFileOrThrow(InputStream in, File destFile) throws IOException {
@@ -265,7 +289,7 @@ public class FileUtils {
* @return number of bytes copied.
*/
public static long copy(@NonNull File from, @NonNull File to) throws IOException {
- return copy(from, to, null, null);
+ return copy(from, to, null, null, null);
}
/**
@@ -274,16 +298,17 @@ public class FileUtils {
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
* @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
*/
public static long copy(@NonNull File from, @NonNull File to,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
- throws IOException {
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
try (FileInputStream in = new FileInputStream(from);
FileOutputStream out = new FileOutputStream(to)) {
- return copy(in, out, listener, signal);
+ return copy(in, out, signal, executor, listener);
}
}
@@ -296,7 +321,7 @@ public class FileUtils {
* @return number of bytes copied.
*/
public static long copy(@NonNull InputStream in, @NonNull OutputStream out) throws IOException {
- return copy(in, out, null, null);
+ return copy(in, out, null, null, null);
}
/**
@@ -305,22 +330,23 @@ public class FileUtils {
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
* @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
*/
public static long copy(@NonNull InputStream in, @NonNull OutputStream out,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
- throws IOException {
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
if (ENABLE_COPY_OPTIMIZATIONS) {
if (in instanceof FileInputStream && out instanceof FileOutputStream) {
return copy(((FileInputStream) in).getFD(), ((FileOutputStream) out).getFD(),
- listener, signal);
+ signal, executor, listener);
}
}
// Worse case fallback to userspace
- return copyInternalUserspace(in, out, listener, signal);
+ return copyInternalUserspace(in, out, signal, executor, listener);
}
/**
@@ -333,7 +359,7 @@ public class FileUtils {
*/
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out)
throws IOException {
- return copy(in, out, null, null);
+ return copy(in, out, null, null, null);
}
/**
@@ -342,14 +368,15 @@ public class FileUtils {
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
* @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
*/
public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal)
- throws IOException {
- return copy(in, out, listener, signal, Long.MAX_VALUE);
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
+ return copy(in, out, Long.MAX_VALUE, signal, executor, listener);
}
/**
@@ -358,22 +385,24 @@ public class FileUtils {
* Attempts to use several optimization strategies to copy the data in the
* kernel before falling back to a userspace copy as a last resort.
*
- * @param listener to be periodically notified as the copy progresses.
- * @param signal to signal if the copy should be cancelled early.
* @param count the number of bytes to copy.
+ * @param signal to signal if the copy should be cancelled early.
+ * @param executor that listener events should be delivered via.
+ * @param listener to be periodically notified as the copy progresses.
* @return number of bytes copied.
+ * @hide
*/
- public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
- @Nullable ProgressListener listener, @Nullable CancellationSignal signal, long count)
- throws IOException {
+ public static long copy(@NonNull FileDescriptor in, @NonNull FileDescriptor out, long count,
+ @Nullable CancellationSignal signal, @Nullable Executor executor,
+ @Nullable ProgressListener listener) throws IOException {
if (ENABLE_COPY_OPTIMIZATIONS) {
try {
final StructStat st_in = Os.fstat(in);
final StructStat st_out = Os.fstat(out);
if (S_ISREG(st_in.st_mode) && S_ISREG(st_out.st_mode)) {
- return copyInternalSendfile(in, out, listener, signal, count);
+ return copyInternalSendfile(in, out, count, signal, executor, listener);
} else if (S_ISFIFO(st_in.st_mode) || S_ISFIFO(st_out.st_mode)) {
- return copyInternalSplice(in, out, listener, signal, count);
+ return copyInternalSplice(in, out, count, signal, executor, listener);
}
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
@@ -381,15 +410,17 @@ public class FileUtils {
}
// Worse case fallback to userspace
- return copyInternalUserspace(in, out, listener, signal, count);
+ return copyInternalUserspace(in, out, count, signal, executor, listener);
}
/**
* Requires one of input or output to be a pipe.
+ *
+ * @hide
*/
@VisibleForTesting
- public static long copyInternalSplice(FileDescriptor in, FileDescriptor out,
- ProgressListener listener, CancellationSignal signal, long count)
+ public static long copyInternalSplice(FileDescriptor in, FileDescriptor out, long count,
+ CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
long progress = 0;
long checkpoint = 0;
@@ -405,24 +436,32 @@ public class FileUtils {
if (signal != null) {
signal.throwIfCanceled();
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
checkpoint = 0;
}
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
return progress;
}
/**
* Requires both input and output to be a regular file.
+ *
+ * @hide
*/
@VisibleForTesting
- public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out,
- ProgressListener listener, CancellationSignal signal, long count)
+ public static long copyInternalSendfile(FileDescriptor in, FileDescriptor out, long count,
+ CancellationSignal signal, Executor executor, ProgressListener listener)
throws ErrnoException {
long progress = 0;
long checkpoint = 0;
@@ -437,33 +476,52 @@ public class FileUtils {
if (signal != null) {
signal.throwIfCanceled();
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
checkpoint = 0;
}
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
return progress;
}
+ /** {@hide} */
+ @Deprecated
@VisibleForTesting
public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out,
- ProgressListener listener, CancellationSignal signal, long count) throws IOException {
+ ProgressListener listener, CancellationSignal signal, long count)
+ throws IOException {
+ return copyInternalUserspace(in, out, count, signal, Runnable::run, listener);
+ }
+
+ /** {@hide} */
+ @VisibleForTesting
+ public static long copyInternalUserspace(FileDescriptor in, FileDescriptor out, long count,
+ CancellationSignal signal, Executor executor, ProgressListener listener)
+ throws IOException {
if (count != Long.MAX_VALUE) {
return copyInternalUserspace(new SizedInputStream(new FileInputStream(in), count),
- new FileOutputStream(out), listener, signal);
+ new FileOutputStream(out), signal, executor, listener);
} else {
return copyInternalUserspace(new FileInputStream(in),
- new FileOutputStream(out), listener, signal);
+ new FileOutputStream(out), signal, executor, listener);
}
}
+ /** {@hide} */
@VisibleForTesting
public static long copyInternalUserspace(InputStream in, OutputStream out,
- ProgressListener listener, CancellationSignal signal) throws IOException {
+ CancellationSignal signal, Executor executor, ProgressListener listener)
+ throws IOException {
long progress = 0;
long checkpoint = 0;
byte[] buffer = new byte[8192];
@@ -479,14 +537,20 @@ public class FileUtils {
if (signal != null) {
signal.throwIfCanceled();
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
checkpoint = 0;
}
}
- if (listener != null) {
- listener.onProgress(progress);
+ if (executor != null && listener != null) {
+ final long progressSnapshot = progress;
+ executor.execute(() -> {
+ listener.onProgress(progressSnapshot);
+ });
}
return progress;
}
@@ -494,6 +558,7 @@ public class FileUtils {
/**
* Check if a filename is "safe" (no metacharacters or spaces).
* @param file The file to check
+ * @hide
*/
public static boolean isFilenameSafe(File file) {
// Note, we check whether it matches what's known to be safe,
@@ -509,6 +574,7 @@ public class FileUtils {
* @param ellipsis to add of the file was truncated (can be null)
* @return the contents of the file, possibly truncated
* @throws IOException if something goes wrong reading the file
+ * @hide
*/
public static String readTextFile(File file, int max, String ellipsis) throws IOException {
InputStream input = new FileInputStream(file);
@@ -563,13 +629,16 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static void stringToFile(File file, String string) throws IOException {
stringToFile(file.getAbsolutePath(), string);
}
- /*
+ /**
* Writes the bytes given in {@code content} to the file whose absolute path
* is {@code filename}.
+ *
+ * @hide
*/
public static void bytesToFile(String filename, byte[] content) throws IOException {
if (filename.startsWith("/proc/")) {
@@ -592,18 +661,23 @@ public class FileUtils {
* @param filename
* @param string
* @throws IOException
+ * @hide
*/
public static void stringToFile(String filename, String string) throws IOException {
bytesToFile(filename, string.getBytes(StandardCharsets.UTF_8));
}
/**
- * Computes the checksum of a file using the CRC32 checksum routine.
- * The value of the checksum is returned.
+ * Computes the checksum of a file using the CRC32 checksum routine. The
+ * value of the checksum is returned.
*
- * @param file the file to checksum, must not be null
+ * @param file the file to checksum, must not be null
* @return the checksum value or an exception is thrown.
+ * @deprecated this is a weak hashing algorithm, and should not be used due
+ * to its potential for collision.
+ * @hide
*/
+ @Deprecated
public static long checksumCrc32(File file) throws FileNotFoundException, IOException {
CRC32 checkSummer = new CRC32();
CheckedInputStream cis = null;
@@ -632,6 +706,7 @@ public class FileUtils {
* @param minCount Always keep at least this many files.
* @param minAgeMs Always keep files younger than this age, in milliseconds.
* @return if any files were deleted.
+ * @hide
*/
public static boolean deleteOlderFiles(File dir, int minCount, long minAgeMs) {
if (minCount < 0 || minAgeMs < 0) {
@@ -673,6 +748,8 @@ public class FileUtils {
* Both files <em>must</em> have been resolved using
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
+ *
+ * @hide
*/
public static boolean contains(File[] dirs, File file) {
for (File dir : dirs) {
@@ -690,12 +767,15 @@ public class FileUtils {
* Both files <em>must</em> have been resolved using
* {@link File#getCanonicalFile()} to avoid symlink or path traversal
* attacks.
+ *
+ * @hide
*/
public static boolean contains(File dir, File file) {
if (dir == null || file == null) return false;
return contains(dir.getAbsolutePath(), file.getAbsolutePath());
}
+ /** {@hide} */
public static boolean contains(String dirPath, String filePath) {
if (dirPath.equals(filePath)) {
return true;
@@ -706,6 +786,7 @@ public class FileUtils {
return filePath.startsWith(dirPath);
}
+ /** {@hide} */
public static boolean deleteContentsAndDir(File dir) {
if (deleteContents(dir)) {
return dir.delete();
@@ -714,6 +795,7 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
@@ -743,6 +825,8 @@ public class FileUtils {
/**
* Check if given filename is valid for an ext4 filesystem.
+ *
+ * @hide
*/
public static boolean isValidExtFilename(String name) {
return (name != null) && name.equals(buildValidExtFilename(name));
@@ -751,6 +835,8 @@ public class FileUtils {
/**
* Mutate the given filename to make it valid for an ext4 filesystem,
* replacing any invalid characters with "_".
+ *
+ * @hide
*/
public static String buildValidExtFilename(String name) {
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
@@ -792,6 +878,8 @@ public class FileUtils {
/**
* Check if given filename is valid for a FAT filesystem.
+ *
+ * @hide
*/
public static boolean isValidFatFilename(String name) {
return (name != null) && name.equals(buildValidFatFilename(name));
@@ -800,6 +888,8 @@ public class FileUtils {
/**
* Mutate the given filename to make it valid for a FAT filesystem,
* replacing any invalid characters with "_".
+ *
+ * @hide
*/
public static String buildValidFatFilename(String name) {
if (TextUtils.isEmpty(name) || ".".equals(name) || "..".equals(name)) {
@@ -820,6 +910,7 @@ public class FileUtils {
return res.toString();
}
+ /** {@hide} */
@VisibleForTesting
public static String trimFilename(String str, int maxBytes) {
final StringBuilder res = new StringBuilder(str);
@@ -827,6 +918,7 @@ public class FileUtils {
return res.toString();
}
+ /** {@hide} */
private static void trimFilename(StringBuilder res, int maxBytes) {
byte[] raw = res.toString().getBytes(StandardCharsets.UTF_8);
if (raw.length > maxBytes) {
@@ -839,12 +931,14 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static String rewriteAfterRename(File beforeDir, File afterDir, String path) {
if (path == null) return null;
final File result = rewriteAfterRename(beforeDir, afterDir, new File(path));
return (result != null) ? result.getAbsolutePath() : null;
}
+ /** {@hide} */
public static String[] rewriteAfterRename(File beforeDir, File afterDir, String[] paths) {
if (paths == null) return null;
final String[] result = new String[paths.length];
@@ -858,6 +952,8 @@ public class FileUtils {
* Given a path under the "before" directory, rewrite it to live under the
* "after" directory. For example, {@code /before/foo/bar.txt} would become
* {@code /after/foo/bar.txt}.
+ *
+ * @hide
*/
public static File rewriteAfterRename(File beforeDir, File afterDir, File file) {
if (file == null || beforeDir == null || afterDir == null) return null;
@@ -869,6 +965,7 @@ public class FileUtils {
return null;
}
+ /** {@hide} */
private static File buildUniqueFileWithExtension(File parent, String name, String ext)
throws FileNotFoundException {
File file = buildFile(parent, name, ext);
@@ -895,6 +992,7 @@ public class FileUtils {
* 'example.txt' or 'example (1).txt', etc.
*
* @throws FileNotFoundException
+ * @hide
*/
public static File buildUniqueFile(File parent, String mimeType, String displayName)
throws FileNotFoundException {
@@ -905,6 +1003,8 @@ public class FileUtils {
/**
* Generates a unique file name under the given parent directory, keeping
* any extension intact.
+ *
+ * @hide
*/
public static File buildUniqueFile(File parent, String displayName)
throws FileNotFoundException {
@@ -929,6 +1029,8 @@ public class FileUtils {
* If the display name doesn't have an extension that matches the requested MIME type, the
* extension is regarded as a part of filename and default extension for that MIME type is
* appended.
+ *
+ * @hide
*/
public static String[] splitFileName(String mimeType, String displayName) {
String name;
@@ -975,6 +1077,7 @@ public class FileUtils {
return new String[] { name, ext };
}
+ /** {@hide} */
private static File buildFile(File parent, String name, String ext) {
if (TextUtils.isEmpty(ext)) {
return new File(parent, name);
@@ -983,6 +1086,7 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static @NonNull String[] listOrEmpty(@Nullable File dir) {
if (dir == null) return EmptyArray.STRING;
final String[] res = dir.list();
@@ -993,6 +1097,7 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir) {
if (dir == null) return EMPTY;
final File[] res = dir.listFiles();
@@ -1003,6 +1108,7 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static @NonNull File[] listFilesOrEmpty(@Nullable File dir, FilenameFilter filter) {
if (dir == null) return EMPTY;
final File[] res = dir.listFiles(filter);
@@ -1013,6 +1119,7 @@ public class FileUtils {
}
}
+ /** {@hide} */
public static @Nullable File newFileOrNull(@Nullable String path) {
return (path != null) ? new File(path) : null;
}
@@ -1021,6 +1128,8 @@ public class FileUtils {
* Creates a directory with name {@code name} under an existing directory {@code baseDir}.
* Returns a {@code File} object representing the directory on success, {@code null} on
* failure.
+ *
+ * @hide
*/
public static @Nullable File createDir(File baseDir, String name) {
final File dir = new File(baseDir, name);
@@ -1036,6 +1145,8 @@ public class FileUtils {
* Round the given size of a storage device to a nice round power-of-two
* value, such as 256MB or 32GB. This avoids showing weird values like
* "29.5GB" in UI.
+ *
+ * @hide
*/
public static long roundStorageSize(long size) {
long val = 1;
@@ -1050,6 +1161,23 @@ public class FileUtils {
return val * pow;
}
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ */
+ public static void closeQuietly(@Nullable AutoCloseable closeable) {
+ IoUtils.closeQuietly(closeable);
+ }
+
+ /**
+ * Closes the given object quietly, ignoring any checked exceptions. Does
+ * nothing if the given object is {@code null}.
+ */
+ public static void closeQuietly(@Nullable FileDescriptor fd) {
+ IoUtils.closeQuietly(fd);
+ }
+
+ /** {@hide} */
@VisibleForTesting
public static class MemoryPipe extends Thread implements AutoCloseable {
private final FileDescriptor[] pipe;
diff --git a/core/java/android/print/PrintFileDocumentAdapter.java b/core/java/android/print/PrintFileDocumentAdapter.java
index a5f93050e307..eb4b31510880 100644
--- a/core/java/android/print/PrintFileDocumentAdapter.java
+++ b/core/java/android/print/PrintFileDocumentAdapter.java
@@ -118,7 +118,7 @@ public class PrintFileDocumentAdapter extends PrintDocumentAdapter {
protected Void doInBackground(Void... params) {
try (InputStream in = new FileInputStream(mFile);
OutputStream out = new FileOutputStream(mDestination.getFileDescriptor())) {
- FileUtils.copy(in, out, null, mCancellationSignal);
+ FileUtils.copy(in, out, mCancellationSignal, null, null);
} catch (OperationCanceledException e) {
// Ignored; already handled below
} catch (IOException e) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index a80dcedf9915..f97c64c96a7a 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -100,7 +100,8 @@ public final class DocumentsContract {
public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER";
/** {@hide} */
- public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME";
+ @Deprecated
+ public static final String EXTRA_PACKAGE_NAME = Intent.EXTRA_PACKAGE_NAME;
/** {@hide} */
public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 82b66d7b7925..a6a6f35d5ea9 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4161,7 +4161,8 @@ public final class Settings {
NOTIFICATION_VIBRATION_INTENSITY,
HAPTIC_FEEDBACK_INTENSITY,
DISPLAY_COLOR_MODE,
- ALARM_ALERT
+ ALARM_ALERT,
+ NOTIFICATION_LIGHT_PULSE,
};
/**
@@ -4364,6 +4365,7 @@ public final class Settings {
VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
VALIDATORS.put(SHOW_BATTERY_PERCENT, SHOW_BATTERY_PERCENT_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, BOOLEAN_VALIDATOR);
}
/**
@@ -8019,6 +8021,8 @@ public final class Settings {
MANUAL_RINGER_TOGGLE_COUNT,
HUSH_GESTURE_USED,
IN_CALL_NOTIFICATION_ENABLED,
+ LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
+ LOCK_SCREEN_SHOW_NOTIFICATIONS,
};
/**
@@ -8161,6 +8165,8 @@ public final class Settings {
VALIDATORS.put(HUSH_GESTURE_USED, HUSH_GESTURE_USED_VALIDATOR);
VALIDATORS.put(MANUAL_RINGER_TOGGLE_COUNT, MANUAL_RINGER_TOGGLE_COUNT_VALIDATOR);
VALIDATORS.put(IN_CALL_NOTIFICATION_ENABLED, IN_CALL_NOTIFICATION_ENABLED_VALIDATOR);
+ VALIDATORS.put(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(LOCK_SCREEN_SHOW_NOTIFICATIONS, BOOLEAN_VALIDATOR);
}
/**
diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java
index 7348cf6848f9..0d94af4bc828 100644
--- a/core/java/android/service/notification/Adjustment.java
+++ b/core/java/android/service/notification/Adjustment.java
@@ -65,6 +65,12 @@ public final class Adjustment implements Parcelable {
public static final String KEY_USER_SENTIMENT = "key_user_sentiment";
/**
+ * Data type: ArrayList of {@link android.app.Notification.Action}.
+ * Used to suggest extra actions for a notification.
+ */
+ public static final String KEY_SMART_ACTIONS = "key_smart_actions";
+
+ /**
* Create a notification adjustment.
*
* @param pkg The package of the notification.
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index a7d70d01b04a..09425a9c5d28 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -1426,6 +1426,7 @@ public abstract class NotificationListenerService extends Service {
private boolean mShowBadge;
private @UserSentiment int mUserSentiment = USER_SENTIMENT_NEUTRAL;
private boolean mHidden;
+ private ArrayList<Notification.Action> mSmartActions;
public Ranking() {}
@@ -1556,6 +1557,13 @@ public abstract class NotificationListenerService extends Service {
}
/**
+ * @hide
+ */
+ public List<Notification.Action> getSmartActions() {
+ return mSmartActions;
+ }
+
+ /**
* Returns whether this notification can be displayed as a badge.
*
* @return true if the notification can be displayed as a badge, false otherwise.
@@ -1583,7 +1591,7 @@ public abstract class NotificationListenerService extends Service {
CharSequence explanation, String overrideGroupKey,
NotificationChannel channel, ArrayList<String> overridePeople,
ArrayList<SnoozeCriterion> snoozeCriteria, boolean showBadge,
- int userSentiment, boolean hidden) {
+ int userSentiment, boolean hidden, ArrayList<Notification.Action> smartActions) {
mKey = key;
mRank = rank;
mIsAmbient = importance < NotificationManager.IMPORTANCE_LOW;
@@ -1599,6 +1607,7 @@ public abstract class NotificationListenerService extends Service {
mShowBadge = showBadge;
mUserSentiment = userSentiment;
mHidden = hidden;
+ mSmartActions = smartActions;
}
/**
@@ -1648,6 +1657,7 @@ public abstract class NotificationListenerService extends Service {
private ArrayMap<String, Boolean> mShowBadge;
private ArrayMap<String, Integer> mUserSentiment;
private ArrayMap<String, Boolean> mHidden;
+ private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions;
private RankingMap(NotificationRankingUpdate rankingUpdate) {
mRankingUpdate = rankingUpdate;
@@ -1676,7 +1686,7 @@ public abstract class NotificationListenerService extends Service {
getVisibilityOverride(key), getSuppressedVisualEffects(key),
getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
- getShowBadge(key), getUserSentiment(key), getHidden(key));
+ getShowBadge(key), getUserSentiment(key), getHidden(key), getSmartActions(key));
return rank >= 0;
}
@@ -1814,6 +1824,15 @@ public abstract class NotificationListenerService extends Service {
return hidden == null ? false : hidden.booleanValue();
}
+ private ArrayList<Notification.Action> getSmartActions(String key) {
+ synchronized (this) {
+ if (mSmartActions == null) {
+ buildSmartActions();
+ }
+ }
+ return mSmartActions.get(key);
+ }
+
// Locked by 'this'
private void buildRanksLocked() {
String[] orderedKeys = mRankingUpdate.getOrderedKeys();
@@ -1931,6 +1950,15 @@ public abstract class NotificationListenerService extends Service {
}
}
+ // Locked by 'this'
+ private void buildSmartActions() {
+ Bundle smartActions = mRankingUpdate.getSmartActions();
+ mSmartActions = new ArrayMap<>(smartActions.size());
+ for (String key : smartActions.keySet()) {
+ mSmartActions.put(key, smartActions.getParcelableArrayList(key));
+ }
+ }
+
// ----------- Parcelable
@Override
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index 00c47ec0ee89..bed221494d4e 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -37,12 +37,13 @@ public class NotificationRankingUpdate implements Parcelable {
private final Bundle mShowBadge;
private final Bundle mUserSentiment;
private final Bundle mHidden;
+ private final Bundle mSmartActions;
public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
Bundle visibilityOverrides, Bundle suppressedVisualEffects,
int[] importance, Bundle explanation, Bundle overrideGroupKeys,
Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
- Bundle showBadge, Bundle userSentiment, Bundle hidden) {
+ Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions) {
mKeys = keys;
mInterceptedKeys = interceptedKeys;
mVisibilityOverrides = visibilityOverrides;
@@ -56,6 +57,7 @@ public class NotificationRankingUpdate implements Parcelable {
mShowBadge = showBadge;
mUserSentiment = userSentiment;
mHidden = hidden;
+ mSmartActions = smartActions;
}
public NotificationRankingUpdate(Parcel in) {
@@ -73,6 +75,7 @@ public class NotificationRankingUpdate implements Parcelable {
mShowBadge = in.readBundle();
mUserSentiment = in.readBundle();
mHidden = in.readBundle();
+ mSmartActions = in.readBundle();
}
@Override
@@ -95,6 +98,7 @@ public class NotificationRankingUpdate implements Parcelable {
out.writeBundle(mShowBadge);
out.writeBundle(mUserSentiment);
out.writeBundle(mHidden);
+ out.writeBundle(mSmartActions);
}
public static final Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -159,4 +163,8 @@ public class NotificationRankingUpdate implements Parcelable {
public Bundle getHidden() {
return mHidden;
}
+
+ public Bundle getSmartActions() {
+ return mSmartActions;
+ }
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 5546e803342b..df88e642a970 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1007,6 +1007,24 @@ public class ZenModeConfig implements Parcelable {
return true;
}
+ /**
+ * Returns whether the conditionId is a valid ScheduleCondition.
+ * If allowNever is true, this will return true even if the ScheduleCondition never occurs.
+ */
+ public static boolean isValidScheduleConditionId(Uri conditionId, boolean allowNever) {
+ ScheduleInfo info;
+ try {
+ info = tryParseScheduleConditionId(conditionId);
+ } catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
+ return false;
+ }
+
+ if (info == null || (!allowNever && (info.days == null || info.days.length == 0))) {
+ return false;
+ }
+ return true;
+ }
+
public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
final boolean isSchedule = conditionId != null
&& conditionId.getScheme().equals(Condition.SCHEME)
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 6b2f80241df3..dde4c1d2801f 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2091,6 +2091,25 @@ public class TextUtils {
return (T) text.subSequence(0, size);
}
+ /**
+ * Trims the {@code text} to the first {@code size} characters and adds an ellipsis if the
+ * resulting string is shorter than the input. This will result in an output string which is
+ * longer than {@code size} for most inputs.
+ *
+ * @param size length of the result, should be greater than 0
+ *
+ * @hide
+ */
+ @Nullable
+ public static <T extends CharSequence> T trimToLengthWithEllipsis(@Nullable T text,
+ @IntRange(from = 1) int size) {
+ T trimmed = trimToSize(text, size);
+ if (trimmed.length() < text.length()) {
+ trimmed = (T) (trimmed.toString() + "...");
+ }
+ return trimmed;
+ }
+
private static Object sLock = new Object();
private static char[] sTemp = null;
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index f3d39de18507..08cbbe628eeb 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -35,6 +35,7 @@ import android.widget.TextView;
import com.android.i18n.phonenumbers.PhoneNumberMatch;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.PhoneNumberUtil.Leniency;
+import com.android.internal.annotations.GuardedBy;
import libcore.util.EmptyArray;
@@ -63,6 +64,10 @@ import java.util.regex.Pattern;
* does not have a URL scheme prefix, the supplied scheme will be prepended to
* create <code>http://example.com</code> when the clickable URL link is
* created.
+ *
+ * @see MatchFilter
+ * @see TransformFilter
+ * @see UrlSpanFactory
*/
public class Linkify {
@@ -218,6 +223,44 @@ public class Linkify {
}
/**
+ * Factory class to create {@link URLSpan}s. While adding spans to a {@link Spannable},
+ * {@link Linkify} will call {@link UrlSpanFactory#create(String)} function to create a
+ * {@link URLSpan}.
+ *
+ * @see #addLinks(Spannable, int, UrlSpanFactory)
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
+ */
+ public static class UrlSpanFactory {
+ private static final Object sInstanceLock = new Object();
+
+ @GuardedBy("sInstanceLock")
+ private static volatile UrlSpanFactory sInstance = null;
+
+ private static synchronized UrlSpanFactory getInstance() {
+ if (sInstance == null) {
+ synchronized (sInstanceLock) {
+ if (sInstance == null) {
+ sInstance = new UrlSpanFactory();
+ }
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Factory function that will called by {@link Linkify} in order to create a
+ * {@link URLSpan}.
+ *
+ * @param url URL found
+ * @return a URLSpan instance
+ */
+ public URLSpan create(final String url) {
+ return new URLSpan(url);
+ }
+ }
+
+ /**
* Scans the text of the provided Spannable and turns all occurrences
* of the link types indicated in the mask into clickable links.
* If the mask is nonzero, it also removes any existing URLSpans
@@ -228,24 +271,55 @@ public class Linkify {
* @param mask Mask to define which kinds of links will be searched.
*
* @return True if at least one link is found and applied.
+ *
+ * @see #addLinks(Spannable, int, UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask) {
- return addLinks(text, mask, null);
+ return addLinks(text, mask, null, null);
}
+ /**
+ * Scans the text of the provided Spannable and turns all occurrences
+ * of the link types indicated in the mask into clickable links.
+ * If the mask is nonzero, it also removes any existing URLSpans
+ * attached to the Spannable, to avoid problems if you call it
+ * repeatedly on the same text.
+ *
+ * @param text Spannable whose text is to be marked-up with links
+ * @param mask mask to define which kinds of links will be searched
+ * @param urlSpanFactory factory class used to create {@link URLSpan}s
+ * @return True if at least one link is found and applied.
+ */
+ public static final boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
+ @Nullable UrlSpanFactory urlSpanFactory) {
+ return addLinks(text, mask, null, urlSpanFactory);
+ }
+
+ /**
+ * Scans the text of the provided Spannable and turns all occurrences of the link types
+ * indicated in the mask into clickable links. If the mask is nonzero, it also removes any
+ * existing URLSpans attached to the Spannable, to avoid problems if you call it repeatedly
+ * on the same text.
+ *
+ * @param text Spannable whose text is to be marked-up with links
+ * @param mask mask to define which kinds of links will be searched
+ * @param context Context to be used while identifying phone numbers
+ * @param urlSpanFactory factory class used to create {@link URLSpan}s
+ * @return true if at least one link is found and applied.
+ */
private static boolean addLinks(@NonNull Spannable text, @LinkifyMask int mask,
- @Nullable Context context) {
+ @Nullable Context context, @Nullable UrlSpanFactory urlSpanFactory) {
if (mask == 0) {
return false;
}
- URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
+ final URLSpan[] old = text.getSpans(0, text.length(), URLSpan.class);
for (int i = old.length - 1; i >= 0; i--) {
text.removeSpan(old[i]);
}
- ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
+ final ArrayList<LinkSpec> links = new ArrayList<LinkSpec>();
if ((mask & WEB_URLS) != 0) {
gatherLinks(links, text, Patterns.AUTOLINK_WEB_URL,
@@ -274,7 +348,7 @@ public class Linkify {
}
for (LinkSpec link: links) {
- applyLink(link.url, link.start, link.end, text);
+ applyLink(link.url, link.start, link.end, text, urlSpanFactory);
}
return true;
@@ -290,6 +364,8 @@ public class Linkify {
* @param mask Mask to define which kinds of links will be searched.
*
* @return True if at least one link is found and applied.
+ *
+ * @see #addLinks(Spannable, int, UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull TextView text, @LinkifyMask int mask) {
if (mask == 0) {
@@ -299,7 +375,7 @@ public class Linkify {
final Context context = text.getContext();
final CharSequence t = text.getText();
if (t instanceof Spannable) {
- if (addLinks((Spannable) t, mask, context)) {
+ if (addLinks((Spannable) t, mask, context, null)) {
addLinkMovementMethod(text);
return true;
}
@@ -308,7 +384,7 @@ public class Linkify {
} else {
SpannableString s = SpannableString.valueOf(t);
- if (addLinks(s, mask, context)) {
+ if (addLinks(s, mask, context, null)) {
addLinkMovementMethod(text);
text.setText(s);
@@ -403,6 +479,8 @@ public class Linkify {
* @param pattern Regex pattern to be used for finding links
* @param scheme URL scheme string (eg <code>http://</code>) to be
* prepended to the links that do not start with this scheme.
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable text, @NonNull Pattern pattern,
@Nullable String scheme) {
@@ -423,6 +501,8 @@ public class Linkify {
* @param transformFilter Filter to allow the client code to update the link found.
*
* @return True if at least one link is found and applied.
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
@Nullable String scheme, @Nullable MatchFilter matchFilter,
@@ -446,10 +526,39 @@ public class Linkify {
* @param transformFilter Filter to allow the client code to update the link found.
*
* @return True if at least one link is found and applied.
+ *
+ * @see #addLinks(Spannable, Pattern, String, String[], MatchFilter, TransformFilter,
+ * UrlSpanFactory)
*/
public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
- @Nullable String defaultScheme, @Nullable String[] schemes,
+ @Nullable String defaultScheme, @Nullable String[] schemes,
@Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter) {
+ return addLinks(spannable, pattern, defaultScheme, schemes, matchFilter, transformFilter,
+ null);
+ }
+
+ /**
+ * Applies a regex to a Spannable turning the matches into links.
+ *
+ * @param spannable spannable whose text is to be marked-up with links.
+ * @param pattern regex pattern to be used for finding links.
+ * @param defaultScheme the default scheme to be prepended to links if the link does not
+ * start with one of the <code>schemes</code> given.
+ * @param schemes array of schemes (eg <code>http://</code>) to check if the link found
+ * contains a scheme. Passing a null or empty value means prepend
+ * defaultScheme
+ * to all links.
+ * @param matchFilter the filter that is used to allow the client code additional control
+ * over which pattern matches are to be converted into links.
+ * @param transformFilter filter to allow the client code to update the link found.
+ * @param urlSpanFactory factory class used to create {@link URLSpan}s
+ *
+ * @return True if at least one link is found and applied.
+ */
+ public static final boolean addLinks(@NonNull Spannable spannable, @NonNull Pattern pattern,
+ @Nullable String defaultScheme, @Nullable String[] schemes,
+ @Nullable MatchFilter matchFilter, @Nullable TransformFilter transformFilter,
+ @Nullable UrlSpanFactory urlSpanFactory) {
final String[] schemesCopy;
if (defaultScheme == null) defaultScheme = "";
if (schemes == null || schemes.length < 1) {
@@ -478,7 +587,7 @@ public class Linkify {
if (allowed) {
String url = makeUrl(m.group(0), schemesCopy, m, transformFilter);
- applyLink(url, start, end, spannable);
+ applyLink(url, start, end, spannable, urlSpanFactory);
hasMatches = true;
}
}
@@ -486,9 +595,12 @@ public class Linkify {
return hasMatches;
}
- private static final void applyLink(String url, int start, int end, Spannable text) {
- URLSpan span = new URLSpan(url);
-
+ private static void applyLink(String url, int start, int end, Spannable text,
+ @Nullable UrlSpanFactory urlSpanFactory) {
+ if (urlSpanFactory == null) {
+ urlSpanFactory = UrlSpanFactory.getInstance();
+ }
+ final URLSpan span = urlSpanFactory.create(url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index b2e24c357e3c..72865ccdc71a 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -187,6 +187,21 @@ public final class MathUtils {
}
/**
+ * Perform Hermite interpolation between two values.
+ * Eg:
+ * smoothStep(0, 0.5f, 0.5f) = 1f
+ * smoothStep(0, 0.5f, 0.25f) = 0.5f
+ *
+ * @param start Left edge.
+ * @param end Right edge.
+ * @param x A value between {@code start} and {@code end}.
+ * @return A number between 0 and 1 representing where {@code x} is in the interpolation.
+ */
+ public static float smoothStep(float start, float end, float x) {
+ return constrain((x - start) / (end - start), 0f, 1f);
+ }
+
+ /**
* Returns the sum of the two parameters, or throws an exception if the resulting sum would
* cause an overflow or underflow.
* @throws IllegalArgumentException when overflow or underflow would occur.
diff --git a/core/java/android/util/Xml.java b/core/java/android/util/Xml.java
index 041e8a85aafe..e3b8fec3559e 100644
--- a/core/java/android/util/Xml.java
+++ b/core/java/android/util/Xml.java
@@ -16,27 +16,27 @@
package android.util;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Reader;
-import java.io.StringReader;
-import java.io.UnsupportedEncodingException;
-import org.apache.harmony.xml.ExpatReader;
-import org.kxml2.io.KXmlParser;
+import libcore.util.XmlObjectFactory;
+
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.UnsupportedEncodingException;
+
/**
* XML utility methods.
*/
public class Xml {
- /** @hide */ public Xml() {}
+ private Xml() {}
/**
* {@link org.xmlpull.v1.XmlPullParser} "relaxed" feature name.
@@ -52,7 +52,7 @@ public class Xml {
public static void parse(String xml, ContentHandler contentHandler)
throws SAXException {
try {
- XMLReader reader = new ExpatReader();
+ XMLReader reader = XmlObjectFactory.newXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(new StringReader(xml)));
} catch (IOException e) {
@@ -66,7 +66,7 @@ public class Xml {
*/
public static void parse(Reader in, ContentHandler contentHandler)
throws IOException, SAXException {
- XMLReader reader = new ExpatReader();
+ XMLReader reader = XmlObjectFactory.newXMLReader();
reader.setContentHandler(contentHandler);
reader.parse(new InputSource(in));
}
@@ -77,7 +77,7 @@ public class Xml {
*/
public static void parse(InputStream in, Encoding encoding,
ContentHandler contentHandler) throws IOException, SAXException {
- XMLReader reader = new ExpatReader();
+ XMLReader reader = XmlObjectFactory.newXMLReader();
reader.setContentHandler(contentHandler);
InputSource source = new InputSource(in);
source.setEncoding(encoding.expatName);
@@ -89,7 +89,7 @@ public class Xml {
*/
public static XmlPullParser newPullParser() {
try {
- KXmlParser parser = new KXmlParser();
+ XmlPullParser parser = XmlObjectFactory.newXmlPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
return parser;
@@ -102,25 +102,7 @@ public class Xml {
* Creates a new xml serializer.
*/
public static XmlSerializer newSerializer() {
- try {
- return XmlSerializerFactory.instance.newSerializer();
- } catch (XmlPullParserException e) {
- throw new AssertionError(e);
- }
- }
-
- /** Factory for xml serializers. Initialized on demand. */
- static class XmlSerializerFactory {
- static final String TYPE
- = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
- static final XmlPullParserFactory instance;
- static {
- try {
- instance = XmlPullParserFactory.newInstance(TYPE, null);
- } catch (XmlPullParserException e) {
- throw new AssertionError(e);
- }
- }
+ return XmlObjectFactory.newXmlSerializer();
}
/**
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index e10eeb0457be..e0df59f57b31 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Matrix;
@@ -29,6 +30,9 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* <p>A display list records a series of graphics related operations and can replay
* them later. Display lists are usually built by recording operations on a
@@ -449,6 +453,25 @@ public class RenderNode {
return nSetHasOverlappingRendering(mNativeRenderNode, hasOverlappingRendering);
}
+ /** @hide */
+ @IntDef({USAGE_BACKGROUND})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageHint {}
+
+ /** The default usage hint */
+ public static final int USAGE_UNKNOWN = 0;
+
+ /** Usage is background content */
+ public static final int USAGE_BACKGROUND = 1;
+
+ /**
+ * Provides a hint on what this RenderNode's display list content contains. This hint is used
+ * for automatic content transforms to improve accessibility or similar.
+ */
+ public void setUsageHint(@UsageHint int usageHint) {
+ nSetUsageHint(mNativeRenderNode, usageHint);
+ }
+
/**
* Indicates whether the content of this display list overlaps.
*
@@ -948,6 +971,8 @@ public class RenderNode {
private static native boolean nSetHasOverlappingRendering(long renderNode,
boolean hasOverlappingRendering);
@CriticalNative
+ private static native void nSetUsageHint(long renderNode, int usageHint);
+ @CriticalNative
private static native boolean nSetElevation(long renderNode, float lift);
@CriticalNative
private static native boolean nSetTranslationX(long renderNode, float translationX);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 546ea87e23a2..e2e2b000d2fd 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -7547,7 +7547,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
- && !TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+ && isAccessibilityPane()) {
event.getText().add(getAccessibilityPaneTitle());
}
}
@@ -12963,7 +12963,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
}
- if (!TextUtils.isEmpty(getAccessibilityPaneTitle())) {
+ if (isAccessibilityPane()) {
if (isVisible != oldVisible) {
notifyViewAccessibilityStateChangedIfNeeded(isVisible
? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
@@ -20442,6 +20442,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private RenderNode getDrawableRenderNode(Drawable drawable, RenderNode renderNode) {
if (renderNode == null) {
renderNode = RenderNode.create(drawable.getClass().getName(), this);
+ renderNode.setUsageHint(RenderNode.USAGE_BACKGROUND);
}
final Rect bounds = drawable.getBounds();
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index e8e653759a97..eee3630c2266 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -601,22 +601,14 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int BOOLEAN_PROPERTY_CHECKED = 0x00000002;
- private static final int BOOLEAN_PROPERTY_FOCUSABLE = 0x00000004;
-
private static final int BOOLEAN_PROPERTY_FOCUSED = 0x00000008;
private static final int BOOLEAN_PROPERTY_SELECTED = 0x00000010;
- private static final int BOOLEAN_PROPERTY_CLICKABLE = 0x00000020;
-
- private static final int BOOLEAN_PROPERTY_LONG_CLICKABLE = 0x00000040;
-
private static final int BOOLEAN_PROPERTY_ENABLED = 0x00000080;
private static final int BOOLEAN_PROPERTY_PASSWORD = 0x00000100;
- private static final int BOOLEAN_PROPERTY_SCROLLABLE = 0x00000200;
-
private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 0x00000400;
private static final int BOOLEAN_PROPERTY_VISIBLE_TO_USER = 0x00000800;
@@ -631,8 +623,6 @@ public class AccessibilityNodeInfo implements Parcelable {
private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000;
- private static final int BOOLEAN_PROPERTY_CONTEXT_CLICKABLE = 0x00020000;
-
private static final int BOOLEAN_PROPERTY_IMPORTANCE = 0x0040000;
private static final int BOOLEAN_PROPERTY_SCREEN_READER_FOCUSABLE = 0x0080000;
@@ -1168,6 +1158,10 @@ public class AccessibilityNodeInfo implements Parcelable {
mActions.add(action);
}
+ private boolean hasActionWithId(int actionId) {
+ return getActionList().stream().anyMatch(action -> action.getId() == actionId);
+ }
+
/**
* Adds an action that can be performed on the node.
* <p>
@@ -1767,7 +1761,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is focusable.
*/
public boolean isFocusable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE);
+ return hasActionWithId(ACTION_FOCUS);
}
/**
@@ -1781,10 +1775,11 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param focusable True if the node is focusable.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_FOCUS}
*/
- public void setFocusable(boolean focusable) {
- setBooleanProperty(BOOLEAN_PROPERTY_FOCUSABLE, focusable);
- }
+ @Deprecated
+ public void setFocusable(boolean focusable) { }
/**
* Gets whether this node is focused.
@@ -1892,7 +1887,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is clickable.
*/
public boolean isClickable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE);
+ return hasActionWithId(ACTION_CLICK);
}
/**
@@ -1906,10 +1901,11 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param clickable True if the node is clickable.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_CLICK}
*/
- public void setClickable(boolean clickable) {
- setBooleanProperty(BOOLEAN_PROPERTY_CLICKABLE, clickable);
- }
+ @Deprecated
+ public void setClickable(boolean clickable) { }
/**
* Gets whether this node is long clickable.
@@ -1917,7 +1913,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is long clickable.
*/
public boolean isLongClickable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE);
+ return hasActionWithId(ACTION_LONG_CLICK);
}
/**
@@ -1931,10 +1927,11 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param longClickable True if the node is long clickable.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_LONG_CLICK}
*/
- public void setLongClickable(boolean longClickable) {
- setBooleanProperty(BOOLEAN_PROPERTY_LONG_CLICKABLE, longClickable);
- }
+ @Deprecated
+ public void setLongClickable(boolean longClickable) { }
/**
* Gets whether this node is enabled.
@@ -1992,7 +1989,13 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is scrollable, false otherwise.
*/
public boolean isScrollable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE);
+ return hasActionWithId(ACTION_SCROLL_BACKWARD)
+ || hasActionWithId(ACTION_SCROLL_FORWARD)
+ || hasActionWithId(R.id.accessibilityActionScrollToPosition)
+ || hasActionWithId(R.id.accessibilityActionScrollUp)
+ || hasActionWithId(R.id.accessibilityActionScrollDown)
+ || hasActionWithId(R.id.accessibilityActionScrollLeft)
+ || hasActionWithId(R.id.accessibilityActionScrollRight);
}
/**
@@ -2006,9 +2009,11 @@ public class AccessibilityNodeInfo implements Parcelable {
* @param scrollable True if the node is scrollable, false otherwise.
*
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
*/
+ @Deprecated
+
public void setScrollable(boolean scrollable) {
- setBooleanProperty(BOOLEAN_PROPERTY_SCROLLABLE, scrollable);
}
/**
@@ -2199,7 +2204,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return True if the node is context clickable.
*/
public boolean isContextClickable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE);
+ return hasActionWithId(R.id.accessibilityActionContextClick);
}
/**
@@ -2212,10 +2217,11 @@ public class AccessibilityNodeInfo implements Parcelable {
*
* @param contextClickable True if the node is context clickable.
* @throws IllegalStateException If called from an AccessibilityService.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_CONTEXT_CLICK}
*/
- public void setContextClickable(boolean contextClickable) {
- setBooleanProperty(BOOLEAN_PROPERTY_CONTEXT_CLICKABLE, contextClickable);
- }
+ @Deprecated
+ public void setContextClickable(boolean contextClickable) { }
/**
* Gets the node's live region mode.
@@ -2309,7 +2315,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return If the node can be dismissed.
*/
public boolean isDismissable() {
- return getBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE);
+ return hasActionWithId(ACTION_DISMISS);
}
/**
@@ -2321,10 +2327,11 @@ public class AccessibilityNodeInfo implements Parcelable {
* </p>
*
* @param dismissable If the node can be dismissed.
+ * @deprecated Use {@link #addAction(AccessibilityAction)}
+ * with {@link AccessibilityAction#ACTION_DISMISS}
*/
- public void setDismissable(boolean dismissable) {
- setBooleanProperty(BOOLEAN_PROPERTY_DISMISSABLE, dismissable);
- }
+ @Deprecated
+ public void setDismissable(boolean dismissable) { }
/**
* Returns whether the node originates from a view considered important for accessibility.
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 8f28102016d7..41daf9e11370 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -57,6 +57,7 @@ import android.view.accessibility.AccessibilityWindowInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
@@ -72,6 +73,8 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
//TODO: use java.lang.ref.Cleaner once Android supports Java 9
import sun.misc.Cleaner;
@@ -572,10 +575,11 @@ public final class AutofillManager {
final AutofillClient client = getClient();
if (client != null) {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- final boolean sessionWasRestored = mService.restoreSession(mSessionId,
- client.autofillClientGetActivityToken(),
- mServiceClient.asBinder());
+ mService.restoreSession(mSessionId, client.autofillClientGetActivityToken(),
+ mServiceClient.asBinder(), receiver);
+ final boolean sessionWasRestored = receiver.getIntResult() == 1;
if (!sessionWasRestored) {
Log.w(TAG, "Session " + mSessionId + " could not be restored");
@@ -691,7 +695,9 @@ public final class AutofillManager {
*/
@Nullable public FillEventHistory getFillEventHistory() {
try {
- return mService.getFillEventHistory();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getFillEventHistory(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1242,8 +1248,10 @@ public final class AutofillManager {
public boolean hasEnabledAutofillServices() {
if (mService == null) return false;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName());
+ mService.isServiceEnabled(mContext.getUserId(), mContext.getPackageName(), receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1257,8 +1265,10 @@ public final class AutofillManager {
public ComponentName getAutofillServiceComponentName() {
if (mService == null) return null;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.getAutofillServiceComponentName();
+ mService.getAutofillServiceComponentName(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1281,7 +1291,9 @@ public final class AutofillManager {
*/
@Nullable public String getUserDataId() {
try {
- return mService.getUserDataId();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getUserDataId(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1301,7 +1313,9 @@ public final class AutofillManager {
*/
@Nullable public UserData getUserData() {
try {
- return mService.getUserData();
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.getUserData(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_PARCELABLE);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1337,8 +1351,10 @@ public final class AutofillManager {
* the user.
*/
public boolean isFieldClassificationEnabled() {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isFieldClassificationEnabled();
+ mService.isFieldClassificationEnabled(receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return false;
@@ -1358,8 +1374,10 @@ public final class AutofillManager {
*/
@Nullable
public String getDefaultFieldClassificationAlgorithm() {
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.getDefaultFieldClassificationAlgorithm();
+ mService.getDefaultFieldClassificationAlgorithm(receiver);
+ return receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
return null;
@@ -1376,9 +1394,10 @@ public final class AutofillManager {
*/
@NonNull
public List<String> getAvailableFieldClassificationAlgorithms() {
- final String[] algorithms;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- algorithms = mService.getAvailableFieldClassificationAlgorithms();
+ mService.getAvailableFieldClassificationAlgorithms(receiver);
+ final String[] algorithms = receiver.getObjectResult(SyncResultReceiver.TYPE_STRING);
return algorithms != null ? Arrays.asList(algorithms) : Collections.emptyList();
} catch (RemoteException e) {
e.rethrowFromSystemServer();
@@ -1399,8 +1418,10 @@ public final class AutofillManager {
public boolean isAutofillSupported() {
if (mService == null) return false;
+ final SyncResultReceiver receiver = new SyncResultReceiver();
try {
- return mService.isServiceSupported(mContext.getUserId());
+ mService.isServiceSupported(mContext.getUserId(), receiver);
+ return receiver.getIntResult() == 1;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1521,10 +1542,12 @@ public final class AutofillManager {
final AutofillClient client = getClient();
if (client == null) return; // NOTE: getClient() already logged it..
- mSessionId = mService.startSession(client.autofillClientGetActivityToken(),
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.startSession(client.autofillClientGetActivityToken(),
mServiceClient.asBinder(), id, bounds, value, mContext.getUserId(),
mCallback != null, flags, client.autofillClientGetComponentName(),
- isCompatibilityModeEnabledLocked());
+ isCompatibilityModeEnabledLocked(), receiver);
+ mSessionId = receiver.getIntResult();
if (mSessionId != NO_SESSION) {
mState = STATE_ACTIVE;
}
@@ -1602,7 +1625,9 @@ public final class AutofillManager {
mServiceClient = new AutofillManagerClient(this);
try {
final int userId = mContext.getUserId();
- final int flags = mService.addClient(mServiceClient, userId);
+ final SyncResultReceiver receiver = new SyncResultReceiver();
+ mService.addClient(mServiceClient, userId, receiver);
+ final int flags = receiver.getIntResult();
mEnabled = (flags & FLAG_ADD_CLIENT_ENABLED) != 0;
sDebug = (flags & FLAG_ADD_CLIENT_DEBUG) != 0;
sVerbose = (flags & FLAG_ADD_CLIENT_VERBOSE) != 0;
@@ -1923,7 +1948,7 @@ public final class AutofillManager {
mFillableIds.add(id);
}
if (sVerbose) {
- Log.v(TAG, "setTrackedViews(): fillableIds=" + fillableIds
+ Log.v(TAG, "setTrackedViews(): fillableIds=" + Arrays.toString(fillableIds)
+ ", mFillableIds" + mFillableIds);
}
}
@@ -2818,4 +2843,104 @@ public final class AutofillManager {
}
}
}
+
+ /**
+ * @hide
+ */
+ public static final class SyncResultReceiver extends IResultReceiver.Stub {
+
+ private static final String EXTRA = "EXTRA";
+
+ /**
+ * How long to block waiting for {@link IResultReceiver} callbacks when calling server.
+ */
+ private static final long BINDER_TIMEOUT_MS = 5000;
+
+ private static final int TYPE_STRING = 0;
+ private static final int TYPE_STRING_ARRAY = 1;
+ private static final int TYPE_PARCELABLE = 2;
+
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private int mResult;
+ private Bundle mBundle;
+
+ private void waitResult() {
+ try {
+ if (!mLatch.await(BINDER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Not called in " + BINDER_TIMEOUT_MS + "ms");
+ }
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code int}.
+ */
+ int getIntResult() {
+ waitResult();
+ return mResult;
+ }
+
+ /**
+ * Gets the result from an operation that returns an {@code Object}.
+ *
+ * @param type type of expected object.
+ */
+ @Nullable
+ @SuppressWarnings("unchecked")
+ <T> T getObjectResult(int type) {
+ waitResult();
+ if (mBundle == null) {
+ return null;
+ }
+ switch (type) {
+ case TYPE_STRING:
+ return (T) mBundle.getString(EXTRA);
+ case TYPE_STRING_ARRAY:
+ return (T) mBundle.getString(EXTRA);
+ case TYPE_PARCELABLE:
+ return (T) mBundle.getParcelable(EXTRA);
+ default:
+ throw new IllegalArgumentException("unsupported type: " + type);
+ }
+ }
+
+ @Override
+ public void send(int resultCode, Bundle resultData) {
+ mResult = resultCode;
+ mBundle = resultData;
+ mLatch.countDown();
+ }
+
+ /**
+ * Creates a bundle for a {@code String} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String value) {
+ final Bundle bundle = new Bundle();
+ bundle.putString(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code String[]} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable String[] value) {
+ final Bundle bundle = new Bundle();
+ bundle.putStringArray(EXTRA, value);
+ return bundle;
+ }
+
+ /**
+ * Creates a bundle for a {@code Parcelable} value.
+ */
+ @NonNull
+ public static Bundle bundleFor(@Nullable Parcelable value) {
+ final Bundle bundle = new Bundle();
+ bundle.putParcelable(EXTRA, value);
+ return bundle;
+ }
+ }
}
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index 6b26f233f58c..26aeba5cfdfa 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -28,41 +28,39 @@ import android.service.autofill.UserData;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
+import com.android.internal.os.IResultReceiver;
/**
* Mediator between apps being auto-filled and auto-fill service implementations.
*
* {@hide}
*/
- // TODO(b/73536867) STOPSHIP : this whole interface should be either oneway or not, and we're
- // gradually converting the methods (as some of them return a value form the server and must be
- // refactored).
-interface IAutoFillManager {
+oneway interface IAutoFillManager {
// Returns flags: FLAG_ADD_CLIENT_ENABLED | FLAG_ADD_CLIENT_DEBUG | FLAG_ADD_CLIENT_VERBOSE
- int addClient(in IAutoFillManagerClient client, int userId);
+ void addClient(in IAutoFillManagerClient client, int userId, in IResultReceiver result);
void removeClient(in IAutoFillManagerClient client, int userId);
- int startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
- in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
- in ComponentName componentName, boolean compatMode);
- FillEventHistory getFillEventHistory();
- boolean restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback);
- oneway void updateSession(int sessionId, in AutofillId id, in Rect bounds,
- in AutofillValue value, int action, int flags, int userId);
- oneway void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
- oneway void finishSession(int sessionId, int userId);
- oneway void cancelSession(int sessionId, int userId);
- oneway void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId,
- int userId);
- oneway void setHasCallback(int sessionId, int userId, boolean hasIt);
+ void startSession(IBinder activityToken, in IBinder appCallback, in AutofillId autoFillId,
+ in Rect bounds, in AutofillValue value, int userId, boolean hasCallback, int flags,
+ in ComponentName componentName, boolean compatMode, in IResultReceiver result);
+ void getFillEventHistory(in IResultReceiver result);
+ void restoreSession(int sessionId, in IBinder activityToken, in IBinder appCallback,
+ in IResultReceiver result);
+ void updateSession(int sessionId, in AutofillId id, in Rect bounds,
+ in AutofillValue value, int action, int flags, int userId);
+ void setAutofillFailure(int sessionId, in List<AutofillId> ids, int userId);
+ void finishSession(int sessionId, int userId);
+ void cancelSession(int sessionId, int userId);
+ void setAuthenticationResult(in Bundle data, int sessionId, int authenticationId, int userId);
+ void setHasCallback(int sessionId, int userId, boolean hasIt);
void disableOwnedAutofillServices(int userId);
- boolean isServiceSupported(int userId);
- boolean isServiceEnabled(int userId, String packageName);
+ void isServiceSupported(int userId, in IResultReceiver result);
+ void isServiceEnabled(int userId, String packageName, in IResultReceiver result);
void onPendingSaveUi(int operation, IBinder token);
- UserData getUserData();
- String getUserDataId();
+ void getUserData(in IResultReceiver result);
+ void getUserDataId(in IResultReceiver result);
void setUserData(in UserData userData);
- boolean isFieldClassificationEnabled();
- ComponentName getAutofillServiceComponentName();
- String[] getAvailableFieldClassificationAlgorithms();
- String getDefaultFieldClassificationAlgorithm();
+ void isFieldClassificationEnabled(in IResultReceiver result);
+ void getAutofillServiceComponentName(in IResultReceiver result);
+ void getAvailableFieldClassificationAlgorithms(in IResultReceiver result);
+ void getDefaultFieldClassificationAlgorithm(in IResultReceiver result);
}
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index e73ec1c40d19..e5d6556e1218 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -21,37 +21,143 @@ import android.os.Parcel;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArrayMap;
+import android.util.Slog;
import android.util.TimeUtils;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Objects;
public final class AssociationState {
private static final String TAG = "ProcessStats";
private static final boolean DEBUG = false;
- private final String mPackage;
+ private final ProcessStats mProcessStats;
+ private final ProcessStats.PackageState mPackageState;
private final String mProcessName;
private final String mName;
private final DurationsTable mDurations;
public final class SourceState {
+ final SourceKey mKey;
+ int mProcStateSeq = -1;
+ int mProcState = ProcessStats.STATE_NOTHING;
+ boolean mInTrackingList;
+ int mNesting;
+ int mCount;
+ long mStartUptime;
+ long mDuration;
+ long mTrackingUptime;
+ int mActiveCount;
+ long mActiveStartUptime;
+ long mActiveDuration;
+
+ SourceState(SourceKey key) {
+ mKey = key;
+ }
+
+ public AssociationState getAssociationState() {
+ return AssociationState.this;
+ }
+
+ public String getProcessName() {
+ return mKey.mProcess;
+ }
+
+ public int getUid() {
+ return mKey.mUid;
+ }
+
+ public void trackProcState(int procState, int seq, long now) {
+ procState = ProcessState.PROCESS_STATE_TO_STATE[procState];
+ if (seq != mProcStateSeq) {
+ mProcStateSeq = seq;
+ mProcState = procState;
+ } else if (procState < mProcState) {
+ mProcState = procState;
+ }
+ if (procState < ProcessStats.STATE_HOME) {
+ if (!mInTrackingList) {
+ mInTrackingList = true;
+ mTrackingUptime = now;
+ mProcessStats.mTrackingAssociations.add(this);
+ }
+ } else {
+ stopTracking(now);
+ }
+ }
+
public void stop() {
mNesting--;
if (mNesting == 0) {
- mDuration += SystemClock.uptimeMillis() - mStartTime;
+ mDuration += SystemClock.uptimeMillis() - mStartUptime;
mNumActive--;
+ stopTracking(SystemClock.uptimeMillis());
}
}
- int mNesting;
- int mCount;
- long mStartTime;
- long mDuration;
+ void startActive(long now) {
+ if (mInTrackingList) {
+ if (mActiveStartUptime == 0) {
+ mActiveStartUptime = now;
+ mActiveCount++;
+ }
+ } else {
+ Slog.wtf(TAG, "startActive while not tracking: " + this);
+ }
+ }
+
+ void stopActive(long now) {
+ if (mActiveStartUptime != 0) {
+ if (!mInTrackingList) {
+ Slog.wtf(TAG, "stopActive while not tracking: " + this);
+ }
+ mActiveDuration += now - mActiveStartUptime;
+ mActiveStartUptime = 0;
+ }
+ }
+
+ void stopTracking(long now) {
+ stopActive(now);
+ if (mInTrackingList) {
+ mInTrackingList = false;
+ // Do a manual search for where to remove, since these objects will typically
+ // be towards the end of the array.
+ final ArrayList<SourceState> list = mProcessStats.mTrackingAssociations;
+ for (int i = list.size() - 1; i >= 0; i--) {
+ if (list.get(i) == this) {
+ list.remove(i);
+ return;
+ }
+ }
+ Slog.wtf(TAG, "Stop tracking didn't find in tracking list: " + this);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("SourceState{").append(Integer.toHexString(System.identityHashCode(this)))
+ .append(" ").append(mKey.mProcess).append("/").append(mKey.mUid);
+ if (mProcState != ProcessStats.STATE_NOTHING) {
+ sb.append(" ").append(DumpUtils.STATE_NAMES[mProcState]).append(" #")
+ .append(mProcStateSeq);
+ }
+ sb.append("}");
+ return sb.toString();
+ }
}
- final static class SourceKey {
+ private final static class SourceKey {
+ /**
+ * UID, consider this final. Not final just to avoid a temporary object during lookup.
+ */
int mUid;
+
+ /**
+ * Process name, consider this final. Not final just to avoid a temporary object during
+ * lookup.
+ */
String mProcess;
SourceKey(int uid, String process) {
@@ -82,7 +188,6 @@ public final class AssociationState {
sb.append('}');
return sb.toString();
}
-
}
/**
@@ -92,18 +197,26 @@ public final class AssociationState {
private final SourceKey mTmpSourceKey = new SourceKey(0, null);
+ private ProcessState mProc;
+
private int mNumActive;
- public AssociationState(ProcessStats processStats, String pkg, String name,
- String processName) {
- mPackage = pkg;
+ public AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState,
+ String name, String processName, ProcessState proc) {
+ mProcessStats = processStats;
+ mPackageState = packageState;
mName = name;
mProcessName = processName;
mDurations = new DurationsTable(processStats.mTableData);
+ mProc = proc;
+ }
+
+ public int getUid() {
+ return mPackageState.mUid;
}
public String getPackage() {
- return mPackage;
+ return mPackageState.mPackageName;
}
public String getProcessName() {
@@ -114,18 +227,27 @@ public final class AssociationState {
return mName;
}
+ public ProcessState getProcess() {
+ return mProc;
+ }
+
+ public void setProcess(ProcessState proc) {
+ mProc = proc;
+ }
+
public SourceState startSource(int uid, String processName) {
mTmpSourceKey.mUid = uid;
mTmpSourceKey.mProcess = processName;
SourceState src = mSources.get(mTmpSourceKey);
if (src == null) {
- src = new SourceState();
- mSources.put(new SourceKey(uid, processName), src);
+ SourceKey key = new SourceKey(uid, processName);
+ src = new SourceState(key);
+ mSources.put(key, src);
}
src.mNesting++;
if (src.mNesting == 1) {
src.mCount++;
- src.mStartTime = SystemClock.uptimeMillis();
+ src.mStartUptime = SystemClock.uptimeMillis();
mNumActive++;
}
return src;
@@ -138,11 +260,13 @@ public final class AssociationState {
final SourceState otherSrc = other.mSources.valueAt(isrc);
SourceState mySrc = mSources.get(key);
if (mySrc == null) {
- mySrc = new SourceState();
+ mySrc = new SourceState(key);
mSources.put(key, mySrc);
}
mySrc.mCount += otherSrc.mCount;
mySrc.mDuration += otherSrc.mDuration;
+ mySrc.mActiveCount += otherSrc.mActiveCount;
+ mySrc.mActiveDuration += otherSrc.mActiveDuration;
}
}
@@ -160,8 +284,15 @@ public final class AssociationState {
SourceState src = mSources.valueAt(isrc);
if (src.mNesting > 0) {
src.mCount = 1;
- src.mStartTime = now;
+ src.mStartUptime = now;
src.mDuration = 0;
+ if (src.mActiveStartUptime > 0) {
+ src.mActiveCount = 1;
+ src.mActiveStartUptime = now;
+ } else {
+ src.mActiveCount = 0;
+ }
+ src.mActiveDuration = 0;
} else {
mSources.removeAt(isrc);
}
@@ -169,7 +300,7 @@ public final class AssociationState {
}
}
- public void writeToParcel(ProcessStats stats, Parcel out, long now) {
+ public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) {
mDurations.writeToParcel(out);
final int NSRC = mSources.size();
out.writeInt(NSRC);
@@ -180,9 +311,15 @@ public final class AssociationState {
stats.writeCommonString(out, key.mProcess);
out.writeInt(src.mCount);
out.writeLong(src.mDuration);
+ out.writeInt(src.mActiveCount);
+ out.writeLong(src.mActiveDuration);
}
}
+ /**
+ * Returns non-null if all else fine, else a String that describes the error that
+ * caused it to fail.
+ */
public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) {
if (!mDurations.readFromParcel(in)) {
return "Duration table corrupt";
@@ -195,21 +332,27 @@ public final class AssociationState {
final int uid = in.readInt();
final String procName = stats.readCommonString(in, parcelVersion);
final SourceKey key = new SourceKey(uid, procName);
- final SourceState src = new SourceState();
+ final SourceState src = new SourceState(key);
src.mCount = in.readInt();
src.mDuration = in.readLong();
+ src.mActiveCount = in.readInt();
+ src.mActiveDuration = in.readLong();
mSources.put(key, src);
}
return null;
}
- public void commitStateTime(long now) {
+ public void commitStateTime(long nowUptime) {
if (isInUse()) {
for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
SourceState src = mSources.valueAt(isrc);
if (src.mNesting > 0) {
- src.mDuration += now - src.mStartTime;
- src.mStartTime = now;
+ src.mDuration += nowUptime - src.mStartUptime;
+ src.mStartUptime = nowUptime;
+ }
+ if (src.mActiveStartUptime > 0) {
+ src.mActiveDuration += nowUptime - src.mActiveStartUptime;
+ src.mActiveStartUptime = nowUptime;
}
}
}
@@ -237,7 +380,7 @@ public final class AssociationState {
pw.print(src.mCount);
long duration = src.mDuration;
if (src.mNesting > 0) {
- duration += now - src.mStartTime;
+ duration += now - src.mStartUptime;
}
if (dumpAll) {
pw.print(" / Duration ");
@@ -248,9 +391,37 @@ public final class AssociationState {
}
DumpUtils.printPercent(pw, (double)duration/(double)totalTime);
if (src.mNesting > 0) {
- pw.print(" (running)");
+ pw.print(" (running");
+ if (src.mProcState != ProcessStats.STATE_NOTHING) {
+ pw.print(" / ");
+ pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
+ pw.print(" #");
+ pw.print(src.mProcStateSeq);
+ }
+ pw.print(")");
}
pw.println();
+ if (src.mActiveCount > 0) {
+ pw.print(prefixInner);
+ pw.print(" Active count ");
+ pw.print(src.mActiveCount);
+ duration = src.mActiveDuration;
+ if (src.mActiveStartUptime > 0) {
+ duration += now - src.mActiveStartUptime;
+ }
+ if (dumpAll) {
+ pw.print(" / Duration ");
+ TimeUtils.formatDuration(duration, pw);
+ pw.print(" / ");
+ } else {
+ pw.print(" / time ");
+ }
+ DumpUtils.printPercent(pw, (double)duration/(double)totalTime);
+ if (src.mActiveStartUptime > 0) {
+ pw.print(" (running)");
+ }
+ pw.println();
+ }
}
}
@@ -277,7 +448,15 @@ public final class AssociationState {
pw.print(src.mCount);
long duration = src.mDuration;
if (src.mNesting > 0) {
- duration += now - src.mStartTime;
+ duration += now - src.mStartUptime;
+ }
+ pw.print(",");
+ pw.print(duration);
+ pw.print(",");
+ pw.print(src.mActiveCount);
+ duration = src.mActiveDuration;
+ if (src.mActiveStartUptime > 0) {
+ duration += now - src.mActiveStartUptime;
}
pw.print(",");
pw.print(duration);
@@ -287,7 +466,7 @@ public final class AssociationState {
public String toString() {
return "AssociationState{" + Integer.toHexString(System.identityHashCode(this))
- + " " + mName + " pkg=" + mPackage + " proc="
- + Integer.toHexString(System.identityHashCode(this)) + "}";
+ + " " + mName + " pkg=" + mPackageState.mPackageName + " proc="
+ + Integer.toHexString(System.identityHashCode(mProc)) + "}";
}
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 5a08f62f218b..ad42288c25bb 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -71,7 +71,7 @@ public final class ProcessState {
private static final boolean DEBUG_PARCEL = false;
// Map from process states to the states we track.
- private static final int[] PROCESS_STATE_TO_STATE = new int[] {
+ static final int[] PROCESS_STATE_TO_STATE = new int[] {
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT
STATE_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI
STATE_TOP, // ActivityManager.PROCESS_STATE_TOP
@@ -129,7 +129,7 @@ public final class ProcessState {
private final PssTable mPssTable;
private ProcessState mCommonProcess;
- private int mCurState = STATE_NOTHING;
+ private int mCurCombinedState = STATE_NOTHING;
private long mStartTime;
private int mLastPssState = STATE_NOTHING;
@@ -180,7 +180,7 @@ public final class ProcessState {
mPackage = pkg;
mUid = uid;
mVersion = vers;
- mCurState = commonProcess.mCurState;
+ mCurCombinedState = commonProcess.mCurCombinedState;
mStartTime = now;
mDurations = new DurationsTable(commonProcess.mStats.mTableData);
mPssTable = new PssTable(commonProcess.mStats.mTableData);
@@ -324,7 +324,7 @@ public final class ProcessState {
public boolean isInUse() {
return mActive || mNumActiveServices > 0 || mNumStartedServices > 0
- || mCurState != STATE_NOTHING;
+ || mCurCombinedState != STATE_NOTHING;
}
public boolean isActive() {
@@ -333,7 +333,7 @@ public final class ProcessState {
public boolean hasAnyData() {
return !(mDurations.getKeyCount() == 0
- && mCurState == STATE_NOTHING
+ && mCurCombinedState == STATE_NOTHING
&& mPssTable.getKeyCount() == 0);
}
@@ -355,7 +355,7 @@ public final class ProcessState {
}
// First update the common process.
- mCommonProcess.setState(state, now);
+ mCommonProcess.setCombinedState(state, now);
// If the common process is not multi-package, there is nothing else to do.
if (!mCommonProcess.mMultiPackage) {
@@ -364,25 +364,29 @@ public final class ProcessState {
if (pkgList != null) {
for (int ip=pkgList.size()-1; ip>=0; ip--) {
- pullFixedProc(pkgList, ip).setState(state, now);
+ pullFixedProc(pkgList, ip).setCombinedState(state, now);
}
}
}
- public void setState(int state, long now) {
+ public void setCombinedState(int state, long now) {
ensureNotDead();
- if (!mDead && (mCurState != state)) {
+ if (!mDead && (mCurCombinedState != state)) {
//Slog.i(TAG, "Setting state in " + mName + "/" + mPackage + ": " + state);
commitStateTime(now);
- mCurState = state;
+ mCurCombinedState = state;
}
}
+ public int getCombinedState() {
+ return mCurCombinedState;
+ }
+
public void commitStateTime(long now) {
- if (mCurState != STATE_NOTHING) {
+ if (mCurCombinedState != STATE_NOTHING) {
long dur = now - mStartTime;
if (dur > 0) {
- mDurations.addDuration(mCurState, dur);
+ mDurations.addDuration(mCurCombinedState, dur);
}
}
mStartTime = now;
@@ -430,8 +434,8 @@ public final class ProcessState {
mCommonProcess.incStartedServices(memFactor, now, serviceName);
}
mNumStartedServices++;
- if (mNumStartedServices == 1 && mCurState == STATE_NOTHING) {
- setState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
+ if (mNumStartedServices == 1 && mCurCombinedState == STATE_NOTHING) {
+ setCombinedState(STATE_SERVICE_RESTARTING + (memFactor*STATE_COUNT), now);
}
}
@@ -446,8 +450,8 @@ public final class ProcessState {
mCommonProcess.decStartedServices(memFactor, now, serviceName);
}
mNumStartedServices--;
- if (mNumStartedServices == 0 && (mCurState%STATE_COUNT) == STATE_SERVICE_RESTARTING) {
- setState(STATE_NOTHING, now);
+ if (mNumStartedServices == 0 && (mCurCombinedState %STATE_COUNT) == STATE_SERVICE_RESTARTING) {
+ setCombinedState(STATE_NOTHING, now);
} else if (mNumStartedServices < 0) {
Slog.wtfStack(TAG, "Proc started services underrun: pkg="
+ mPackage + " uid=" + mUid + " name=" + mName);
@@ -481,16 +485,16 @@ public final class ProcessState {
break;
}
if (!always) {
- if (mLastPssState == mCurState && SystemClock.uptimeMillis()
+ if (mLastPssState == mCurCombinedState && SystemClock.uptimeMillis()
< (mLastPssTime+(30*1000))) {
return;
}
}
- mLastPssState = mCurState;
+ mLastPssState = mCurCombinedState;
mLastPssTime = SystemClock.uptimeMillis();
- if (mCurState != STATE_NOTHING) {
+ if (mCurCombinedState != STATE_NOTHING) {
// First update the common process.
- mCommonProcess.mPssTable.mergeStats(mCurState, 1, pss, pss, pss, uss, uss, uss,
+ mCommonProcess.mPssTable.mergeStats(mCurCombinedState, 1, pss, pss, pss, uss, uss, uss,
rss, rss, rss);
// If the common process is not multi-package, there is nothing else to do.
@@ -500,7 +504,7 @@ public final class ProcessState {
if (pkgList != null) {
for (int ip=pkgList.size()-1; ip>=0; ip--) {
- pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurState, 1,
+ pullFixedProc(pkgList, ip).mPssTable.mergeStats(mCurCombinedState, 1,
pss, pss, pss, uss, uss, uss, rss, rss, rss);
}
}
@@ -619,7 +623,7 @@ public final class ProcessState {
public long getDuration(int state, long now) {
long time = mDurations.getValueForId((byte)state);
- if (mCurState == state) {
+ if (mCurCombinedState == state) {
time += now - mStartTime;
}
return time;
@@ -724,7 +728,7 @@ public final class ProcessState {
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
long time = mDurations.getValue(key);
- if (mCurState == type) {
+ if (mCurCombinedState == type) {
time += now - mStartTime;
}
final int procState = type % STATE_COUNT;
@@ -827,7 +831,7 @@ public final class ProcessState {
final int bucket = ((iscreen + imem) * STATE_COUNT) + procStates[ip];
long time = mDurations.getValueForId((byte)bucket);
String running = "";
- if (mCurState == bucket) {
+ if (mCurCombinedState == bucket) {
running = " (running)";
}
if (time != 0) {
@@ -1177,14 +1181,14 @@ public final class ProcessState {
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
long time = mDurations.getValue(key);
- if (mCurState == type) {
+ if (mCurCombinedState == type) {
didCurState = true;
time += now - mStartTime;
}
DumpUtils.printProcStateTagAndValue(pw, type, time);
}
- if (!didCurState && mCurState != STATE_NOTHING) {
- DumpUtils.printProcStateTagAndValue(pw, mCurState, now - mStartTime);
+ if (!didCurState && mCurCombinedState != STATE_NOTHING) {
+ DumpUtils.printProcStateTagAndValue(pw, mCurCombinedState, now - mStartTime);
}
}
@@ -1251,14 +1255,14 @@ public final class ProcessState {
final int key = mDurations.getKeyAt(i);
final int type = SparseMappingTable.getIdFromKey(key);
long time = mDurations.getValue(key);
- if (mCurState == type) {
+ if (mCurCombinedState == type) {
didCurState = true;
time += now - mStartTime;
}
durationByState.put(type, time);
}
- if (!didCurState && mCurState != STATE_NOTHING) {
- durationByState.put(mCurState, now - mStartTime);
+ if (!didCurState && mCurCombinedState != STATE_NOTHING) {
+ durationByState.put(mCurCombinedState, now - mStartTime);
}
for (int i=0; i<mPssTable.getKeyCount(); i++) {
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 1f871d9f92b8..15f140e2ea2d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -16,6 +16,7 @@
package com.android.internal.app.procstats;
+import android.content.ComponentName;
import android.os.Debug;
import android.os.Parcel;
import android.os.Parcelable;
@@ -157,7 +158,7 @@ public final class ProcessStats implements Parcelable {
};
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 31;
+ private static final int PARCEL_VERSION = 32;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535454;
@@ -168,6 +169,8 @@ public final class ProcessStats implements Parcelable {
public final ProcessMap<LongSparseArray<PackageState>> mPackages = new ProcessMap<>();
public final ProcessMap<ProcessState> mProcesses = new ProcessMap<>();
+ public final ArrayList<AssociationState.SourceState> mTrackingAssociations = new ArrayList<>();
+
public final long[] mMemFactorDurations = new long[ADJ_COUNT];
public int mMemFactor = STATE_NOTHING;
public long mStartTime;
@@ -1203,8 +1206,8 @@ public final class ProcessStats implements Parcelable {
AssociationState asc = hadData
? pkgState.mAssociations.get(associationName) : null;
if (asc == null) {
- asc = new AssociationState(this, pkgName, associationName,
- processName);
+ asc = new AssociationState(this, pkgState, associationName,
+ processName, null);
}
String errorMsg = asc.readFromParcel(this, in, version);
if (errorMsg != null) {
@@ -1308,6 +1311,17 @@ public final class ProcessStats implements Parcelable {
Slog.d(TAG, "GETPROC leaving proc of " + ss);
}
}
+ // Also update active associations.
+ for (int i=commonPkgState.mAssociations.size()-1; i>=0; i--) {
+ AssociationState as = commonPkgState.mAssociations.valueAt(i);
+ if (as.getProcess() == commonProc) {
+ if (DEBUG) Slog.d(TAG, "GETPROC switching association to cloned: "
+ + as);
+ as.setProcess(cloned);
+ } else if (DEBUG) {
+ Slog.d(TAG, "GETPROC leaving proc of " + as);
+ }
+ }
} else {
Slog.w(TAG, "Cloning proc state: no package state " + commonProc.getPackage()
+ "/" + pkgState.mUid + " for proc " + commonProc.getName());
@@ -1356,12 +1370,42 @@ public final class ProcessStats implements Parcelable {
}
final ProcessState procs = processName != null
? getProcessStateLocked(packageName, uid, vers, processName) : null;
- as = new AssociationState(this, packageName, className, processName);
+ as = new AssociationState(this, pkgs, className, processName, procs);
pkgs.mAssociations.put(className, as);
if (DEBUG) Slog.d(TAG, "GETASC: creating " + as + " in " + procs);
return as;
}
+ public void updateTrackingAssociationsLocked(int curSeq, long now) {
+ final int NUM = mTrackingAssociations.size();
+ for (int i = NUM - 1; i >= 0; i--) {
+ final AssociationState.SourceState act = mTrackingAssociations.get(i);
+ if (act.mProcStateSeq != curSeq) {
+ act.mInTrackingList = false;
+ act.mProcState = STATE_NOTHING;
+ mTrackingAssociations.remove(i);
+ } else {
+ final ProcessState proc = act.getAssociationState().getProcess();
+ if (proc != null) {
+ final int procState = proc.getCombinedState() % STATE_COUNT;
+ if (act.mProcState == procState) {
+ act.startActive(now);
+ } else {
+ act.stopActive(now);
+ if (act.mProcState < procState) {
+ Slog.w(TAG, "Tracking association " + act + " whose proc state "
+ + act.mProcState + " is better than process " + proc
+ + " proc state " + procState);
+ }
+ }
+ } else {
+ Slog.wtf(TAG, "Tracking association without process: " + act
+ + " in " + act.getAssociationState());
+ }
+ }
+ }
+ }
+
public void dumpLocked(PrintWriter pw, String reqPackage, long now, boolean dumpSummary,
boolean dumpAll, boolean activeOnly) {
long totalTime = DumpUtils.dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
@@ -1543,10 +1587,70 @@ public final class ProcessStats implements Parcelable {
proc.dumpInternalLocked(pw, " ", dumpAll);
}
}
+
if (dumpAll) {
- pw.println();
+ if (sepNeeded) {
+ pw.println();
+ }
+ sepNeeded = true;
pw.print(" Total procs: "); pw.print(numShownProcs);
pw.print(" shown of "); pw.print(numTotalProcs); pw.println(" total");
+ if (mTrackingAssociations.size() > 0) {
+ pw.println();
+ pw.println("Tracking associations:");
+ for (int i = 0; i < mTrackingAssociations.size(); i++) {
+ final AssociationState.SourceState src = mTrackingAssociations.get(i);
+ final AssociationState asc = src.getAssociationState();
+ pw.print(" #");
+ pw.print(i);
+ pw.print(": ");
+ pw.print(asc.getProcessName());
+ pw.print("/");
+ UserHandle.formatUid(pw, asc.getUid());
+ pw.print(" <- ");
+ pw.print(src.getProcessName());
+ pw.print("/");
+ UserHandle.formatUid(pw, src.getUid());
+ pw.println(":");
+ pw.print(" Tracking for: ");
+ TimeUtils.formatDuration(now - src.mTrackingUptime, pw);
+ pw.println();
+ pw.print(" Component: ");
+ pw.print(new ComponentName(asc.getPackage(), asc.getName())
+ .flattenToShortString());
+ pw.println();
+ pw.print(" Proc state: ");
+ if (src.mProcState != ProcessStats.STATE_NOTHING) {
+ pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
+ } else {
+ pw.print("--");
+ }
+ pw.print(" #");
+ pw.println(src.mProcStateSeq);
+ pw.print(" Process: ");
+ pw.println(asc.getProcess());
+ if (src.mActiveCount > 0) {
+ pw.print(" Active count ");
+ pw.print(src.mActiveCount);
+ long duration = src.mActiveDuration;
+ if (src.mActiveStartUptime > 0) {
+ duration += now - src.mActiveStartUptime;
+ }
+ if (dumpAll) {
+ pw.print(" / Duration ");
+ TimeUtils.formatDuration(duration, pw);
+ pw.print(" / ");
+ } else {
+ pw.print(" / time ");
+ }
+ DumpUtils.printPercent(pw, (double)duration/(double)totalTime);
+ if (src.mActiveStartUptime > 0) {
+ pw.print(" (running)");
+ }
+ pw.println();
+ }
+ }
+ }
}
if (sepNeeded) {
@@ -1985,15 +2089,19 @@ public final class ProcessStats implements Parcelable {
mVersionCode = versionCode;
}
- public AssociationState getAssociationStateLocked(String processName, String className) {
+ public AssociationState getAssociationStateLocked(ProcessState proc, String className) {
AssociationState as = mAssociations.get(className);
if (as != null) {
if (DEBUG) Slog.d(TAG, "GETASC: returning existing " + as);
+ if (proc != null) {
+ as.setProcess(proc);
+ }
return as;
}
- as = new AssociationState(mProcessStats, mPackageName, className, processName);
+ as = new AssociationState(mProcessStats, this, className, proc.getName(),
+ proc);
mAssociations.put(className, as);
- if (DEBUG) Slog.d(TAG, "GETASC: creating " + as + " in " + processName);
+ if (DEBUG) Slog.d(TAG, "GETASC: creating " + as + " in " + proc.getName());
return as;
}
}
diff --git a/core/java/com/android/internal/app/procstats/ServiceState.java b/core/java/com/android/internal/app/procstats/ServiceState.java
index 53ed5c0128d8..04e61e067f53 100644
--- a/core/java/com/android/internal/app/procstats/ServiceState.java
+++ b/core/java/com/android/internal/app/procstats/ServiceState.java
@@ -554,6 +554,6 @@ public final class ServiceState {
public String toString() {
return "ServiceState{" + Integer.toHexString(System.identityHashCode(this))
+ " " + mName + " pkg=" + mPackage + " proc="
- + Integer.toHexString(System.identityHashCode(this)) + "}";
+ + Integer.toHexString(System.identityHashCode(mProc)) + "}";
}
}
diff --git a/core/java/com/android/internal/os/BinderCallsStats.java b/core/java/com/android/internal/os/BinderCallsStats.java
index 20eab92d018b..f87c081fd04f 100644
--- a/core/java/com/android/internal/os/BinderCallsStats.java
+++ b/core/java/com/android/internal/os/BinderCallsStats.java
@@ -16,6 +16,7 @@
package com.android.internal.os;
+import android.annotation.Nullable;
import android.os.Binder;
import android.os.SystemClock;
import android.os.UserHandle;
@@ -23,6 +24,7 @@ import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
+import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -75,10 +77,10 @@ public class BinderCallsStats {
}
public CallSession callStarted(Binder binder, int code) {
- return callStarted(binder.getClass().getName(), code);
+ return callStarted(binder.getClass().getName(), code, binder.getTransactionName(code));
}
- private CallSession callStarted(String className, int code) {
+ private CallSession callStarted(String className, int code, @Nullable String methodName) {
if (!mEnabled) {
return NOT_ENABLED;
}
@@ -90,6 +92,7 @@ public class BinderCallsStats {
s.callStat.className = className;
s.callStat.msg = code;
+ s.callStat.methodName = methodName;
s.exceptionThrown = false;
s.cpuTimeStarted = -1;
s.timeStarted = -1;
@@ -169,6 +172,7 @@ public class BinderCallsStats {
callStat = s.sampledCallStat;
}
callStat.callCount++;
+ callStat.methodName = s.callStat.methodName;
if (s.cpuTimeStarted >= 0) {
callStat.cpuTimeMicros += duration;
callStat.maxCpuTimeMicros = Math.max(callStat.maxCpuTimeMicros, duration);
@@ -209,6 +213,39 @@ public class BinderCallsStats {
}
}
+ public ArrayList<ExportedCallStat> getExportedCallStats() {
+ // We do not collect all the data if detailed tracking is off.
+ if (!mDetailedTracking) {
+ return new ArrayList<ExportedCallStat>();
+ }
+
+ ArrayList<ExportedCallStat> resultCallStats = new ArrayList<>();
+ synchronized (mLock) {
+ int uidEntriesSize = mUidEntries.size();
+ for (int entryIdx = 0; entryIdx < uidEntriesSize; entryIdx++){
+ UidEntry entry = mUidEntries.valueAt(entryIdx);
+ for (CallStat stat : entry.getCallStatsList()) {
+ ExportedCallStat exported = new ExportedCallStat();
+ exported.uid = entry.uid;
+ exported.className = stat.className;
+ exported.methodName = stat.methodName == null
+ ? String.valueOf(stat.msg) : stat.methodName;
+ exported.cpuTimeMicros = stat.cpuTimeMicros;
+ exported.maxCpuTimeMicros = stat.maxCpuTimeMicros;
+ exported.latencyMicros = stat.latencyMicros;
+ exported.maxLatencyMicros = stat.maxLatencyMicros;
+ exported.callCount = stat.callCount;
+ exported.maxRequestSizeBytes = stat.maxRequestSizeBytes;
+ exported.maxReplySizeBytes = stat.maxReplySizeBytes;
+ exported.exceptionCount = stat.exceptionCount;
+ resultCallStats.add(exported);
+ }
+ }
+ }
+
+ return resultCallStats;
+ }
+
public void dump(PrintWriter pw, Map<Integer,String> appIdToPkgNameMap, boolean verbose) {
synchronized (mLock) {
dumpLocked(pw, appIdToPkgNameMap, verbose);
@@ -250,6 +287,7 @@ public class BinderCallsStats {
sb.setLength(0);
sb.append(" ")
.append(uidToString(uidEntry.uid, appIdToPkgNameMap))
+ .append(",").append(e)
.append(',').append(e.cpuTimeMicros)
.append(',').append(e.maxCpuTimeMicros)
.append(',').append(e.latencyMicros)
@@ -368,10 +406,30 @@ public class BinderCallsStats {
}
}
+ /**
+ * Aggregated data by uid/class/method to be sent through WestWorld.
+ */
+ public static class ExportedCallStat {
+ public int uid;
+ public String className;
+ public String methodName;
+ public long cpuTimeMicros;
+ public long maxCpuTimeMicros;
+ public long latencyMicros;
+ public long maxLatencyMicros;
+ public long callCount;
+ public long maxRequestSizeBytes;
+ public long maxReplySizeBytes;
+ public long exceptionCount;
+ }
+
@VisibleForTesting
public static class CallStat {
public String className;
public int msg;
+ // Method name might be null when we cannot resolve the transaction code. For instance, if
+ // the binder was not generated by AIDL.
+ public @Nullable String methodName;
public long cpuTimeMicros;
public long maxCpuTimeMicros;
public long latencyMicros;
@@ -410,7 +468,7 @@ public class BinderCallsStats {
@Override
public String toString() {
- return className + "/" + msg;
+ return className + "#" + (methodName == null ? msg : methodName);
}
}
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index 60dd86b1fc10..16ca4fcdbe34 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -454,9 +454,12 @@ public class ContrastColorUtil {
/**
* Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
*/
- public static int resolveColor(Context context, int color) {
+ public static int resolveColor(Context context, int color, boolean defaultBackgroundIsDark) {
if (color == Notification.COLOR_DEFAULT) {
- return context.getColor(com.android.internal.R.color.notification_default_color_light);
+ int res = defaultBackgroundIsDark
+ ? com.android.internal.R.color.notification_default_color_dark
+ : com.android.internal.R.color.notification_default_color_light;
+ return context.getColor(res);
}
return color;
}
@@ -486,7 +489,7 @@ public class ContrastColorUtil {
*/
public static int resolveContrastColor(Context context, int notificationColor,
int backgroundColor, boolean isDark) {
- final int resolvedColor = resolveColor(context, notificationColor);
+ final int resolvedColor = resolveColor(context, notificationColor, isDark);
int color = resolvedColor;
color = ContrastColorUtil.ensureTextContrast(color, backgroundColor, isDark);
@@ -520,7 +523,8 @@ public class ContrastColorUtil {
}
public static int resolveAmbientColor(Context context, int notificationColor) {
- final int resolvedColor = resolveColor(context, notificationColor);
+ final int resolvedColor = resolveColor(context, notificationColor,
+ true /* defaultBackgroundIsDark */);
int color = resolvedColor;
color = ContrastColorUtil.ensureTextContrastOnBlack(color);
@@ -538,8 +542,9 @@ public class ContrastColorUtil {
return color;
}
- public static int resolvePrimaryColor(Context context, int backgroundColor) {
- boolean useDark = shouldUseDark(backgroundColor);
+ public static int resolvePrimaryColor(Context context, int backgroundColor,
+ boolean defaultBackgroundIsDark) {
+ boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
if (useDark) {
return context.getColor(
com.android.internal.R.color.notification_primary_text_color_light);
@@ -549,8 +554,9 @@ public class ContrastColorUtil {
}
}
- public static int resolveSecondaryColor(Context context, int backgroundColor) {
- boolean useDark = shouldUseDark(backgroundColor);
+ public static int resolveSecondaryColor(Context context, int backgroundColor,
+ boolean defaultBackgroundIsDark) {
+ boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
if (useDark) {
return context.getColor(
com.android.internal.R.color.notification_secondary_text_color_light);
@@ -560,8 +566,9 @@ public class ContrastColorUtil {
}
}
- public static int resolveDefaultColor(Context context, int backgroundColor) {
- boolean useDark = shouldUseDark(backgroundColor);
+ public static int resolveDefaultColor(Context context, int backgroundColor,
+ boolean defaultBackgroundIsDark) {
+ boolean useDark = shouldUseDark(backgroundColor, defaultBackgroundIsDark);
if (useDark) {
return context.getColor(
com.android.internal.R.color.notification_default_color_light);
@@ -591,12 +598,11 @@ public class ContrastColorUtil {
return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
}
- private static boolean shouldUseDark(int backgroundColor) {
- boolean useDark = backgroundColor == Notification.COLOR_DEFAULT;
- if (!useDark) {
- useDark = ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
+ private static boolean shouldUseDark(int backgroundColor, boolean defaultBackgroundIsDark) {
+ if (backgroundColor == Notification.COLOR_DEFAULT) {
+ return !defaultBackgroundIsDark;
}
- return useDark;
+ return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5;
}
public static double calculateLuminance(int backgroundColor) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d4903d80c7e2..b675698a461b 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -126,7 +126,6 @@ cc_library_shared {
"android/graphics/Camera.cpp",
"android/graphics/CanvasProperty.cpp",
"android/graphics/ColorFilter.cpp",
- "android/graphics/DrawFilter.cpp",
"android/graphics/FontFamily.cpp",
"android/graphics/FontUtils.cpp",
"android/graphics/CreateJavaOutputStreamAdaptor.cpp",
@@ -143,6 +142,7 @@ cc_library_shared {
"android/graphics/NinePatch.cpp",
"android/graphics/NinePatchPeeker.cpp",
"android/graphics/Paint.cpp",
+ "android/graphics/PaintFilter.cpp",
"android/graphics/Path.cpp",
"android/graphics/PathMeasure.cpp",
"android/graphics/PathEffect.cpp",
@@ -220,7 +220,6 @@ cc_library_shared {
"external/skia/src/image",
"external/skia/src/images",
"frameworks/base/media/jni",
- "libcore/include",
"system/media/camera/include",
"system/media/private/camera/include",
],
diff --git a/core/jni/android/graphics/DrawFilter.cpp b/core/jni/android/graphics/PaintFilter.cpp
index c1dc0dd025b6..182b22b3c917 100644
--- a/core/jni/android/graphics/DrawFilter.cpp
+++ b/core/jni/android/graphics/PaintFilter.cpp
@@ -15,36 +15,43 @@
** limitations under the License.
*/
-// This file was generated from the C++ include file: SkColorFilter.h
-// Any changes made to this file will be discarded by the build.
-// To change this file, either edit the include, or device/tools/gluemaker/main.cpp,
-// or one of the auxilary file specifications in device/tools/gluemaker.
-
#include "jni.h"
#include "GraphicsJNI.h"
#include <android_runtime/AndroidRuntime.h>
#include "core_jni_helpers.h"
-#include "SkDrawFilter.h"
-#include "SkPaintFlagsDrawFilter.h"
+#include "hwui/PaintFilter.h"
#include "SkPaint.h"
namespace android {
-// Custom version of SkPaintFlagsDrawFilter that also calls setFilterQuality.
-class CompatFlagsDrawFilter : public SkPaintFlagsDrawFilter {
+class PaintFlagsFilter : public PaintFilter {
public:
- CompatFlagsDrawFilter(uint32_t clearFlags, uint32_t setFlags,
- SkFilterQuality desiredQuality)
- : SkPaintFlagsDrawFilter(clearFlags, setFlags)
+ PaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags) {
+ fClearFlags = static_cast<uint16_t>(clearFlags & SkPaint::kAllFlags);
+ fSetFlags = static_cast<uint16_t>(setFlags & SkPaint::kAllFlags);
+ }
+ void filter(SkPaint* paint) override {
+ paint->setFlags((paint->getFlags() & ~fClearFlags) | fSetFlags);
+ }
+
+private:
+ uint16_t fClearFlags;
+ uint16_t fSetFlags;
+};
+
+// Custom version of PaintFlagsDrawFilter that also calls setFilterQuality.
+class CompatPaintFlagsFilter : public PaintFlagsFilter {
+public:
+ CompatPaintFlagsFilter(uint32_t clearFlags, uint32_t setFlags, SkFilterQuality desiredQuality)
+ : PaintFlagsFilter(clearFlags, setFlags)
, fDesiredQuality(desiredQuality) {
}
- virtual bool filter(SkPaint* paint, Type type) {
- SkPaintFlagsDrawFilter::filter(paint, type);
+ virtual void filter(SkPaint* paint) {
+ PaintFlagsFilter::filter(paint);
paint->setFilterQuality(fDesiredQuality);
- return true;
}
private:
@@ -61,16 +68,16 @@ static inline bool hadFiltering(jint& flags) {
return result;
}
-class SkDrawFilterGlue {
+class PaintFilterGlue {
public:
static void finalizer(JNIEnv* env, jobject clazz, jlong objHandle) {
- SkDrawFilter* obj = reinterpret_cast<SkDrawFilter*>(objHandle);
+ PaintFilter* obj = reinterpret_cast<PaintFilter*>(objHandle);
SkSafeUnref(obj);
}
- static jlong CreatePaintFlagsDF(JNIEnv* env, jobject clazz,
- jint clearFlags, jint setFlags) {
+ static jlong CreatePaintFlagsFilter(JNIEnv* env, jobject clazz,
+ jint clearFlags, jint setFlags) {
if (clearFlags | setFlags) {
// Mask both groups of flags to remove FILTER_BITMAP_FLAG, which no
// longer has a Skia equivalent flag (instead it corresponds to
@@ -79,16 +86,16 @@ public:
const bool turnFilteringOn = hadFiltering(setFlags);
const bool turnFilteringOff = hadFiltering(clearFlags);
- SkDrawFilter* filter;
+ PaintFilter* filter;
if (turnFilteringOn) {
// Turning filtering on overrides turning it off.
- filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+ filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
kLow_SkFilterQuality);
} else if (turnFilteringOff) {
- filter = new CompatFlagsDrawFilter(clearFlags, setFlags,
+ filter = new CompatPaintFlagsFilter(clearFlags, setFlags,
kNone_SkFilterQuality);
} else {
- filter = new SkPaintFlagsDrawFilter(clearFlags, setFlags);
+ filter = new PaintFlagsFilter(clearFlags, setFlags);
}
return reinterpret_cast<jlong>(filter);
} else {
@@ -98,11 +105,11 @@ public:
};
static const JNINativeMethod drawfilter_methods[] = {
- {"nativeDestructor", "(J)V", (void*) SkDrawFilterGlue::finalizer}
+ {"nativeDestructor", "(J)V", (void*) PaintFilterGlue::finalizer}
};
static const JNINativeMethod paintflags_methods[] = {
- {"nativeConstructor","(II)J", (void*) SkDrawFilterGlue::CreatePaintFlagsDF}
+ {"nativeConstructor","(II)J", (void*) PaintFilterGlue::CreatePaintFlagsFilter}
};
int register_android_graphics_DrawFilter(JNIEnv* env) {
@@ -110,7 +117,7 @@ int register_android_graphics_DrawFilter(JNIEnv* env) {
NELEM(drawfilter_methods));
result |= RegisterMethodsOrDie(env, "android/graphics/PaintFlagsDrawFilter", paintflags_methods,
NELEM(paintflags_methods));
-
+
return 0;
}
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp
index 484b33f1ca9e..3b024b442060 100644
--- a/core/jni/android_graphics_Canvas.cpp
+++ b/core/jni/android_graphics_Canvas.cpp
@@ -22,13 +22,13 @@
#include <androidfw/ResourceTypes.h>
#include <hwui/Canvas.h>
#include <hwui/Paint.h>
+#include <hwui/PaintFilter.h>
#include <hwui/Typeface.h>
#include <minikin/Layout.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/ScopedStringChars.h>
#include "Bitmap.h"
-#include "SkDrawFilter.h"
#include "SkGraphics.h"
#include "SkRegion.h"
#include "SkVertices.h"
@@ -582,8 +582,9 @@ static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstri
env->ReleaseStringChars(text, jchars);
}
-static void setDrawFilter(jlong canvasHandle, jlong filterHandle) {
- get_canvas(canvasHandle)->setDrawFilter(reinterpret_cast<SkDrawFilter*>(filterHandle));
+static void setPaintFilter(jlong canvasHandle, jlong filterHandle) {
+ PaintFilter* paintFilter = reinterpret_cast<PaintFilter*>(filterHandle);
+ get_canvas(canvasHandle)->setPaintFilter(sk_ref_sp(paintFilter));
}
static void freeCaches(JNIEnv* env, jobject) {
@@ -633,7 +634,7 @@ static const JNINativeMethod gMethods[] = {
{"nQuickReject","(JFFFF)Z", (void*)CanvasJNI::quickRejectRect},
{"nClipRect","(JFFFFI)Z", (void*) CanvasJNI::clipRect},
{"nClipPath","(JJI)Z", (void*) CanvasJNI::clipPath},
- {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setDrawFilter},
+ {"nSetDrawFilter", "(JJ)V", (void*) CanvasJNI::setPaintFilter},
};
// If called from Canvas these are regular JNI
diff --git a/core/jni/android_media_MediaMetricsJNI.h b/core/jni/android_media_MediaMetricsJNI.h
index 16081b4af1cb..b3cb4d293399 100644
--- a/core/jni/android_media_MediaMetricsJNI.h
+++ b/core/jni/android_media_MediaMetricsJNI.h
@@ -17,7 +17,6 @@
#ifndef _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
#define _ANDROID_MEDIA_MEDIAMETRICSJNI_H_
-#include <android_runtime/AndroidRuntime.h>
#include <jni.h>
#include <nativehelper/JNIHelp.h>
#include <media/MediaAnalyticsItem.h>
diff --git a/core/jni/android_text_MeasuredParagraph.cpp b/core/jni/android_text_MeasuredParagraph.cpp
index 41a81accf72c..9eb6f8d4189a 100644
--- a/core/jni/android_text_MeasuredParagraph.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "MeasuredParagraph"
#include "GraphicsJNI.h"
-#include "ScopedIcuLocale.h"
#include "unicode/locid.h"
#include "unicode/brkiter.h"
#include "utils/misc.h"
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index ad84858bb8db..fec5b6995646 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -16,7 +16,6 @@
#define LOG_TAG "StaticLayout"
-#include "ScopedIcuLocale.h"
#include "unicode/locid.h"
#include "unicode/brkiter.h"
#include "utils/misc.h"
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 22bbc3c48119..46b19bdf660f 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -222,6 +222,11 @@ static jboolean android_view_RenderNode_setHasOverlappingRendering(jlong renderN
RenderNode::GENERIC);
}
+static void android_view_RenderNode_setUsageHint(jlong renderNodePtr, jint usageHint) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ renderNode->setUsageHint(static_cast<UsageHint>(usageHint));
+}
+
static jboolean android_view_RenderNode_setElevation(jlong renderNodePtr, float elevation) {
return SET_AND_DIRTY(setElevation, elevation, RenderNode::Z);
}
@@ -614,6 +619,7 @@ static const JNINativeMethod gMethods[] = {
{ "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha },
{ "nSetHasOverlappingRendering", "(JZ)Z",
(void*) android_view_RenderNode_setHasOverlappingRendering },
+ { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint },
{ "nSetElevation", "(JF)Z", (void*) android_view_RenderNode_setElevation },
{ "nSetTranslationX", "(JF)Z", (void*) android_view_RenderNode_setTranslationX },
{ "nSetTranslationY", "(JF)Z", (void*) android_view_RenderNode_setTranslationY },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e2a5c57fecd6..472df1a4ab70 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2437,7 +2437,8 @@
<permission android:name="android.permission.ASEC_RENAME"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows applications to write the apn settings.
+ <!-- @SystemApi Allows applications to write the apn settings and read sensitive fields of
+ an existing apn settings like user and password.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.WRITE_APN_SETTINGS"
android:protectionLevel="signature|privileged" />
diff --git a/core/res/res/drawable-hdpi/ic_grayedout_printer.png b/core/res/res/drawable-hdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970d6b3f..000000000000
--- a/core/res/res/drawable-hdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-mdpi/ic_grayedout_printer.png b/core/res/res/drawable-mdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970d6b3f..000000000000
--- a/core/res/res/drawable-mdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable-xhdpi/ic_grayedout_printer.png b/core/res/res/drawable-xhdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970d6b3f..000000000000
--- a/core/res/res/drawable-xhdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/values-land/dimens_package_installer.xml b/core/res/res/values-land/dimens_package_installer.xml
new file mode 100644
index 000000000000..72f4ec22c36f
--- /dev/null
+++ b/core/res/res/values-land/dimens_package_installer.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+
+<!-- Landscape dimensions for the permission grant dialog. -->
+<resources>
+ <!-- 37:20 == 65% width -->
+ <dimen name="permissionGrantDialogWidth">37</dimen>
+</resources>
diff --git a/packages/PrintSpooler/res/drawable/print_warning.xml b/core/res/res/values-mcc334-mnc03/config.xml
index 35f0fed78a7f..c0d2b3598f82 100644
--- a/packages/PrintSpooler/res/drawable/print_warning.xml
+++ b/core/res/res/values-mcc334-mnc03/config.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!-- 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.
@@ -14,12 +14,7 @@
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="96dp"
- android:height="96dp"
- android:viewportWidth="96.0"
- android:viewportHeight="96.0">
- <path
- android:fillColor="#C8CCCE"
- android:pathData="M4,84H92L48,8 4,84zM52,72h-8v-8h8v8zM52,56H44V40h8v16z"/>
-</vector>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc334-mnc030/config.xml b/core/res/res/values-mcc334-mnc030/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc334-mnc030/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc704-mnc03/config.xml b/core/res/res/values-mcc704-mnc03/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc704-mnc03/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc706-mnc04/config.xml b/core/res/res/values-mcc706-mnc04/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc706-mnc04/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc712-mnc04/config.xml b/core/res/res/values-mcc712-mnc04/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc712-mnc04/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc716-mnc06/config.xml b/core/res/res/values-mcc716-mnc06/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc716-mnc06/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc716-mnc10/config.xml b/core/res/res/values-mcc716-mnc10/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc716-mnc10/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc716-mnc17/config.xml b/core/res/res/values-mcc716-mnc17/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc716-mnc17/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc722-mnc07/config.xml b/core/res/res/values-mcc722-mnc07/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc722-mnc07/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc732-mnc123/config.xml b/core/res/res/values-mcc732-mnc123/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc732-mnc123/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-mcc740-mnc00/config.xml b/core/res/res/values-mcc740-mnc00/config.xml
new file mode 100644
index 000000000000..c0d2b3598f82
--- /dev/null
+++ b/core/res/res/values-mcc740-mnc00/config.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Show area update info settings in CellBroadcastReceiver and information in SIM status in Settings app -->
+ <bool name="config_showAreaUpdateInfoSettings">true</bool>
+</resources>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
new file mode 100644
index 000000000000..688040ba0cc1
--- /dev/null
+++ b/core/res/res/values-night/colors.xml
@@ -0,0 +1,31 @@
+<?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
+
+ NOTE: You might also want to edit: packages/SystemUI/res/values-night/colors.xml
+ -->
+<resources>
+ <!-- The primary text color if the text is on top of a dark background.
+ This is also affects colorized notifications with dark backgrounds. -->
+ <color name="notification_primary_text_color_dark">#dadada</color>
+
+ <!-- The secondary text color if the text is on top of a dark background. -->
+ <color name="notification_secondary_text_color_dark">#dadada</color>
+
+ <color name="notification_default_color_dark">#dadada</color>
+
+ <!-- The background color of a notification card. -->
+ <color name="notification_material_background_color">@*android:color/material_grey_900</color>
+</resources> \ No newline at end of file
diff --git a/core/res/res/values-night/values.xml b/core/res/res/values-night/values.xml
index 23b0a24fff98..4eb2ff3b5970 100644
--- a/core/res/res/values-night/values.xml
+++ b/core/res/res/values-night/values.xml
@@ -31,4 +31,9 @@
<!-- volume background -->
<item name="panelColorBackground">@color/material_grey_800</item>
</style>
+
+ <style name="TextAppearance.Material.Notification">
+ <item name="textColor">@color/notification_secondary_text_color_dark</item>
+ <item name="textSize">@dimen/notification_text_size</item>
+ </style>
</resources> \ No newline at end of file
diff --git a/core/res/res/values-port/dimens_package_installer.xml b/core/res/res/values-port/dimens_package_installer.xml
new file mode 100644
index 000000000000..67cafe752409
--- /dev/null
+++ b/core/res/res/values-port/dimens_package_installer.xml
@@ -0,0 +1,22 @@
+<?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.
+ -->
+
+<!-- portrait dimensions for the permission grant dialog. -->
+<resources>
+ <!-- 380:20 == 95% width -->
+ <dimen name="permissionGrantDialogWidth">380</dimen>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 18624370ccfa..e4ce2b1e91ca 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3414,7 +3414,7 @@
<string-array translatable="false" name="config_batteryPackageTypeService"/>
<!-- Flag indicating whether or not to enable night mode detection. -->
- <bool name="config_enableNightMode">false</bool>
+ <bool name="config_enableNightMode">true</bool>
<!-- Flag indicating that the actions buttons for a notification should be tinted with by the
color supplied by the Notification.Builder if present. -->
diff --git a/core/res/res/values/styles_package_installer.xml b/core/res/res/values/styles_package_installer.xml
new file mode 100644
index 000000000000..8bfcc8dcde76
--- /dev/null
+++ b/core/res/res/values/styles_package_installer.xml
@@ -0,0 +1,97 @@
+<?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.
+ -->
+
+<!-- styles for the permission grant dialog. -->
+<resources>
+ <style name="PermissionGrantDialog">
+ <item name="background">?attr/windowBackground</item>
+ <item name="elevation">?attr/windowElevation</item>
+ <item name="layout_weight">@dimen/permissionGrantDialogWidth</item>
+ </style>
+
+ <style name="PermissionGrantTitleIcon">
+ <item name="layout_width">36dp</item>
+ <item name="layout_height">36dp</item>
+ <item name="tint">?attr/colorAccent</item>
+ <item name="scaleType">fitCenter</item>
+ </style>
+
+ <style name="PermissionGrantTitleMessage"
+ parent="@style/TextAppearance.DeviceDefault">
+ <item name="paddingStart">22dp</item>
+ <item name="textSize">20sp</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ </style>
+
+ <style name="PermissionGrantIndex"
+ parent="@style/TextAppearance.DeviceDefault">
+ <item name="paddingEnd">12dp</item>
+ <item name="singleLine">true</item>
+ <item name="textColor">?attr/textColorSecondary</item>
+ </style>
+
+ <style name="PermissionGrantDescription">
+ <item name="layout_marginTop">20dp</item>
+ <item name="layout_marginStart">24dp</item>
+ <item name="layout_marginBottom">16dp</item>
+ <item name="layout_marginEnd">24dp</item>
+ </style>
+
+ <style name="PermissionGrantContent">
+ <item name="layout_marginStart">16dp</item>
+ <item name="layout_marginEnd">24dp</item>
+ </style>
+
+ <style name="PermissionGrantRadioGroup">
+ <item name="layout_marginStart">8dp</item>
+ <item name="layout_marginTop">-4dp</item>
+ </style>
+
+ <style name="PermissionGrantRadioButton"
+ parent="@style/Widget.DeviceDefault.CompoundButton.RadioButton">
+ <item name="paddingStart">16dp</item>
+ <item name="paddingTop">8dp</item>
+ <item name="paddingBottom">8dp</item>
+ <item name="textSize">16sp</item>
+ </style>
+
+ <style name="PermissionGrantDetailMessage"
+ parent="@style/TextAppearance.DeviceDefault">
+ <item name="layout_marginStart">8dp</item>
+ <item name="layout_marginBottom">4dp</item>
+ <item name="textColor">?attr/textColorPrimary</item>
+ <item name="textSize">16sp</item>
+ </style>
+
+ <style name="PermissionGrantDetailMessageSpace">
+ <item name="layout_height">8dp</item>
+ </style>
+
+ <style name="PermissionGrantCheckbox"
+ parent="@style/Widget.DeviceDefault.CompoundButton.CheckBox">
+ <item name="paddingStart">16dp</item>
+ <item name="textColor">?attr/textColorSecondary</item>
+ <item name="buttonTint">?attr/textColorSecondary</item>
+ <item name="textSize">16sp</item>
+ </style>
+
+ <style name="PermissionGrantButtonBar">
+ <item name="layout_marginStart">24dp</item>
+ <item name="layout_marginEnd">16dp</item>
+ <item name="layout_marginBottom">4dp</item>
+ </style>
+</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 99001e3a9209..6a58b670b666 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1339,7 +1339,6 @@
<java-symbol type="drawable" name="ic_text_dot" />
<java-symbol type="drawable" name="ic_print" />
<java-symbol type="drawable" name="ic_print_error" />
- <java-symbol type="drawable" name="ic_grayedout_printer" />
<java-symbol type="drawable" name="jog_dial_arrow_long_left_green" />
<java-symbol type="drawable" name="jog_dial_arrow_long_right_red" />
<java-symbol type="drawable" name="jog_dial_arrow_short_left_and_right" />
diff --git a/core/res/res/values/themes_package_installer.xml b/core/res/res/values/themes_package_installer.xml
new file mode 100644
index 000000000000..a6341dc664ab
--- /dev/null
+++ b/core/res/res/values/themes_package_installer.xml
@@ -0,0 +1,35 @@
+<?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.
+ -->
+
+<!-- themes for the permission grant dialog. -->
+<resources>
+ <style name="Theme.DeviceDefault.Light.Panel.PermissionGrantApp"
+ parent="@style/Theme.DeviceDefault.Light.Panel">
+ <item name="windowIsFloating">false</item>
+ <item name="windowTranslucentStatus">true</item>
+ <item name="backgroundDimEnabled">true</item>
+ <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item>
+ </style>
+
+ <style name="Theme.DeviceDefault.Light.Dialog.PermissionGrant"
+ parent="@style/Theme.DeviceDefault.Light.Dialog">
+ <item name="titleTextStyle">@style/PermissionGrantTitleMessage</item>
+ <item name="radioButtonStyle">@style/PermissionGrantRadioButton</item>
+ <item name="checkboxStyle">@style/PermissionGrantCheckbox</item>
+ <item name="buttonBarStyle">@style/PermissionGrantButtonBar</item>
+ </style>
+</resources>
diff --git a/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
index 5989da7af655..c70fc3cce43c 100644
--- a/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/FileUtilsBenchmark.java
@@ -54,7 +54,7 @@ public class FileUtilsBenchmark {
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -63,7 +63,7 @@ public class FileUtilsBenchmark {
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalSendfile(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalSendfile(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -72,7 +72,7 @@ public class FileUtilsBenchmark {
for (int i = 0; i < reps; i++) {
try (MemoryPipe in = MemoryPipe.createSource(mData);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -81,7 +81,7 @@ public class FileUtilsBenchmark {
for (int i = 0; i < reps; i++) {
try (MemoryPipe in = MemoryPipe.createSource(mData);
FileOutputStream out = new FileOutputStream(mDest)) {
- copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalSplice(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -90,7 +90,7 @@ public class FileUtilsBenchmark {
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
MemoryPipe out = MemoryPipe.createSink(mData)) {
- copyInternalUserspace(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalUserspace(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
@@ -99,7 +99,7 @@ public class FileUtilsBenchmark {
for (int i = 0; i < reps; i++) {
try (FileInputStream in = new FileInputStream(mSrc);
MemoryPipe out = MemoryPipe.createSink(mData)) {
- copyInternalSplice(in.getFD(), out.getFD(), null, null, Long.MAX_VALUE);
+ copyInternalSplice(in.getFD(), out.getFD(), Long.MAX_VALUE, null, null, null);
}
}
}
diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
index ada03666b7ba..b906d84adf52 100644
--- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java
@@ -446,7 +446,10 @@ public class BluetoothTestUtils extends Assert {
final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(receiver, filter);
- assertTrue(adapter.enable());
+ // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to
+ // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable.
+ // So no assertion applied here.
+ adapter.enable();
boolean success = false;
try {
success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
@@ -489,7 +492,10 @@ public class BluetoothTestUtils extends Assert {
final IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(receiver, filter);
- assertTrue(adapter.disable());
+ // Note: for Wear Local Edition builds, which have Permission Review Mode enabled to
+ // obey China CMIIT, BluetoothAdapter may not startup immediately on methods enable/disable.
+ // So no assertion applied here.
+ adapter.disable();
boolean success = false;
try {
success = completionSemaphore.tryAcquire(ENABLE_DISABLE_TIMEOUT, TimeUnit.MILLISECONDS);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index fe58116002f2..3d114f4b7c6c 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -47,6 +47,7 @@ import android.os.Parcelable;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,7 @@ import org.mockito.InOrder;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.stream.Collectors;
/** Test {@link TransactionExecutor} logic. */
@@ -232,6 +234,44 @@ public class TransactionExecutorTests {
}
@Test
+ public void testDoNotLaunchDestroyedActivity() {
+ final Map<IBinder, ClientTransactionItem> activitiesToBeDestroyed = new ArrayMap<>();
+ when(mTransactionHandler.getActivitiesToBeDestroyed()).thenReturn(activitiesToBeDestroyed);
+ // Assume launch transaction is still in queue, so there is no client record.
+ when(mTransactionHandler.getActivityClient(any())).thenReturn(null);
+
+ // An incoming destroy transaction enters binder thread (preExecute).
+ final IBinder token = mock(IBinder.class);
+ final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */,
+ token /* activityToken */);
+ destroyTransaction.setLifecycleStateRequest(
+ DestroyActivityItem.obtain(false /* finished */, 0 /* configChanges */));
+ destroyTransaction.preExecute(mTransactionHandler);
+ // The activity should be added to to-be-destroyed container.
+ assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size());
+
+ // A previous queued launch transaction runs on main thread (execute).
+ final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */,
+ token /* activityToken */);
+ final LaunchActivityItem launchItem = spy(LaunchActivityItem.obtain(
+ null /* intent */, 0 /* ident */, null /* info */, null /* curConfig */,
+ null, /* overrideConfig */ null /* compatInfo */, null /* referrer */ ,
+ null /* voiceInteractor */, 0 /* procState */, null /* state */,
+ null /* persistentState */, null /* pendingResults */,
+ null /* pendingNewIntents */, false /* isForward */, null /* profilerInfo */));
+ launchTransaction.addCallback(launchItem);
+ mExecutor.execute(launchTransaction);
+
+ // The launch transaction should not be executed because its token is in the
+ // to-be-destroyed container.
+ verify(launchItem, times(0)).execute(any(), any(), any());
+
+ // After the destroy transaction has been executed, the token should be removed.
+ mExecutor.execute(destroyTransaction);
+ assertEquals(0, mTransactionHandler.getActivitiesToBeDestroyed().size());
+ }
+
+ @Test
public void testActivityResultRequiredStateResolution() {
PostExecItem postExecItem = new PostExecItem(ON_RESUME);
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 0bc3a2d879ab..9c9f11b76ab4 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -193,7 +193,7 @@ public class FileUtilsTest {
try (MemoryPipe in = MemoryPipe.createSource(source);
FileOutputStream out = new FileOutputStream(dest)) {
- FileUtils.copy(in.getFD(), out.getFD(), null, null, size);
+ FileUtils.copy(in.getFD(), out.getFD(), size, null, null, null);
}
actual = readFile(dest);
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index f91d14988d29..60e512cb2c1c 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -66,7 +66,6 @@ public class SettingsBackupTest {
Settings.System.LOCKSCREEN_DISABLED, // ?
Settings.System.MEDIA_BUTTON_RECEIVER, // candidate for backup?
Settings.System.MUTE_STREAMS_AFFECTED, // candidate for backup?
- Settings.System.NOTIFICATION_LIGHT_PULSE, // candidate for backup?
Settings.System.NOTIFICATION_SOUND_CACHE, // internal cache
Settings.System.POINTER_LOCATION, // backup candidate?
Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, // used for testing only
@@ -560,10 +559,8 @@ public class SettingsBackupTest {
Settings.Secure.LAST_SETUP_SHOWN,
Settings.Secure.LOCATION_CHANGER,
Settings.Secure.LOCATION_MODE,
- Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, // Candidate?
Settings.Secure.LOCK_SCREEN_ALLOW_REMOTE_INPUT, // Candidate?
Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
- Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, // Candidate?
Settings.Secure.LOCK_TO_APP_EXIT_LOCKED,
Settings.Secure.MANAGED_PROFILE_CONTACT_REMOTE_SEARCH,
Settings.Secure.MULTI_PRESS_TIMEOUT,
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 870d6b2b7370..72290bf10f9d 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -785,4 +785,11 @@ public class TextUtilsTest {
assertEquals(2, TextUtils.length(" "));
assertEquals(6, TextUtils.length("Hello!"));
}
+
+ @Test
+ public void testTrimToLengthWithEllipsis() {
+ assertEquals("ABC...", TextUtils.trimToLengthWithEllipsis("ABCDEF", 3));
+ assertEquals("ABC", TextUtils.trimToLengthWithEllipsis("ABC", 3));
+ assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3));
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
index 914fb7409c74..d46c1543e0f0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BinderCallsStatsTest.java
@@ -226,6 +226,26 @@ public class BinderCallsStatsTest {
}
@Test
+ public void testTransactionCodeResolved() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Binder binder = new Binder() {
+ @Override
+ public String getTransactionName(int code) {
+ return "resolved";
+ }
+ };
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.time += 10;
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ List<BinderCallsStats.CallStat> callStatsList =
+ bcs.getUidEntries().get(TEST_UID).getCallStatsList();
+ assertEquals(1, callStatsList.get(0).msg);
+ assertEquals("resolved", callStatsList.get(0).methodName);
+ }
+
+ @Test
public void testParcelSize() {
TestBinderCallsStats bcs = new TestBinderCallsStats();
bcs.setDetailedTracking(true);
@@ -323,6 +343,42 @@ public class BinderCallsStatsTest {
bcs.dump(pw, new HashMap<>(), true);
}
+ @Test
+ public void testGetExportedStatsWhenDetailedTrackingDisabled() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(false);
+ Binder binder = new Binder();
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(0, bcs.getExportedCallStats().size());
+ }
+
+ @Test
+ public void testGetExportedStatsWhenDetailedTrackingEnabled() {
+ TestBinderCallsStats bcs = new TestBinderCallsStats();
+ bcs.setDetailedTracking(true);
+ Binder binder = new Binder();
+ BinderCallsStats.CallSession callSession = bcs.callStarted(binder, 1);
+ bcs.time += 10;
+ bcs.elapsedTime += 20;
+ bcs.callEnded(callSession, REQUEST_SIZE, REPLY_SIZE);
+
+ assertEquals(1, bcs.getExportedCallStats().size());
+ BinderCallsStats.ExportedCallStat stat = bcs.getExportedCallStats().get(0);
+ assertEquals(TEST_UID, stat.uid);
+ assertEquals("android.os.Binder", stat.className);
+ assertEquals("1", stat.methodName);
+ assertEquals(10, stat.cpuTimeMicros);
+ assertEquals(10, stat.maxCpuTimeMicros);
+ assertEquals(20, stat.latencyMicros);
+ assertEquals(20, stat.maxLatencyMicros);
+ assertEquals(1, stat.callCount);
+ assertEquals(REQUEST_SIZE, stat.maxRequestSizeBytes);
+ assertEquals(REPLY_SIZE, stat.maxReplySizeBytes);
+ assertEquals(0, stat.exceptionCount);
+ }
+
static class TestBinderCallsStats extends BinderCallsStats {
int callingUid = TEST_UID;
long time = 1234;
diff --git a/data/sounds/AllAudio.mk b/data/sounds/AllAudio.mk
index bf8067ccbd5c..bb8add1187e7 100644
--- a/data/sounds/AllAudio.mk
+++ b/data/sounds/AllAudio.mk
@@ -234,3 +234,8 @@ PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/effects/ogg/camera_focus.ogg:system/media/audio/ui/camera_focus.ogg \
$(LOCAL_PATH)/effects/ogg/ChargingStarted.ogg:system/media/audio/ui/ChargingStarted.ogg \
$(LOCAL_PATH)/effects/ogg/InCallNotification.ogg:system/media/audio/ui/InCallNotification.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCFailure.ogg:system/media/audio/ui/NFCFailure.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCInitiated.ogg:system/media/audio/ui/NFCInitiated.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCSuccess.ogg:system/media/audio/ui/NFCSuccess.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCTransferComplete.ogg:system/media/audio/ui/NFCTransferComplete.ogg \
+ $(LOCAL_PATH)/effects/ogg/NFCTransferInitiated.ogg:system/media/audio/ui/NFCTransferInitiated.ogg \
diff --git a/data/sounds/AudioPackage14.mk b/data/sounds/AudioPackage14.mk
new file mode 100644
index 000000000000..c903a2b0eb97
--- /dev/null
+++ b/data/sounds/AudioPackage14.mk
@@ -0,0 +1,32 @@
+#
+# Audio Package 14 - P
+#
+# Include this file in a product makefile to include these audio files
+#
+#
+
+LOCAL_PATH := frameworks/base/data/sounds
+
+# Simple files that do not require renaming
+ALARM_FILES := Argon Carbon Helium Krypton Neon Oxygen Osmium Platinum Timer
+NOTIFICATION_FILES := Ariel Ceres Carme Elara Europa Iapetus Io Rhea Salacia Titan Tethys
+RINGTONE_FILES := Atria Callisto Dione Ganymede Luna Oberon Phobos Pyxis Sedna Titania Triton \
+ Umbriel
+EFFECT_FILES := Effect_Tick KeypressReturn KeypressInvalid KeypressDelete KeypressSpacebar KeypressStandard \
+ camera_focus Dock Undock Lock Unlock Trusted ChargingStarted InCallNotification \
+ NFCFailure NFCInitiated NFCSuccess NFCTransferComplete NFCTransferInitiated
+MATERIAL_EFFECT_FILES := camera_click VideoRecord WirelessChargingStarted LowBattery VideoStop
+
+PRODUCT_COPY_FILES += $(foreach fn,$(ALARM_FILES),\
+ $(LOCAL_PATH)/alarms/material/ogg/$(fn).ogg:system/media/audio/alarms/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(NOTIFICATION_FILES),\
+ $(LOCAL_PATH)/notifications/material/ogg/$(fn).ogg:system/media/audio/notifications/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(RINGTONE_FILES),\
+ $(LOCAL_PATH)/ringtones/material/ogg/$(fn).ogg:system/media/audio/ringtones/$(fn).ogg)
+
+PRODUCT_COPY_FILES += $(foreach fn,$(EFFECT_FILES),\
+ $(LOCAL_PATH)/effects/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
+PRODUCT_COPY_FILES += $(foreach fn,$(MATERIAL_EFFECT_FILES),\
+ $(LOCAL_PATH)/effects/material/ogg/$(fn).ogg:system/media/audio/ui/$(fn).ogg)
diff --git a/data/sounds/README.txt b/data/sounds/README.txt
index 193fd7179f5f..db20319f6bfc 100644
--- a/data/sounds/README.txt
+++ b/data/sounds/README.txt
@@ -31,3 +31,13 @@ Video recording
./effects/ogg/VideoStop_48k.ogg
unused
+NFC
+---
+
+./effects/ogg/NFCFailure.ogg
+./effects/ogg/NFCInitiated.ogg
+./effects/ogg/NFCSuccess.ogg
+./effects/ogg/NFCTransferComplete.ogg
+./effects/ogg/NFCTransferInitiated.ogg
+
+referenced in AudioPackage14.mk (= AudioPackage13.mk + NFC sounds).
diff --git a/data/sounds/effects/ogg/NFCFailure.ogg b/data/sounds/effects/ogg/NFCFailure.ogg
new file mode 100644
index 000000000000..e9ee6624b16c
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCFailure.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCInitiated.ogg b/data/sounds/effects/ogg/NFCInitiated.ogg
new file mode 100644
index 000000000000..a86319faaac7
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCInitiated.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCSuccess.ogg b/data/sounds/effects/ogg/NFCSuccess.ogg
new file mode 100644
index 000000000000..39dfd1f9d863
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCSuccess.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCTransferComplete.ogg b/data/sounds/effects/ogg/NFCTransferComplete.ogg
new file mode 100644
index 000000000000..f00cd9876eff
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCTransferComplete.ogg
Binary files differ
diff --git a/data/sounds/effects/ogg/NFCTransferInitiated.ogg b/data/sounds/effects/ogg/NFCTransferInitiated.ogg
new file mode 100644
index 000000000000..7be1bcbe6375
--- /dev/null
+++ b/data/sounds/effects/ogg/NFCTransferInitiated.ogg
Binary files differ
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 0db779906545..59760ab0a20f 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -48,6 +48,11 @@ cc_defaults {
device_uses_hwc2: {
cflags: ["-DUSE_HWC2"],
},
+ eng: {
+ lto: {
+ never: true,
+ },
+ },
},
}
@@ -198,6 +203,7 @@ cc_defaults {
"AnimatorManager.cpp",
"Caches.cpp",
"CanvasState.cpp",
+ "CanvasTransform.cpp",
"ClipArea.cpp",
"DamageAccumulator.cpp",
"DeferredLayerUpdater.cpp",
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
new file mode 100644
index 000000000000..bac7a4d17a49
--- /dev/null
+++ b/libs/hwui/CanvasTransform.cpp
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+#include "CanvasTransform.h"
+#include "Properties.h"
+
+#include <SkColorFilter.h>
+#include <SkPaint.h>
+#include <log/log.h>
+
+namespace android::uirenderer {
+
+static SkColor makeLight(SkColor color) {
+ SkScalar hsv[3];
+ SkColorToHSV(color, hsv);
+ if (hsv[1] > .2f) return color;
+ // hsv[1] *= .85f;
+ // hsv[2] = std::min(1.0f, std::max(hsv[2], 1 - hsv[2]) * 1.3f);
+ hsv[2] = std::max(hsv[2], 1.1f - hsv[2]);
+ return SkHSVToColor(SkColorGetA(color), hsv);
+}
+
+static SkColor makeDark(SkColor color) {
+ SkScalar hsv[3];
+ SkColorToHSV(color, hsv);
+ if (hsv[1] > .2f) return color;
+ // hsv[1] *= .85f;
+ // hsv[2] = std::max(0.0f, std::min(hsv[2], 1 - hsv[2]) * .7f);
+ hsv[2] = std::min(hsv[2], 1.1f - hsv[2]);
+ return SkHSVToColor(SkColorGetA(color), hsv);
+}
+
+static SkColor transformColor(ColorTransform transform, SkColor color) {
+ switch (transform) {
+ case ColorTransform::Light:
+ return makeLight(color);
+ case ColorTransform::Dark:
+ return makeDark(color);
+ default:
+ return color;
+ }
+}
+
+static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
+ if (transform == ColorTransform::None) return;
+
+ SkColor newColor = transformColor(transform, paint.getColor());
+ paint.setColor(newColor);
+
+ if (paint.getColorFilter()) {
+ SkBlendMode mode;
+ SkColor color;
+ // TODO: LRU this or something to avoid spamming new color mode filters
+ if (paint.getColorFilter()->asColorMode(&color, &mode)) {
+ color = transformColor(transform, color);
+ paint.setColorFilter(SkColorFilter::MakeModeFilter(color, mode));
+ }
+ }
+}
+
+class ColorFilterCanvas : public SkPaintFilterCanvas {
+public:
+ ColorFilterCanvas(ColorTransform transform, SkCanvas* canvas)
+ : SkPaintFilterCanvas(canvas), mTransform(transform) {}
+
+ bool onFilter(SkTCopyOnFirstWrite<SkPaint>* paint, Type type) const override {
+ if (*paint) {
+ applyColorTransform(mTransform, *(paint->writable()));
+ }
+ return true;
+ }
+
+private:
+ ColorTransform mTransform;
+};
+
+std::unique_ptr<SkCanvas> makeTransformCanvas(SkCanvas* inCanvas, ColorTransform transform) {
+ switch (transform) {
+ case ColorTransform::Light:
+ return std::make_unique<ColorFilterCanvas>(ColorTransform::Light, inCanvas);
+ case ColorTransform::Dark:
+ return std::make_unique<ColorFilterCanvas>(ColorTransform::Dark, inCanvas);
+ default:
+ return nullptr;
+ }
+}
+
+std::unique_ptr<SkCanvas> makeTransformCanvas(SkCanvas* inCanvas, UsageHint usageHint) {
+ if (Properties::forceDarkMode) {
+ switch (usageHint) {
+ case UsageHint::Unknown:
+ return makeTransformCanvas(inCanvas, ColorTransform::Light);
+ case UsageHint::Background:
+ return makeTransformCanvas(inCanvas, ColorTransform::Dark);
+ }
+ }
+ return nullptr;
+}
+
+}; // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
new file mode 100644
index 000000000000..f71fdfaf3fba
--- /dev/null
+++ b/libs/hwui/CanvasTransform.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <SkCanvas.h>
+#include <SkPaintFilterCanvas.h>
+
+#include <memory>
+
+namespace android::uirenderer {
+
+enum class UsageHint {
+ Unknown = 0,
+ Background = 1,
+};
+
+enum class ColorTransform {
+ None,
+ Light,
+ Dark,
+};
+
+std::unique_ptr<SkCanvas> makeTransformCanvas(SkCanvas* inCanvas, ColorTransform transform);
+std::unique_ptr<SkCanvas> makeTransformCanvas(SkCanvas* inCanvas, UsageHint usageHint);
+
+} // namespace android::uirenderer; \ No newline at end of file
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 0338a3aba97d..17bec1934490 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -60,6 +60,7 @@ bool Properties::forceDrawFrame = false;
bool Properties::filterOutTestOverhead = false;
bool Properties::disableVsync = false;
bool Properties::skpCaptureEnabled = false;
+bool Properties::forceDarkMode = false;
bool Properties::enableRTAnimations = true;
bool Properties::runningInEmulator = false;
@@ -146,6 +147,8 @@ bool Properties::load() {
runningInEmulator = property_get_bool(PROPERTY_QEMU_KERNEL, false);
+ forceDarkMode = property_get_bool(PROPERTY_FORCE_DARK, false);
+
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) ||
(prevDebugStencilClip != debugStencilClip);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index d640c749d829..ea017a72cd74 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -190,6 +190,8 @@ enum DebugLevel {
*/
#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
+#define PROPERTY_FORCE_DARK "debug.hwui.force_dark"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -263,6 +265,7 @@ public:
static bool disableVsync;
static bool skpCaptureEnabled;
+ static bool forceDarkMode;
// For experimentation b/68769804
ANDROID_API static bool enableRTAnimations;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index dc962f307903..8393288d2ffd 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -28,6 +28,7 @@
#include <androidfw/ResourceTypes.h>
#include "AnimatorManager.h"
+#include "CanvasTransform.h"
#include "Debug.h"
#include "DisplayList.h"
#include "Matrix.h"
@@ -208,6 +209,14 @@ public:
void output(std::ostream& output, uint32_t level);
+ void setUsageHint(UsageHint usageHint) {
+ mUsageHint = usageHint;
+ }
+
+ UsageHint usageHint() const {
+ return mUsageHint;
+ }
+
private:
void computeOrderingImpl(RenderNodeOp* opState,
std::vector<RenderNodeOp*>* compositedChildrenOfProjectionSurface,
@@ -263,6 +272,8 @@ private:
sp<PositionListener> mPositionListener;
+ UsageHint mUsageHint = UsageHint::Unknown;
+
// METHODS & FIELDS ONLY USED BY THE SKIA RENDERER
public:
/**
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index e49574462e9e..ff9cf45cdc73 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -44,8 +44,8 @@ void LayerProperties::reset() {
}
bool LayerProperties::setColorFilter(SkColorFilter* filter) {
- if (mColorFilter == filter) return false;
- SkRefCnt_SafeAssign(mColorFilter, filter);
+ if (mColorFilter.get() == filter) return false;
+ mColorFilter = sk_ref_sp(filter);
return true;
}
@@ -62,7 +62,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) {
setOpaque(other.opaque());
setAlpha(other.alpha());
setXferMode(other.xferMode());
- setColorFilter(other.colorFilter());
+ setColorFilter(other.getColorFilter());
return *this;
}
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index c024373efb9e..0766e3b7ed28 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -89,9 +89,7 @@ public:
SkBlendMode xferMode() const { return mMode; }
- bool setColorFilter(SkColorFilter* filter);
-
- SkColorFilter* colorFilter() const { return mColorFilter; }
+ SkColorFilter* getColorFilter() const { return mColorFilter.get(); }
// Sets alpha, xfermode, and colorfilter from an SkPaint
// paint may be NULL, in which case defaults will be set
@@ -105,6 +103,7 @@ private:
LayerProperties();
~LayerProperties();
void reset();
+ bool setColorFilter(SkColorFilter* filter);
// Private since external users should go through properties().effectiveLayerType()
LayerType type() const { return mType; }
@@ -116,7 +115,7 @@ private:
bool mOpaque;
uint8_t mAlpha;
SkBlendMode mMode;
- SkColorFilter* mColorFilter = nullptr;
+ sk_sp<SkColorFilter> mColorFilter;
};
/*
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 7b41f89605d2..17f1a3b4db3c 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -21,6 +21,7 @@
#include "VectorDrawable.h"
#include "hwui/Bitmap.h"
#include "hwui/MinikinUtils.h"
+#include "hwui/PaintFilter.h"
#include "pipeline/skia/AnimatedDrawables.h"
#include <SkAnimatedImage.h>
@@ -28,7 +29,6 @@
#include <SkColorFilter.h>
#include <SkColorSpaceXformCanvas.h>
#include <SkDeque.h>
-#include <SkDrawFilter.h>
#include <SkDrawable.h>
#include <SkGraphics.h>
#include <SkImage.h>
@@ -40,6 +40,8 @@
#include <SkTextBlob.h>
#include <memory>
+#include <optional>
+#include <utility>
namespace android {
@@ -211,7 +213,7 @@ public:
Clip(const SkRRect& rrect, SkClipOp op, const SkMatrix& m)
: mType(Type::RRect), mOp(op), mMatrix(m), mRRect(rrect) {}
Clip(const SkPath& path, SkClipOp op, const SkMatrix& m)
- : mType(Type::Path), mOp(op), mMatrix(m), mPath(&path) {}
+ : mType(Type::Path), mOp(op), mMatrix(m), mPath(std::in_place, path) {}
void apply(SkCanvas* canvas) const {
canvas->setMatrix(mMatrix);
@@ -223,7 +225,7 @@ public:
canvas->clipRRect(mRRect, mOp);
break;
case Type::Path:
- canvas->clipPath(*mPath.get(), mOp);
+ canvas->clipPath(mPath.value(), mOp);
break;
}
}
@@ -240,7 +242,7 @@ private:
SkMatrix mMatrix;
// These are logically a union (tracked separately due to non-POD path).
- SkTLazy<SkPath> mPath;
+ std::optional<SkPath> mPath;
SkRRect mRRect;
};
@@ -400,12 +402,12 @@ bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
// Canvas state operations: Filters
// ----------------------------------------------------------------------------
-SkDrawFilter* SkiaCanvas::getDrawFilter() {
- return mCanvas->getDrawFilter();
+PaintFilter* SkiaCanvas::getPaintFilter() {
+ return mPaintFilter.get();
}
-void SkiaCanvas::setDrawFilter(SkDrawFilter* drawFilter) {
- mCanvas->setDrawFilter(drawFilter);
+void SkiaCanvas::setPaintFilter(sk_sp<PaintFilter> paintFilter) {
+ mPaintFilter = std::move(paintFilter);
}
// ----------------------------------------------------------------------------
@@ -439,8 +441,15 @@ void SkiaCanvas::drawColor(int color, SkBlendMode mode) {
mCanvas->drawColor(color, mode);
}
+SkiaCanvas::PaintCoW&& SkiaCanvas::filterPaint(PaintCoW&& paint) const {
+ if (mPaintFilter) {
+ mPaintFilter->filter(&paint.writeable());
+ }
+ return std::move(paint);
+}
+
void SkiaCanvas::drawPaint(const SkPaint& paint) {
- mCanvas->drawPaint(paint);
+ mCanvas->drawPaint(*filterPaint(paint));
}
// ----------------------------------------------------------------------------
@@ -457,53 +466,53 @@ void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint
pts[i].set(points[0], points[1]);
points += 2;
}
- mCanvas->drawPoints(mode, count, pts.get(), paint);
+ mCanvas->drawPoints(mode, count, pts.get(), *filterPaint(paint));
}
void SkiaCanvas::drawPoint(float x, float y, const SkPaint& paint) {
- mCanvas->drawPoint(x, y, paint);
+ mCanvas->drawPoint(x, y, *filterPaint(paint));
}
void SkiaCanvas::drawPoints(const float* points, int count, const SkPaint& paint) {
- this->drawPoints(points, count, paint, SkCanvas::kPoints_PointMode);
+ this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kPoints_PointMode);
}
void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
const SkPaint& paint) {
- mCanvas->drawLine(startX, startY, stopX, stopY, paint);
+ mCanvas->drawLine(startX, startY, stopX, stopY, *filterPaint(paint));
}
void SkiaCanvas::drawLines(const float* points, int count, const SkPaint& paint) {
if (CC_UNLIKELY(count < 4 || paint.nothingToDraw())) return;
- this->drawPoints(points, count, paint, SkCanvas::kLines_PointMode);
+ this->drawPoints(points, count, *filterPaint(paint), SkCanvas::kLines_PointMode);
}
void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
- mCanvas->drawRect({left, top, right, bottom}, paint);
+ mCanvas->drawRect({left, top, right, bottom}, *filterPaint(paint));
}
void SkiaCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
- mCanvas->drawRegion(region, paint);
+ mCanvas->drawRegion(region, *filterPaint(paint));
}
void SkiaCanvas::drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect rect = SkRect::MakeLTRB(left, top, right, bottom);
- mCanvas->drawRoundRect(rect, rx, ry, paint);
+ mCanvas->drawRoundRect(rect, rx, ry, *filterPaint(paint));
}
void SkiaCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
if (CC_UNLIKELY(radius <= 0 || paint.nothingToDraw())) return;
- mCanvas->drawCircle(x, y, radius, paint);
+ mCanvas->drawCircle(x, y, radius, *filterPaint(paint));
}
void SkiaCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect oval = SkRect::MakeLTRB(left, top, right, bottom);
- mCanvas->drawOval(oval, paint);
+ mCanvas->drawOval(oval, *filterPaint(paint));
}
void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float startAngle,
@@ -511,9 +520,9 @@ void SkiaCanvas::drawArc(float left, float top, float right, float bottom, float
if (CC_UNLIKELY(paint.nothingToDraw())) return;
SkRect arc = SkRect::MakeLTRB(left, top, right, bottom);
if (fabs(sweepAngle) >= 360.0f) {
- mCanvas->drawOval(arc, paint);
+ mCanvas->drawOval(arc, *filterPaint(paint));
} else {
- mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, paint);
+ mCanvas->drawArc(arc, startAngle, sweepAngle, useCenter, *filterPaint(paint));
}
}
@@ -522,19 +531,19 @@ void SkiaCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
if (CC_UNLIKELY(path.isEmpty() && (!path.isInverseFillType()))) {
return;
}
- mCanvas->drawPath(path, paint);
+ mCanvas->drawPath(path, *filterPaint(paint));
}
void SkiaCanvas::drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) {
- mCanvas->drawVertices(vertices, mode, paint);
+ mCanvas->drawVertices(vertices, mode, *filterPaint(paint));
}
// ----------------------------------------------------------------------------
// Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
-const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter) {
+SkiaCanvas::PaintCoW&& SkiaCanvas::filterBitmap(PaintCoW&& paint,
+ sk_sp<SkColorFilter> colorSpaceFilter) const {
/* We don't apply the colorSpace filter if this canvas is already wrapped with
* a SkColorSpaceXformCanvas since it already takes care of converting the
* contents of the bitmap into the appropriate colorspace. The mCanvasWrapper
@@ -542,39 +551,31 @@ const SkPaint* SkiaCanvas::addFilter(const SkPaint* origPaint, SkPaint* tmpPaint
* to have a non-sRGB colorspace.
*/
if (!mCanvasWrapper && colorSpaceFilter) {
- if (origPaint) {
- *tmpPaint = *origPaint;
- }
-
- if (tmpPaint->getColorFilter()) {
- tmpPaint->setColorFilter(
- SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter));
- LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
+ SkPaint& tmpPaint = paint.writeable();
+ if (tmpPaint.getColorFilter()) {
+ tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(tmpPaint.refColorFilter(),
+ std::move(colorSpaceFilter)));
+ LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter());
} else {
- tmpPaint->setColorFilter(colorSpaceFilter);
+ tmpPaint.setColorFilter(std::move(colorSpaceFilter));
}
-
- return tmpPaint;
- } else {
- return origPaint;
}
+ return filterPaint(std::move(paint));
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImage(image, left, top, addFilter(paint, &tmpPaint, colorFilter));
+ mCanvas->drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)));
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, const SkPaint* paint) {
SkAutoCanvasRestore acr(mCanvas, true);
mCanvas->concat(matrix);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImage(image, 0, 0, addFilter(paint, &tmpPaint, colorFilter));
+ mCanvas->drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)));
}
void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float srcRight,
@@ -583,10 +584,9 @@ void SkiaCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop, float s
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImageRect(image, srcRect, dstRect, addFilter(paint, &tmpPaint, colorFilter),
+ mCanvas->drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)),
SkCanvas::kFast_SrcRectConstraint);
}
@@ -665,21 +665,20 @@ void SkiaCanvas::drawBitmapMesh(Bitmap& bitmap, int meshWidth, int meshHeight,
#endif
// cons-up a shader for the bitmap
- SkPaint tmpPaint;
- if (paint) {
- tmpPaint = *paint;
- }
+ PaintCoW paintCoW(paint);
+ SkPaint& tmpPaint = paintCoW.writeable();
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
sk_sp<SkShader> shader =
image->makeShader(SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
if (colorFilter) {
- shader = shader->makeWithColorFilter(colorFilter);
+ shader = shader->makeWithColorFilter(std::move(colorFilter));
}
- tmpPaint.setShader(shader);
+ tmpPaint.setShader(std::move(shader));
- mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate, tmpPaint);
+ mCanvas->drawVertices(builder.detach(), SkBlendMode::kModulate,
+ *filterPaint(std::move(paintCoW)));
}
void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, float dstLeft,
@@ -706,10 +705,10 @@ void SkiaCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& chunk, floa
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mCanvas->drawImageLattice(image.get(), lattice, dst, addFilter(paint, &tmpPaint, colorFilter));
+ mCanvas->drawImageLattice(image.get(), lattice, dst,
+ filterBitmap(paint, std::move(colorFilter)));
}
double SkiaCanvas::drawAnimatedImage(AnimatedImageDrawable* imgDrawable) {
@@ -732,6 +731,9 @@ void SkiaCanvas::drawGlyphs(ReadGlyphFunc glyphFunc, int count, const SkPaint& p
// glyphs centered or right-aligned; the offset above takes
// care of all alignment.
SkPaint paintCopy(paint);
+ if (mPaintFilter) {
+ mPaintFilter->filter(&paintCopy);
+ }
paintCopy.setTextAlign(SkPaint::kLeft_Align);
SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
// Stroke with a hairline is drawn on HW with a fill style for compatibility with Android O and
@@ -760,6 +762,9 @@ void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset,
// glyphs centered or right-aligned; the offsets take care of
// that portion of the alignment.
SkPaint paintCopy(paint);
+ if (mPaintFilter) {
+ mPaintFilter->filter(&paintCopy);
+ }
paintCopy.setTextAlign(SkPaint::kLeft_Align);
SkASSERT(paintCopy.getTextEncoding() == SkPaint::kGlyphID_TextEncoding);
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 3efc22a03cdf..24b7ec6d5c7b 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -22,7 +22,9 @@
#include "hwui/Canvas.h"
#include <SkCanvas.h>
-#include <SkTLazy.h>
+
+#include <cassert>
+#include <optional>
namespace android {
@@ -87,8 +89,8 @@ public:
virtual bool clipRect(float left, float top, float right, float bottom, SkClipOp op) override;
virtual bool clipPath(const SkPath* path, SkClipOp op) override;
- virtual SkDrawFilter* getDrawFilter() override;
- virtual void setDrawFilter(SkDrawFilter* drawFilter) override;
+ virtual PaintFilter* getPaintFilter() override;
+ virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) override;
virtual SkCanvasState* captureCanvasState() const override;
@@ -158,6 +160,46 @@ protected:
const SkPaint& paint, const SkPath& path, size_t start,
size_t end) override;
+ /** This class acts as a copy on write SkPaint.
+ *
+ * Initially this will be the SkPaint passed to the contructor.
+ * The first time writable() is called this will become a copy of the
+ * initial SkPaint (or a default SkPaint if nullptr).
+ */
+ struct PaintCoW {
+ PaintCoW(const SkPaint& that) : mPtr(&that) {}
+ PaintCoW(const SkPaint* ptr) : mPtr(ptr) {}
+ PaintCoW(const PaintCoW&) = delete;
+ PaintCoW(PaintCoW&&) = delete;
+ PaintCoW& operator=(const PaintCoW&) = delete;
+ PaintCoW& operator=(PaintCoW&&) = delete;
+ SkPaint& writeable() {
+ if (!mStorage) {
+ if (!mPtr) {
+ mStorage.emplace();
+ } else {
+ mStorage.emplace(*mPtr);
+ }
+ mPtr = &*mStorage;
+ }
+ return *mStorage;
+ }
+ operator const SkPaint*() const { return mPtr; }
+ const SkPaint* operator->() const { assert(mPtr); return mPtr; }
+ const SkPaint& operator*() const { assert(mPtr); return *mPtr; }
+ explicit operator bool() { return mPtr != nullptr; }
+ private:
+ const SkPaint* mPtr;
+ std::optional<SkPaint> mStorage;
+ };
+
+ /** Filters the paint using the current paint filter.
+ *
+ * @param paint the paint to filter. Will be initialized with the default
+ * SkPaint before filtering if filtering is required.
+ */
+ PaintCoW&& filterPaint(PaintCoW&& paint) const;
+
private:
struct SaveRec {
int saveCount;
@@ -174,8 +216,15 @@ private:
void drawPoints(const float* points, int count, const SkPaint& paint, SkCanvas::PointMode mode);
- const SkPaint* addFilter(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter);
+ /** Filters the paint for bitmap drawing.
+ *
+ * After filtering the paint for bitmap drawing,
+ * also calls filterPaint on the paint.
+ *
+ * @param paint the paint to filter. Will be initialized with the default
+ * SkPaint before filtering if filtering is required.
+ */
+ PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter) const;
class Clip;
@@ -185,6 +234,7 @@ private:
// unless it is the same as mCanvasOwned.get()
std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves.
std::vector<Clip> mClipStack; // tracks persistent clips.
+ sk_sp<PaintFilter> mPaintFilter;
};
} // namespace android
diff --git a/libs/hwui/VectorDrawable.h b/libs/hwui/VectorDrawable.h
index e84b9acffca7..af0ae05a74b3 100644
--- a/libs/hwui/VectorDrawable.h
+++ b/libs/hwui/VectorDrawable.h
@@ -59,12 +59,6 @@ namespace VectorDrawable {
onPropertyChanged(); \
retVal; \
})
-#define UPDATE_SKPROP(field, value) \
- ({ \
- bool retVal = ((field) != (value)); \
- if ((field) != (value)) SkRefCnt_SafeAssign((field), (value)); \
- retVal; \
- })
/* A VectorDrawable is composed of a tree of nodes.
* Each node can be a group node, or a path.
@@ -223,29 +217,28 @@ public:
int fillType = 0; /* non-zero or kWinding_FillType in Skia */
};
explicit FullPathProperties(Node* mNode) : Properties(mNode), mTrimDirty(false) {}
- ~FullPathProperties() {
- SkSafeUnref(fillGradient);
- SkSafeUnref(strokeGradient);
- }
+ ~FullPathProperties() {}
void syncProperties(const FullPathProperties& prop) {
mPrimitiveFields = prop.mPrimitiveFields;
mTrimDirty = true;
- UPDATE_SKPROP(fillGradient, prop.fillGradient);
- UPDATE_SKPROP(strokeGradient, prop.strokeGradient);
+ fillGradient = prop.fillGradient;
+ strokeGradient = prop.strokeGradient;
onPropertyChanged();
}
void setFillGradient(SkShader* gradient) {
- if (UPDATE_SKPROP(fillGradient, gradient)) {
+ if (fillGradient.get() != gradient) {
+ fillGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
void setStrokeGradient(SkShader* gradient) {
- if (UPDATE_SKPROP(strokeGradient, gradient)) {
+ if (strokeGradient.get() != gradient) {
+ strokeGradient = sk_ref_sp(gradient);
onPropertyChanged();
}
}
- SkShader* getFillGradient() const { return fillGradient; }
- SkShader* getStrokeGradient() const { return strokeGradient; }
+ SkShader* getFillGradient() const { return fillGradient.get(); }
+ SkShader* getStrokeGradient() const { return strokeGradient.get(); }
float getStrokeWidth() const { return mPrimitiveFields.strokeWidth; }
void setStrokeWidth(float strokeWidth) {
VD_SET_PRIMITIVE_FIELD_AND_NOTIFY(strokeWidth, strokeWidth);
@@ -325,8 +318,8 @@ public:
count,
};
PrimitiveFields mPrimitiveFields;
- SkShader* fillGradient = nullptr;
- SkShader* strokeGradient = nullptr;
+ sk_sp<SkShader> fillGradient;
+ sk_sp<SkShader> strokeGradient;
};
// Called from UI thread
@@ -550,8 +543,7 @@ public:
SkRect bounds;
int scaledWidth = 0;
int scaledHeight = 0;
- SkColorFilter* colorFilter = nullptr;
- ~NonAnimatableProperties() { SkSafeUnref(colorFilter); }
+ sk_sp<SkColorFilter> colorFilter;
} mNonAnimatableProperties;
bool mNonAnimatablePropertiesDirty = true;
@@ -561,8 +553,7 @@ public:
void syncNonAnimatableProperties(const TreeProperties& prop) {
// Copy over the data that can only be changed in UI thread
if (mNonAnimatableProperties.colorFilter != prop.mNonAnimatableProperties.colorFilter) {
- SkRefCnt_SafeAssign(mNonAnimatableProperties.colorFilter,
- prop.mNonAnimatableProperties.colorFilter);
+ mNonAnimatableProperties.colorFilter = prop.mNonAnimatableProperties.colorFilter;
}
mNonAnimatableProperties = prop.mNonAnimatableProperties;
}
@@ -599,12 +590,13 @@ public:
}
}
void setColorFilter(SkColorFilter* filter) {
- if (UPDATE_SKPROP(mNonAnimatableProperties.colorFilter, filter)) {
+ if (mNonAnimatableProperties.colorFilter.get() != filter) {
+ mNonAnimatableProperties.colorFilter = sk_ref_sp(filter);
mNonAnimatablePropertiesDirty = true;
mTree->onPropertyChanged(this);
}
}
- SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter; }
+ SkColorFilter* getColorFilter() const { return mNonAnimatableProperties.colorFilter.get(); }
float getViewportWidth() const { return mNonAnimatableProperties.viewportWidth; }
float getViewportHeight() const { return mNonAnimatableProperties.viewportHeight; }
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index fb6bd6f0821a..af7f013e27b1 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -23,7 +23,7 @@
#include "Typeface.h"
#include "pipeline/skia/SkiaRecordingCanvas.h"
-#include <SkDrawFilter.h>
+#include "hwui/PaintFilter.h"
namespace android {
@@ -40,10 +40,10 @@ static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkSca
void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) {
uint32_t flags;
- SkDrawFilter* drawFilter = getDrawFilter();
- if (drawFilter) {
+ PaintFilter* paintFilter = getPaintFilter();
+ if (paintFilter) {
SkPaint paintCopy(paint);
- drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type);
+ paintFilter->filter(&paintCopy);
flags = paintCopy.getFlags();
} else {
flags = paint.getFlags();
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 88b12b258419..5d380a68631f 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -39,6 +39,7 @@ enum class Bidi : uint8_t;
}
namespace android {
+class PaintFilter;
namespace uirenderer {
class CanvasPropertyPaint;
@@ -213,8 +214,8 @@ public:
virtual bool clipPath(const SkPath* path, SkClipOp op) = 0;
// filters
- virtual SkDrawFilter* getDrawFilter() = 0;
- virtual void setDrawFilter(SkDrawFilter* drawFilter) = 0;
+ virtual PaintFilter* getPaintFilter() = 0;
+ virtual void setPaintFilter(sk_sp<PaintFilter> paintFilter) = 0;
// WebView only
virtual SkCanvasState* captureCanvasState() const { return nullptr; }
diff --git a/libs/hwui/hwui/PaintFilter.h b/libs/hwui/hwui/PaintFilter.h
new file mode 100644
index 000000000000..bf5627eac229
--- /dev/null
+++ b/libs/hwui/hwui/PaintFilter.h
@@ -0,0 +1,19 @@
+#ifndef ANDROID_GRAPHICS_PAINT_FILTER_H_
+#define ANDROID_GRAPHICS_PAINT_FILTER_H_
+
+class SkPaint;
+
+namespace android {
+
+class PaintFilter : public SkRefCnt {
+public:
+ /**
+ * Called with the paint that will be used to draw.
+ * The implementation may modify the paint as they wish.
+ */
+ virtual void filter(SkPaint*) = 0;
+};
+
+} // namespace android
+
+#endif
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index c195a8eee870..85fdc103b1e3 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -156,10 +156,10 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip
SkPaint* paint) {
paint->setFilterQuality(kLow_SkFilterQuality);
if (alphaMultiplier < 1.0f || properties.alpha() < 255 ||
- properties.xferMode() != SkBlendMode::kSrcOver || properties.colorFilter() != nullptr) {
+ properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) {
paint->setAlpha(properties.alpha() * alphaMultiplier);
paint->setBlendMode(properties.xferMode());
- paint->setColorFilter(sk_ref_sp(properties.colorFilter()));
+ paint->setColorFilter(sk_ref_sp(properties.getColorFilter()));
return true;
}
return false;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index f0da660f17b0..46e39aa58c6f 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -17,6 +17,7 @@
#include "SkiaRecordingCanvas.h"
#include <SkImagePriv.h>
+#include "CanvasTransform.h"
#include "Layer.h"
#include "LayerDrawable.h"
#include "NinePatchUtils.h"
@@ -44,13 +45,21 @@ void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, in
}
mDisplayList->attachRecorder(&mRecorder, SkIRect::MakeWH(width, height));
- SkiaCanvas::reset(&mRecorder);
+ SkCanvas* canvas = &mRecorder;
+ if (renderNode) {
+ mWrappedCanvas = makeTransformCanvas(&mRecorder, renderNode->usageHint());
+ if (mWrappedCanvas) {
+ canvas = mWrappedCanvas.get();
+ }
+ }
+ SkiaCanvas::reset(canvas);
}
uirenderer::DisplayList* SkiaRecordingCanvas::finishRecording() {
// close any existing chunks if necessary
insertReorderBarrier(false);
mRecorder.restoreToCount(1);
+ mWrappedCanvas = nullptr;
return mDisplayList.release();
}
@@ -148,12 +157,45 @@ void SkiaRecordingCanvas::drawVectorDrawable(VectorDrawableRoot* tree) {
// Recording Canvas draw operations: Bitmaps
// ----------------------------------------------------------------------------
+SkiaCanvas::PaintCoW&& SkiaRecordingCanvas::filterBitmap(PaintCoW&& paint,
+ sk_sp<SkColorFilter> colorSpaceFilter) {
+ bool fixBlending = false;
+ bool fixAA = false;
+ if (paint) {
+ // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
+ // older.
+ fixBlending = sApiLevel <= 27 && paint->getBlendMode() == SkBlendMode::kClear;
+ fixAA = paint->isAntiAlias();
+ }
+
+ if (fixBlending || fixAA || colorSpaceFilter) {
+ SkPaint& tmpPaint = paint.writeable();
+
+ if (fixBlending) {
+ tmpPaint.setBlendMode(SkBlendMode::kDstOut);
+ }
+
+ if (colorSpaceFilter) {
+ if (tmpPaint.getColorFilter()) {
+ tmpPaint.setColorFilter(SkColorFilter::MakeComposeFilter(
+ tmpPaint.refColorFilter(), std::move(colorSpaceFilter)));
+ } else {
+ tmpPaint.setColorFilter(std::move(colorSpaceFilter));
+ }
+ LOG_ALWAYS_FATAL_IF(!tmpPaint.getColorFilter());
+ }
+
+ // disabling AA on bitmap draws matches legacy HWUI behavior
+ tmpPaint.setAntiAlias(false);
+ }
+
+ return filterPaint(std::move(paint));
+}
void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const SkPaint* paint) {
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mRecorder.drawImage(image, left, top, bitmapPaint(paint, &tmpPaint, colorFilter));
+ mRecorder.drawImage(image, left, top, filterBitmap(paint, std::move(colorFilter)));
// if image->unique() is true, then mRecorder.drawImage failed for some reason. It also means
// it is not safe to store a raw SkImage pointer, because the image object will be destroyed
// when this function ends.
@@ -166,10 +208,9 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, const SkMatrix& matrix, con
SkAutoCanvasRestore acr(&mRecorder, true);
concat(matrix);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mRecorder.drawImage(image, 0, 0, bitmapPaint(paint, &tmpPaint, colorFilter));
+ mRecorder.drawImage(image, 0, 0, filterBitmap(paint, std::move(colorFilter)));
if (!bitmap.isImmutable() && image.get() && !image->unique()) {
mDisplayList->mMutableImages.push_back(image.get());
}
@@ -181,10 +222,9 @@ void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float srcLeft, float srcTop
SkRect srcRect = SkRect::MakeLTRB(srcLeft, srcTop, srcRight, srcBottom);
SkRect dstRect = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
sk_sp<SkColorFilter> colorFilter;
sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- mRecorder.drawImageRect(image, srcRect, dstRect, bitmapPaint(paint, &tmpPaint, colorFilter),
+ mRecorder.drawImageRect(image, srcRect, dstRect, filterBitmap(paint, std::move(colorFilter)),
SkCanvas::kFast_SrcRectConstraint);
if (!bitmap.isImmutable() && image.get() && !image->unique() && !srcRect.isEmpty() &&
!dstRect.isEmpty()) {
@@ -216,23 +256,16 @@ void SkiaRecordingCanvas::drawNinePatch(Bitmap& bitmap, const Res_png_9patch& ch
lattice.fBounds = nullptr;
SkRect dst = SkRect::MakeLTRB(dstLeft, dstTop, dstRight, dstBottom);
- SkPaint tmpPaint;
- sk_sp<SkColorFilter> colorFilter;
- sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
- const SkPaint* filteredPaint = bitmapPaint(paint, &tmpPaint, colorFilter);
+ PaintCoW filteredPaint(paint);
// Besides kNone, the other three SkFilterQualities are treated the same. And Android's
// Java API only supports kLow and kNone anyway.
if (!filteredPaint || filteredPaint->getFilterQuality() == kNone_SkFilterQuality) {
- if (filteredPaint != &tmpPaint) {
- if (paint) {
- tmpPaint = *paint;
- }
- filteredPaint = &tmpPaint;
- }
- tmpPaint.setFilterQuality(kLow_SkFilterQuality);
+ filteredPaint.writeable().setFilterQuality(kLow_SkFilterQuality);
}
-
- mRecorder.drawImageLattice(image.get(), lattice, dst, filteredPaint);
+ sk_sp<SkColorFilter> colorFilter;
+ sk_sp<SkImage> image = bitmap.makeImage(&colorFilter);
+ mRecorder.drawImageLattice(image.get(), lattice, dst,
+ filterBitmap(std::move(filteredPaint), std::move(colorFilter)));
if (!bitmap.isImmutable() && image.get() && !image->unique() && !dst.isEmpty()) {
mDisplayList->mMutableImages.push_back(image.get());
}
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 93807a5476e6..50d84d6cf101 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -78,6 +78,7 @@ public:
private:
SkLiteRecorder mRecorder;
std::unique_ptr<SkiaDisplayList> mDisplayList;
+ std::unique_ptr<SkCanvas> mWrappedCanvas;
StartReorderBarrierDrawable* mCurrentBarrier;
/**
@@ -89,44 +90,7 @@ private:
*/
void initDisplayList(uirenderer::RenderNode* renderNode, int width, int height);
- inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorSpaceFilter) {
- bool fixBlending = false;
- bool fixAA = false;
- if (origPaint) {
- // kClear blend mode is drawn as kDstOut on HW for compatibility with Android O and
- // older.
- fixBlending = sApiLevel <= 27 && origPaint->getBlendMode() == SkBlendMode::kClear;
- fixAA = origPaint->isAntiAlias();
- }
-
- if (fixBlending || fixAA || colorSpaceFilter) {
- if (origPaint) {
- *tmpPaint = *origPaint;
- }
-
- if (fixBlending) {
- tmpPaint->setBlendMode(SkBlendMode::kDstOut);
- }
-
- if (colorSpaceFilter) {
- if (tmpPaint->getColorFilter()) {
- tmpPaint->setColorFilter(SkColorFilter::MakeComposeFilter(
- tmpPaint->refColorFilter(), colorSpaceFilter));
- } else {
- tmpPaint->setColorFilter(colorSpaceFilter);
- }
- LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
- }
-
- // disabling AA on bitmap draws matches legacy HWUI behavior
- tmpPaint->setAntiAlias(false);
- return tmpPaint;
- } else {
- return origPaint;
- }
- }
-
+ PaintCoW&& filterBitmap(PaintCoW&& paint, sk_sp<SkColorFilter> colorSpaceFilter);
};
}; // namespace skiapipeline
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index b74d3595e964..e3c97ce686d9 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -136,8 +136,6 @@ static bool setBenchmarkFormat(const char* format) {
gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
} else if (!strcmp(format, "json")) {
gBenchmarkReporter.reset(new benchmark::JSONReporter());
- } else if (!strcmp(format, "csv")) {
- gBenchmarkReporter.reset(new benchmark::CSVReporter());
} else {
fprintf(stderr, "Unknown format '%s'", format);
return false;
diff --git a/media/java/android/media/MediaPlayer2.java b/media/java/android/media/MediaPlayer2.java
index b747291d1d28..61c412dac185 100644
--- a/media/java/android/media/MediaPlayer2.java
+++ b/media/java/android/media/MediaPlayer2.java
@@ -2137,11 +2137,17 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener
*/
public static final int CALL_STATUS_ERROR_IO = 4;
+ /** Status code represents that the call has been skipped. For example, a {@link #seekTo}
+ * request may be skipped if it is followed by another {@link #seekTo} request.
+ * @see EventCallback#onCallCompleted
+ */
+ public static final int CALL_STATUS_SKIPPED = 5;
+
/** Status code represents that DRM operation is called before preparing a DRM scheme through
* {@link #prepareDrm}.
* @see android.media.MediaPlayer2.EventCallback#onCallCompleted
*/
- public static final int CALL_STATUS_NO_DRM_SCHEME = 5;
+ public static final int CALL_STATUS_NO_DRM_SCHEME = 6;
/**
* @hide
@@ -2153,6 +2159,7 @@ public abstract class MediaPlayer2 implements SubtitleController.Listener
CALL_STATUS_BAD_VALUE,
CALL_STATUS_PERMISSION_DENIED,
CALL_STATUS_ERROR_IO,
+ CALL_STATUS_SKIPPED,
CALL_STATUS_NO_DRM_SCHEME})
@Retention(RetentionPolicy.SOURCE)
public @interface CallStatus {}
diff --git a/media/java/android/media/MediaPlayer2Impl.java b/media/java/android/media/MediaPlayer2Impl.java
index 5a71afd4625e..dccfd7a87ba7 100644
--- a/media/java/android/media/MediaPlayer2Impl.java
+++ b/media/java/android/media/MediaPlayer2Impl.java
@@ -203,8 +203,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
* playback will continue from where it was paused. If playback had
* been stopped, or never started before, playback will start at the
* beginning.
- *
- * @throws IllegalStateException if it is called in an invalid state
*/
@Override
public void play() {
@@ -226,8 +224,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
* call prepare(). For streams, you should call prepare(),
* which returns immediately, rather than blocking until enough data has been
* buffered.
- *
- * @throws IllegalStateException if it is called in an invalid state
*/
@Override
public void prepare() {
@@ -243,9 +239,6 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
/**
* Pauses playback. Call play() to resume.
- *
- * @throws IllegalStateException if the internal player engine has not been
- * initialized.
*/
@Override
public void pause() {
@@ -351,23 +344,22 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
* Sets the data source as described by a DataSourceDesc.
*
* @param dsd the descriptor of data source you want to play
- * @throws IllegalStateException if it is called in an invalid state
- * @throws NullPointerException if dsd is null
*/
@Override
public void setDataSource(@NonNull DataSourceDesc dsd) {
addTask(new Task(CALL_COMPLETED_SET_DATA_SOURCE, false) {
@Override
- void process() {
- Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
- // TODO: setDataSource could update exist data source
+ void process() throws IOException {
+ Preconditions.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
+ int state = getState();
+ if (state != PLAYER_STATE_ERROR && state != PLAYER_STATE_IDLE) {
+ throw new IllegalStateException("called in wrong state " + state);
+ }
+
synchronized (mSrcLock) {
mCurrentDSD = dsd;
mCurrentSrcId = mSrcIdGenerator++;
- try {
- handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
- } catch (IOException e) {
- }
+ handleDataSource(true /* isCurrent */, dsd, mCurrentSrcId);
}
}
});
@@ -386,7 +378,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
addTask(new Task(CALL_COMPLETED_SET_NEXT_DATA_SOURCE, false) {
@Override
void process() {
- Preconditions.checkNotNull(dsd, "the DataSourceDesc cannot be null");
+ Preconditions.checkArgument(dsd != null, "the DataSourceDesc cannot be null");
synchronized (mSrcLock) {
mNextDSDs = new ArrayList<DataSourceDesc>(1);
mNextDSDs.add(dsd);
@@ -1282,7 +1274,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
addTask(new Task(CALL_COMPLETED_SET_BUFFERING_PARAMS, false) {
@Override
void process() {
- Preconditions.checkNotNull(params, "the BufferingParams cannot be null");
+ Preconditions.checkArgument(params != null, "the BufferingParams cannot be null");
_setBufferingParams(params);
}
});
@@ -1346,7 +1338,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
addTask(new Task(CALL_COMPLETED_SET_PLAYBACK_PARAMS, false) {
@Override
void process() {
- Preconditions.checkNotNull(params, "the PlaybackParams cannot be null");
+ Preconditions.checkArgument(params != null, "the PlaybackParams cannot be null");
_setPlaybackParams(params);
}
});
@@ -1379,7 +1371,7 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
addTask(new Task(CALL_COMPLETED_SET_SYNC_PARAMS, false) {
@Override
void process() {
- Preconditions.checkNotNull(params, "the SyncParams cannot be null");
+ Preconditions.checkArgument(params != null, "the SyncParams cannot be null");
_setSyncParams(params);
}
});
@@ -4627,7 +4619,12 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
public void run() {
int status = CALL_STATUS_NO_ERROR;
try {
- process();
+ if (mMediaCallType != CALL_COMPLETED_NOTIFY_WHEN_COMMAND_LABEL_REACHED
+ && getState() == PLAYER_STATE_ERROR) {
+ status = CALL_STATUS_INVALID_OPERATION;
+ } else {
+ process();
+ }
} catch (IllegalStateException e) {
status = CALL_STATUS_INVALID_OPERATION;
} catch (IllegalArgumentException e) {
@@ -4638,6 +4635,8 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
status = CALL_STATUS_ERROR_IO;
} catch (NoDrmSchemeException e) {
status = CALL_STATUS_NO_DRM_SCHEME;
+ } catch (CommandSkippedException e) {
+ status = CALL_STATUS_SKIPPED;
} catch (Exception e) {
status = CALL_STATUS_ERROR_UNKNOWN;
}
@@ -4671,4 +4670,10 @@ public final class MediaPlayer2Impl extends MediaPlayer2 {
}
}
};
+
+ private final class CommandSkippedException extends RuntimeException {
+ public CommandSkippedException(String detailMessage) {
+ super(detailMessage);
+ }
+ };
}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 143182f83ace..ec2d9bab5107 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -888,6 +888,8 @@ public final class TvInputManager {
if (token != null) {
session = new Session(token, channel, mService, mUserId, seq,
mSessionCallbackRecordMap);
+ } else {
+ mSessionCallbackRecordMap.delete(seq);
}
record.postSessionCreated(session);
}
@@ -2487,7 +2489,7 @@ public final class TvInputManager {
}
}
synchronized (mSessionCallbackRecordMap) {
- mSessionCallbackRecordMap.remove(mSeq);
+ mSessionCallbackRecordMap.delete(mSeq);
}
}
diff --git a/media/jni/android_media_Media2DataSource.cpp b/media/jni/android_media_Media2DataSource.cpp
index bc3f6bd80cd8..b3434e9ab8ea 100644
--- a/media/jni/android_media_Media2DataSource.cpp
+++ b/media/jni/android_media_Media2DataSource.cpp
@@ -20,12 +20,12 @@
#include "android_media_Media2DataSource.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include <drm/drm_framework_common.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -56,7 +56,7 @@ JMedia2DataSource::JMedia2DataSource(JNIEnv* env, jobject source)
}
JMedia2DataSource::~JMedia2DataSource() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mMedia2DataSourceObj);
env->DeleteGlobalRef(mByteArrayObj);
}
@@ -75,12 +75,12 @@ ssize_t JMedia2DataSource::readAt(off64_t offset, void *data, size_t size) {
size = kBufferSize;
}
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jint numread = env->CallIntMethod(mMedia2DataSourceObj, mReadAtMethod,
(jlong)offset, mByteArrayObj, (jint)0, (jint)size);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred in readAt()");
- LOGW_EX(env);
+ jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
env->ExceptionClear();
mJavaObjStatus = UNKNOWN_ERROR;
return -1;
@@ -117,11 +117,11 @@ status_t JMedia2DataSource::getSize(off64_t* size) {
return OK;
}
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
*size = env->CallLongMethod(mMedia2DataSourceObj, mGetSizeMethod);
if (env->ExceptionCheck()) {
ALOGW("An exception occurred in getSize()");
- LOGW_EX(env);
+ jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
env->ExceptionClear();
// After returning an error, size shouldn't be used by callers.
*size = UNKNOWN_ERROR;
@@ -142,7 +142,7 @@ status_t JMedia2DataSource::getSize(off64_t* size) {
void JMedia2DataSource::close() {
Mutex::Autolock lock(mLock);
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
env->CallVoidMethod(mMedia2DataSourceObj, mCloseMethod);
// The closed state is effectively the same as an error state.
mJavaObjStatus = UNKNOWN_ERROR;
diff --git a/media/jni/android_media_Media2HTTPConnection.cpp b/media/jni/android_media_Media2HTTPConnection.cpp
index 60176e3d238e..d02ee06dba49 100644
--- a/media/jni/android_media_Media2HTTPConnection.cpp
+++ b/media/jni/android_media_Media2HTTPConnection.cpp
@@ -18,14 +18,14 @@
#define LOG_TAG "Media2HTTPConnection-JNI"
#include <utils/Log.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
#include "android_media_Media2HTTPConnection.h"
#include "android_util_Binder.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
@@ -84,7 +84,7 @@ JMedia2HTTPConnection::JMedia2HTTPConnection(JNIEnv *env, jobject thiz) {
}
JMedia2HTTPConnection::~JMedia2HTTPConnection() {
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mMedia2HTTPConnectionObj);
env->DeleteGlobalRef(mByteArrayObj);
}
@@ -101,7 +101,7 @@ bool JMedia2HTTPConnection::connect(
}
}
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jstring juri = env->NewStringUTF(uri);
jstring jheaders = env->NewStringUTF(tmp.string());
@@ -115,12 +115,12 @@ bool JMedia2HTTPConnection::connect(
}
void JMedia2HTTPConnection::disconnect() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
env->CallVoidMethod(mMedia2HTTPConnectionObj, mDisconnectMethod);
}
ssize_t JMedia2HTTPConnection::readAt(off64_t offset, void *data, size_t size) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
if (size > kBufferSize) {
size = kBufferSize;
@@ -141,12 +141,12 @@ ssize_t JMedia2HTTPConnection::readAt(off64_t offset, void *data, size_t size) {
}
off64_t JMedia2HTTPConnection::getSize() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
return (off64_t)(env->CallLongMethod(mMedia2HTTPConnectionObj, mGetSizeMethod));
}
status_t JMedia2HTTPConnection::getMIMEType(String8 *mimeType) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jstring jmime = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetMIMETypeMethod);
jboolean flag = env->ExceptionCheck();
if (flag) {
@@ -165,7 +165,7 @@ status_t JMedia2HTTPConnection::getMIMEType(String8 *mimeType) {
}
status_t JMedia2HTTPConnection::getUri(String8 *uri) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jstring juri = (jstring)env->CallObjectMethod(mMedia2HTTPConnectionObj, mGetUriMethod);
jboolean flag = env->ExceptionCheck();
if (flag) {
diff --git a/media/jni/android_media_Media2HTTPService.cpp b/media/jni/android_media_Media2HTTPService.cpp
index 382f099b7932..1c63889e2939 100644
--- a/media/jni/android_media_Media2HTTPService.cpp
+++ b/media/jni/android_media_Media2HTTPService.cpp
@@ -21,11 +21,11 @@
#include "android_media_Media2HTTPConnection.h"
#include "android_media_Media2HTTPService.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <media/stagefright/foundation/ADebug.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -46,12 +46,12 @@ JMedia2HTTPService::JMedia2HTTPService(JNIEnv *env, jobject thiz) {
}
JMedia2HTTPService::~JMedia2HTTPService() {
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mMedia2HTTPServiceObj);
}
sp<MediaHTTPConnection> JMedia2HTTPService::makeHTTPConnection() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+ JNIEnv* env = JavaVMHelper::getJNIEnv();
jobject media2HTTPConnectionObj =
env->CallObjectMethod(mMedia2HTTPServiceObj, mMakeHTTPConnectionMethod);
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index abf053408633..426598727c89 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -29,6 +29,7 @@
#include <media/stagefright/Utils.h>
#include <media/stagefright/foundation/ByteUtils.h> // for FOURCC definition
#include <mediaplayer2/JAudioTrack.h>
+#include <mediaplayer2/JavaVMHelper.h>
#include <mediaplayer2/mediaplayer2.h>
#include <stdio.h>
#include <assert.h>
@@ -39,7 +40,7 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include "android/native_window_jni.h"
-#include "android_runtime/Log.h"
+#include "log/log.h"
#include "utils/Errors.h" // for status_t
#include "utils/KeyedVector.h"
#include "utils/String8.h"
@@ -184,14 +185,14 @@ JNIMediaPlayer2Listener::JNIMediaPlayer2Listener(JNIEnv* env, jobject thiz, jobj
JNIMediaPlayer2Listener::~JNIMediaPlayer2Listener()
{
// remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
env->DeleteGlobalRef(mObject);
env->DeleteGlobalRef(mClass);
}
void JNIMediaPlayer2Listener::notify(int64_t srcId, int msg, int ext1, int ext2, const Parcel *obj)
{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
+ JNIEnv *env = JavaVMHelper::getJNIEnv();
if (obj && obj->dataSize() > 0) {
jobject jParcel = createJavaParcelObject(env);
if (jParcel != NULL) {
@@ -207,7 +208,7 @@ void JNIMediaPlayer2Listener::notify(int64_t srcId, int msg, int ext1, int ext2,
}
if (env->ExceptionCheck()) {
ALOGW("An exception occurred while notifying an event.");
- LOGW_EX(env);
+ jniLogException(env, ANDROID_LOG_WARN, LOG_TAG);
env->ExceptionClear();
}
}
@@ -1539,8 +1540,7 @@ static const JNINativeMethod gMethods[] = {
// This function only registers the native methods
static int register_android_media_MediaPlayer2Impl(JNIEnv *env)
{
- return AndroidRuntime::registerNativeMethods(env,
- "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
+ return jniRegisterNativeMethods(env, "android/media/MediaPlayer2Impl", gMethods, NELEM(gMethods));
}
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
@@ -1559,6 +1559,8 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
goto bail;
}
+ JavaVMHelper::setJavaVM(vm);
+
/* success -- return valid version number */
result = JNI_VERSION_1_4;
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index b2be46430831..5ab50925a268 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -43,6 +43,8 @@ import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.InputConfiguration;
import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.location.Location;
import android.location.LocationManager;
@@ -75,6 +77,7 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -694,6 +697,38 @@ public class CameraTestUtils extends Assert {
}
/**
+ * Configure a new camera session with output surfaces and initial session parameters.
+ *
+ * @param camera The CameraDevice to be configured.
+ * @param outputSurfaces The surface list that used for camera output.
+ * @param listener The callback CameraDevice will notify when session is available.
+ * @param handler The handler used to notify callbacks.
+ * @param initialRequest Initial request settings to use as session parameters.
+ */
+ public static CameraCaptureSession configureCameraSessionWithParameters(CameraDevice camera,
+ List<Surface> outputSurfaces, BlockingSessionCallback listener,
+ Handler handler, CaptureRequest initialRequest) throws CameraAccessException {
+ List<OutputConfiguration> outConfigurations = new ArrayList<>(outputSurfaces.size());
+ for (Surface surface : outputSurfaces) {
+ outConfigurations.add(new OutputConfiguration(surface));
+ }
+ SessionConfiguration sessionConfig = new SessionConfiguration(
+ SessionConfiguration.SESSION_REGULAR, outConfigurations,
+ new HandlerExecutor(handler), listener);
+ sessionConfig.setSessionParameters(initialRequest);
+ camera.createCaptureSession(sessionConfig);
+
+ CameraCaptureSession session = listener.waitAndGetSession(SESSION_CONFIGURE_TIMEOUT_MS);
+ assertFalse("Camera session should not be a reprocessable session",
+ session.isReprocessable());
+ assertFalse("Capture session type must be regular",
+ CameraConstrainedHighSpeedCaptureSession.class.isAssignableFrom(
+ session.getClass()));
+
+ return session;
+ }
+
+ /**
* Configure a new camera session with output surfaces and type.
*
* @param camera The CameraDevice to be configured.
@@ -1334,6 +1369,20 @@ public class CameraTestUtils extends Assert {
}
}
+ public static class HandlerExecutor implements Executor {
+ private final Handler mHandler;
+
+ public HandlerExecutor(Handler handler) {
+ assertNotNull("handler must be valid", handler);
+ mHandler = handler;
+ }
+
+ @Override
+ public void execute(Runnable runCmd) {
+ mHandler.post(runCmd);
+ }
+ }
+
/**
* Provide a mock for {@link CameraDevice.StateCallback}.
*
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
index ddb05f017cc8..8f27fefca0d0 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/Camera2RecordingTest.java
@@ -58,6 +58,7 @@ import static com.android.mediaframeworktest.helpers.CameraTestUtils.SIZE_BOUND_
import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleCaptureCallback;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.SimpleImageReaderListener;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSession;
+import static com.android.mediaframeworktest.helpers.CameraTestUtils.configureCameraSessionWithParameters;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.getSupportedVideoSizes;
import static com.android.mediaframeworktest.helpers.CameraTestUtils.getValueNotNull;
@@ -872,7 +873,6 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
outputSurfaces.add(mReaderSurface);
}
mSessionListener = new BlockingSessionCallback();
- mSession = configureCameraSession(mCamera, outputSurfaces, mSessionListener, mHandler);
CaptureRequest.Builder recordingRequestBuilder =
mCamera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
@@ -885,7 +885,10 @@ public class Camera2RecordingTest extends Camera2SurfaceViewTestCase {
}
recordingRequestBuilder.addTarget(mRecordingSurface);
recordingRequestBuilder.addTarget(mPreviewSurface);
- mSession.setRepeatingRequest(recordingRequestBuilder.build(), listener, mHandler);
+ CaptureRequest recordingRequest = recordingRequestBuilder.build();
+ mSession = configureCameraSessionWithParameters(mCamera, outputSurfaces, mSessionListener,
+ mHandler, recordingRequest);
+ mSession.setRepeatingRequest(recordingRequest, listener, mHandler);
if (useMediaRecorder) {
mMediaRecorder.start();
diff --git a/packages/CarSystemUI/Android.mk b/packages/CarSystemUI/Android.mk
new file mode 100644
index 000000000000..0d40b7f22c5b
--- /dev/null
+++ b/packages/CarSystemUI/Android.mk
@@ -0,0 +1,86 @@
+# LOCAL_PATH is not the current directory of this file.
+# If you need the local path, use CAR_SYSUI_PATH.
+# This tweak ensures that the resource overlay that is device-specific still works
+# which requires that LOCAL_PATH match the original path (which must be frameworks/base/packages/SystemUI).
+#
+# For clarity, we also define SYSTEM_UI_AOSP_PATH to frameworks/base/packages/SystemUI and refer to that
+SYSTEM_UI_AOSP_PATH := frameworks/base/packages/SystemUI
+SYSTEM_UI_CAR_PATH := frameworks/base/packages/CarSystemUI
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_USE_AAPT2 := true
+
+LOCAL_MODULE_TAGS := optional
+
+# The same as SYSTEM_UI_AOSP_PATH but based on the value of LOCAL_PATH which is
+# frameworks/base/packages/CarSystemUI.
+RELATIVE_SYSTEM_UI_AOSP_PATH := ../../../../$(SYSTEM_UI_AOSP_PATH)
+
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, src) \
+ $(call all-java-files-under, $(RELATIVE_SYSTEM_UI_AOSP_PATH)/src) \
+ $(call all-Iaidl-files-under, $(RELATIVE_SYSTEM_UI_AOSP_PATH)/src)
+
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ SystemUIPluginLib \
+ SystemUISharedLib \
+ androidx.car_car \
+ androidx.legacy_legacy-support-v4 \
+ androidx.recyclerview_recyclerview \
+ androidx.preference_preference \
+ androidx.appcompat_appcompat \
+ androidx.mediarouter_mediarouter \
+ androidx.palette_palette \
+ androidx.legacy_legacy-preference-v14 \
+ androidx.leanback_leanback \
+ androidx.slice_slice-core \
+ androidx.slice_slice-view \
+ androidx.slice_slice-builders \
+ androidx.arch.core_core-runtime \
+ androidx.lifecycle_lifecycle-extensions \
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ SystemUI-tags \
+ SystemUI-proto
+
+LOCAL_JAVA_LIBRARIES := telephony-common \
+ android.car
+
+LOCAL_FULL_LIBS_MANIFEST_FILES := $(SYSTEM_UI_AOSP_PATH)/AndroidManifest.xml
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_MODULE_OWNER := google
+LOCAL_PACKAGE_NAME := CarSystemUI
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_PROGUARD_FLAG_FILES := $(RELATIVE_SYSTEM_UI_AOSP_PATH)/proguard.flags \
+ proguard.flags
+
+LOCAL_RESOURCE_DIR := \
+ $(LOCAL_PATH)/res \
+ $(SYSTEM_UI_AOSP_PATH)/res-keyguard \
+ $(SYSTEM_UI_AOSP_PATH)/res
+
+ifneq ($(INCREMENTAL_BUILDS),)
+ LOCAL_PROGUARD_ENABLED := disabled
+ LOCAL_JACK_ENABLED := incremental
+endif
+
+LOCAL_DX_FLAGS := --multi-dex
+LOCAL_JACK_FLAGS := --multi-dex native
+
+include frameworks/base/packages/SettingsLib/common.mk
+
+LOCAL_OVERRIDES_PACKAGES := SystemUI
+
+LOCAL_AAPT_FLAGS := --extra-packages com.android.keyguard
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under, $(SYSTEM_UI_CAR_PATH))
diff --git a/packages/CarSystemUI/AndroidManifest.xml b/packages/CarSystemUI/AndroidManifest.xml
new file mode 100644
index 000000000000..4e8a3a3885a7
--- /dev/null
+++ b/packages/CarSystemUI/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ package="com.android.systemui"
+ android:sharedUserId="android.uid.systemui"
+ coreApp="true">
+
+
+</manifest>
diff --git a/packages/CarSystemUI/proguard.flags b/packages/CarSystemUI/proguard.flags
new file mode 100644
index 000000000000..ceb037cdb70f
--- /dev/null
+++ b/packages/CarSystemUI/proguard.flags
@@ -0,0 +1 @@
+-keep class com.android.systemui.CarSystemUIFactory
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps.xml b/packages/CarSystemUI/res/drawable/car_ic_apps.xml
new file mode 100644
index 000000000000..13b543b2ee60
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="33dp"
+ android:height="33dp"
+ android:viewportHeight="33.0"
+ android:viewportWidth="33.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M1,20L12,20C12.55,20 13,20.45 13,21L13,32C13,32.55 12.55,33 12,33L1,33C0.45,33 0,32.55 0,32L0,21C0,20.45 0.45,20 1,20ZM21,20L32,20C32.55,20 33,20.45 33,21L33,32C33,32.55 32.55,33 32,33L21,33C20.45,33 20,32.55 20,32L20,21C20,20.45 20.45,20 21,20ZM1,0L12,0C12.55,0 13,0.45 13,1L13,12C13,12.55 12.55,13 12,13L1,13C0.45,13 0,12.55 0,12L0,1C0,0.45 0.45,0 1,0ZM21,0L32,0C32.55,0 33,0.45 33,1L33,12C33,12.55 32.55,13 32,13L21,13C20.45,13 20,12.55 20,12L20,1C20,0.45 20.45,0 21,0Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml b/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml
new file mode 100644
index 000000000000..498d36621766
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="33dp"
+ android:height="33dp"
+ android:viewportHeight="33.0"
+ android:viewportWidth="33.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M1,20L12,20C12.55,20 13,20.45 13,21L13,32C13,32.55 12.55,33 12,33L1,33C0.45,33 0,32.55 0,32L0,21C0,20.45 0.45,20 1,20ZM21,20L32,20C32.55,20 33,20.45 33,21L33,32C33,32.55 32.55,33 32,33L21,33C20.45,33 20,32.55 20,32L20,21C20,20.45 20.45,20 21,20ZM1,0L12,0C12.55,0 13,0.45 13,1L13,12C13,12.55 12.55,13 12,13L1,13C0.45,13 0,12.55 0,12L0,1C0,0.45 0.45,0 1,0ZM21,0L32,0C32.55,0 33,0.45 33,1L33,12C33,12.55 32.55,13 32,13L21,13C20.45,13 20,12.55 20,12L20,1C20,0.45 20.45,0 21,0Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml
new file mode 100644
index 000000000000..250bfe8668fb
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_apps_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_apps_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music.xml b/packages/CarSystemUI/res/drawable/car_ic_music.xml
new file mode 100644
index 000000000000..8c1549470a0e
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music.xml
@@ -0,0 +1,34 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="39dp"
+ android:viewportHeight="39.0"
+ android:viewportWidth="34.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M23,25L31,23L31,9L12,12L12,38C12,38.45 11.45,39 11,39L1,39C0.55,39 0.01,38.87 0,38.3C0,38.3 0,35.87 0,31C0,30.42 0.62,30.08 1,30L9,28L9,4.85C9,4.36 9.36,3.94 9.84,3.87L32.84,0.19C33.39,0.1 33.9,0.47 33.99,1.01C34,1.07 34,1.12 34,1.17L34,33.06C34,33.51 33.45,34 33,34L23,34C22.55,34 22,33.51 22,33.06L22,26C22,25.61 22.16,25.18 23,25Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M23.57,26.48L23.57,32.42L32.42,32.42L32.42,24.27L23.57,26.48ZM32.42,7.18L32.42,1.85L10.57,5.34L10.57,10.63L32.42,7.18ZM10.42,29.27L9.38,29.53L1.58,31.48C1.58,34.35 1.58,34.45 1.57,36.48C1.58,36.89 1.58,37.2 1.58,37.43L10.42,37.43L10.42,29.27Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="3.15"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music_black.xml b/packages/CarSystemUI/res/drawable/car_ic_music_black.xml
new file mode 100644
index 000000000000..64287a549314
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music_black.xml
@@ -0,0 +1,34 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="34dp"
+ android:height="39dp"
+ android:viewportHeight="39.0"
+ android:viewportWidth="34.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M23,25L31,23L31,9L12,12L12,38C12,38.45 11.45,39 11,39L1,39C0.55,39 0.01,38.87 0,38.3C0,38.3 0,35.87 0,31C0,30.42 0.62,30.08 1,30L9,28L9,4.85C9,4.36 9.36,3.94 9.84,3.87L32.84,0.19C33.39,0.1 33.9,0.47 33.99,1.01C34,1.07 34,1.12 34,1.17L34,33.06C34,33.51 33.45,34 33,34L23,34C22.55,34 22,33.51 22,33.06L22,26C22,25.61 22.16,25.18 23,25Z"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="1"/>
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M23.57,26.48L23.57,32.42L32.42,32.42L32.42,24.27L23.57,26.48ZM32.42,7.18L32.42,1.85L10.57,5.34L10.57,10.63L32.42,7.18ZM10.42,29.27L9.38,29.53L1.58,31.48C1.58,34.35 1.58,34.45 1.57,36.48C1.58,36.89 1.58,37.2 1.58,37.43L10.42,37.43L10.42,29.27Z"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="3.15"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml
new file mode 100644
index 000000000000..9703a8c57174
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_music_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_music_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation.xml
new file mode 100644
index 000000000000..58667beb98d9
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="37dp"
+ android:viewportHeight="37.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml
new file mode 100644
index 000000000000..145d4c912ff0
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="37dp"
+ android:viewportHeight="37.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml
new file mode 100644
index 000000000000..f0174db25312
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_navigation_selected.xml
@@ -0,0 +1,21 @@
+<?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
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_navigation_black" android:gravity="center"/>
+</layer-list> \ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification.xml b/packages/CarSystemUI/res/drawable/car_ic_notification.xml
new file mode 100644
index 000000000000..f96b05038edb
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="38dp"
+ android:viewportHeight="38.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M20.62,3.98C25.51,5.85 28.98,10.61 28.98,16.18L28.98,24.95L32,24.95L32,31.22L0,31.22L0,24.95L3.02,24.95L3.02,16.18C3.02,10.61 6.49,5.85 11.38,3.98C11.72,1.73 13.66,0 16,0C18.34,0 20.28,1.73 20.62,3.98ZM11.33,33.3L20.67,33.3C20.67,35.9 18.58,38 16,38C13.42,38 11.33,35.9 11.33,33.3Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml
new file mode 100644
index 000000000000..5f8df8829b40
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32dp"
+ android:height="38dp"
+ android:viewportHeight="38.0"
+ android:viewportWidth="32.0">
+ <path
+ android:fillColor="#000000"
+ android:fillType="evenOdd"
+ android:pathData="M20.62,3.98C25.51,5.85 28.98,10.61 28.98,16.18L28.98,24.95L32,24.95L32,31.22L0,31.22L0,24.95L3.02,24.95L3.02,16.18C3.02,10.61 6.49,5.85 11.38,3.98C11.72,1.73 13.66,0 16,0C18.34,0 20.28,1.73 20.62,3.98ZM11.33,33.3L20.67,33.3C20.67,35.9 18.58,38 16,38C13.42,38 11.33,35.9 11.33,33.3Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
new file mode 100644
index 000000000000..02eec9351208
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
@@ -0,0 +1,21 @@
+<?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
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_notification_black" android:gravity="center"/>
+</layer-list> \ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview.xml b/packages/CarSystemUI/res/drawable/car_ic_overview.xml
new file mode 100644
index 000000000000..590109a0735e
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="39dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="39.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="evenOdd"
+ android:pathData="M33.4,22.74L33.4,37.74C33.4,38.84 32.51,39.74 31.4,39.74L24.4,39.74L24.4,24.74L14.4,24.74L14.4,39.74L7.4,39.74C6.3,39.74 5.4,38.84 5.4,37.74L5.4,22.74L0.5,22.74C0.22,22.74 0,22.51 0,22.24C0,22.12 0.04,22 0.12,21.91L19.03,0.17C19.21,-0.04 19.52,-0.06 19.73,0.12C19.75,0.14 19.76,0.15 19.78,0.17L38.68,21.91C38.86,22.12 38.84,22.43 38.63,22.62C38.54,22.69 38.43,22.74 38.31,22.74L33.4,22.74Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml b/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml
new file mode 100644
index 000000000000..48cff978ec79
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="39dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="39.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="evenOdd"
+ android:pathData="M33.4,22.74L33.4,37.74C33.4,38.84 32.51,39.74 31.4,39.74L24.4,39.74L24.4,24.74L14.4,24.74L14.4,39.74L7.4,39.74C6.3,39.74 5.4,38.84 5.4,37.74L5.4,22.74L0.5,22.74C0.22,22.74 0,22.51 0,22.24C0,22.12 0.04,22 0.12,21.91L19.03,0.17C19.21,-0.04 19.52,-0.06 19.73,0.12C19.75,0.14 19.76,0.15 19.78,0.17L38.68,21.91C38.86,22.12 38.84,22.43 38.63,22.62C38.54,22.69 38.43,22.74 38.31,22.74L33.4,22.74Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml
new file mode 100644
index 000000000000..18ebf0c6a4f8
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_overview_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_overview_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone.xml b/packages/CarSystemUI/res/drawable/car_ic_phone.xml
new file mode 100644
index 000000000000..70916703f882
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="40.0">
+ <path
+ android:fillColor="@color/car_nav_icon_fill_color"
+ android:fillType="nonZero"
+ android:pathData="M21.31,35.75L21.3,35.76L3.46,17.92L0.03,6.25C-0.16,5.32 0.53,3.88 1.41,3.51C6.36,1.25 8.88,0.1 8.95,0.08C10.02,-0.23 11.39,0.39 11.7,1.45L13.76,10.37C13.97,11.1 13.62,11.91 13.07,12.43L8.24,17.25L22.31,31.32L27.13,26.49C27.65,25.94 28.47,25.6 29.19,25.81L38.11,27.87C39.18,28.17 39.79,29.55 39.49,30.61C39.46,30.69 38.32,33.2 36.05,38.16C35.68,39.04 34.25,39.73 33.31,39.53L21.65,36.1C21.51,35.96 21.4,35.84 21.31,35.75Z"
+ android:strokeColor="@color/car_nav_icon_fill_color"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml b/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml
new file mode 100644
index 000000000000..087eebb2bd0a
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone_black.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="40dp"
+ android:height="40dp"
+ android:viewportHeight="40.0"
+ android:viewportWidth="40.0">
+ <path
+ android:fillColor="#FF000000"
+ android:fillType="nonZero"
+ android:pathData="M21.31,35.75L21.3,35.76L3.46,17.92L0.03,6.25C-0.16,5.32 0.53,3.88 1.41,3.51C6.36,1.25 8.88,0.1 8.95,0.08C10.02,-0.23 11.39,0.39 11.7,1.45L13.76,10.37C13.97,11.1 13.62,11.91 13.07,12.43L8.24,17.25L22.31,31.32L27.13,26.49C27.65,25.94 28.47,25.6 29.19,25.81L38.11,27.87C39.18,28.17 39.79,29.55 39.49,30.61C39.46,30.69 38.32,33.2 36.05,38.16C35.68,39.04 34.25,39.73 33.31,39.53L21.65,36.1C21.51,35.96 21.4,35.84 21.31,35.75Z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml
new file mode 100644
index 000000000000..197eac0204e5
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_phone_selected.xml
@@ -0,0 +1,20 @@
+<?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
+ -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/car_ic_selection_bg" android:gravity="center"/>
+ <item android:drawable="@drawable/car_ic_phone_black" android:gravity="center"/>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml b/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml
new file mode 100644
index 000000000000..781beaf96264
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_ic_selection_bg.xml
@@ -0,0 +1,28 @@
+<?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
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="70dp"
+ android:height="70dp"
+ android:viewportHeight="70.0"
+ android:viewportWidth="70.0">
+ <path
+ android:fillColor="@color/car_accent"
+ android:fillType="evenOdd"
+ android:pathData="M4,0L66,0A4,4 0,0 1,70 4L70,66A4,4 0,0 1,66 70L4,70A4,4 0,0 1,0 66L0,4A4,4 0,0 1,4 0z"
+ android:strokeColor="#00000000"
+ android:strokeWidth="1"/>
+</vector>
diff --git a/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml b/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml
new file mode 100644
index 000000000000..1a9b8a521d78
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/car_seekbar_thumb.xml
@@ -0,0 +1,37 @@
+<?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
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape android:shape="oval">
+ <padding
+ android:bottom="@dimen/car_padding_1"
+ android:left="@dimen/car_padding_1"
+ android:right="@dimen/car_padding_1"
+ android:top="@dimen/car_padding_1"/>
+ <solid android:color="@android:color/black"/>
+ </shape>
+ </item>
+ <item>
+ <shape android:shape="oval">
+ <solid android:color="@color/car_accent"/>
+ <size
+ android:width="@dimen/car_seekbar_thumb_size"
+ android:height="@dimen/car_seekbar_thumb_size"/>
+ </shape>
+ </item>
+</layer-list>
diff --git a/packages/CarSystemUI/res/drawable/nav_button_background.xml b/packages/CarSystemUI/res/drawable/nav_button_background.xml
new file mode 100644
index 000000000000..376347cdf4a9
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/nav_button_background.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/nav_bar_ripple_background_color">
+ <item android:id="@android:id/mask">
+ <shape android:shape="rectangle">
+ <solid android:color="?android:colorAccent"/>
+ <corners android:radius="6dp"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/notification_material_bg.xml b/packages/CarSystemUI/res/drawable/notification_material_bg.xml
new file mode 100644
index 000000000000..03746c8bfe4d
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/notification_material_bg.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/notification_ripple_untinted_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/notification_material_background_color"/>
+ <corners
+ android:radius="@dimen/notification_shadow_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml b/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml
new file mode 100644
index 000000000000..03746c8bfe4d
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/notification_material_bg_dim.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="@color/notification_ripple_untinted_color">
+ <item>
+ <shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/notification_material_background_color"/>
+ <corners
+ android:radius="@dimen/notification_shadow_radius"/>
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/CarSystemUI/res/drawable/volume_dialog_background.xml b/packages/CarSystemUI/res/drawable/volume_dialog_background.xml
new file mode 100644
index 000000000000..fa3ca8f27fc9
--- /dev/null
+++ b/packages/CarSystemUI/res/drawable/volume_dialog_background.xml
@@ -0,0 +1,26 @@
+<?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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="?android:attr/colorBackgroundFloating"/>
+ <padding
+ android:bottom="5dp"
+ android:left="5dp"
+ android:right="5dp"
+ android:top="5dp"/>
+ <corners android:bottomLeftRadius="20dp"
+ android:bottomRightRadius="20dp"/>
+</shape>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
new file mode 100644
index 000000000000..c7dfa9b9934c
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -0,0 +1,129 @@
+<?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
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/home"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:longIntent="intent:#Intent;action=com.google.android.demandspace.START;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/maps_nav"
+ style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MAPS"
+ systemui:icon="@drawable/car_ic_navigation"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.CarLauncher;category=android.intent.category.APP_MAPS;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_navigation_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/music_nav"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_music"
+ systemui:intent="intent:#Intent;component=com.android.car.media/.MediaActivity;launchFlags=0x14000000;end"
+ systemui:packages="com.android.car.media"
+ systemui:selectedIcon="@drawable/car_ic_music_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/phone_nav"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.dialer/.TelecomActivity"
+ systemui:icon="@drawable/car_ic_phone"
+ systemui:intent="intent:#Intent;component=com.android.car.dialer/.TelecomActivity;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_phone_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.systemui.statusbar.car.CarFacetButton
+ android:id="@+id/grid_nav"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_apps_selected"
+ systemui:useMoreIcon="false"
+ />
+
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"/>
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/notifications"
+ style="@style/NavigationBarButton"
+ android:background="?android:attr/selectableItemBackground"
+ android:src="@drawable/car_ic_notification"
+ />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/lock_screen_nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="@dimen/car_keyline_1"
+ android:paddingEnd="@dimen/car_keyline_1"
+ android:gravity="center"
+ android:visibility="gone">
+ </LinearLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
new file mode 100644
index 000000000000..46e60db0ba4b
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
@@ -0,0 +1,44 @@
+<?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
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="@dimen/car_padding_5"
+ android:paddingEnd="@dimen/car_padding_5">
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/home"
+ android:layout_width="@dimen/car_touch_target_size"
+ android:layout_height="match_parent"
+ android:background="?android:attr/selectableItemBackground"
+ android:src="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ />
+ </LinearLayout>
+</com.android.systemui.statusbar.car.CarNavigationBarView>
+
diff --git a/packages/CarSystemUI/res/layout/car_status_bar_header.xml b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
new file mode 100644
index 000000000000..e4460721890c
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
@@ -0,0 +1,29 @@
+<?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
+ -->
+<!-- Extends LinearLayout -->
+<com.android.systemui.qs.car.CarStatusBarHeader
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/status_bar_height">
+
+ <include layout="@layout/car_top_navigation_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ />
+</com.android.systemui.qs.car.CarStatusBarHeader>
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
new file mode 100644
index 000000000000..55d50a0545fe
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -0,0 +1,151 @@
+<?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
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <FrameLayout
+ android:id="@+id/left_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacleft"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/lefttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/car_padding_4"
+ android:paddingEnd="16dp"
+ android:gravity="center_vertical|start"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.Car.Status"
+ systemui:hvacAreaId="1"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/clock_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerInParent="true"
+ >
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/qs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:intent="intent:#Intent;component=com.android.car.settings/.common.CarSettingActivity;launchFlags=0x14008000;end"
+ />
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ />
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/system_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@+id/clock_container"
+ android:paddingStart="@dimen/car_padding_1"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ >
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:gravity="center_vertical"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/right_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacright"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/righttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="@dimen/car_padding_4"
+ android:gravity="center_vertical|end"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.Car.Status"
+ systemui:hvacAreaId="4"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+ </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_volume_dialog.xml b/packages/CarSystemUI/res/layout/car_volume_dialog.xml
new file mode 100644
index 000000000000..c98740e42701
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_volume_dialog.xml
@@ -0,0 +1,30 @@
+<?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
+ -->
+<androidx.car.widget.PagedListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/volume_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@android:color/black"
+ android:minWidth="@dimen/volume_dialog_panel_width"
+ android:theme="@style/Theme.Car.DialogListView"
+ app:dividerEndMargin="@dimen/car_keyline_1"
+ app:dividerStartMargin="@dimen/car_keyline_1"
+ app:gutter="none"
+ app:scrollBarEnabled="false"
+ app:showPagedListViewDivider="true"/>
diff --git a/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml b/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml
new file mode 100644
index 000000000000..2793e562a574
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/status_bar_wifi_group.xml
@@ -0,0 +1,85 @@
+<?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
+ -->
+<com.android.systemui.statusbar.StatusBarWifiView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/wifi_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical">
+
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/wifi_group"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="2dp"
+ android:gravity="center_vertical"
+ >
+ <FrameLayout
+ android:id="@+id/inout_container"
+ android:layout_width="wrap_content"
+ android:layout_height="17dp"
+ android:gravity="center_vertical">
+ <ImageView
+ android:id="@+id/wifi_in"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="2dp"
+ android:src="@drawable/ic_activity_down"
+ android:visibility="gone"
+ />
+ <ImageView
+ android:id="@+id/wifi_out"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="2dp"
+ android:src="@drawable/ic_activity_up"
+ android:visibility="gone"
+ />
+ </FrameLayout>
+ <FrameLayout
+ android:id="@+id/wifi_combo"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical">
+ <com.android.systemui.statusbar.AlphaOptimizedImageView
+ android:id="@+id/wifi_signal"
+ android:layout_width="@dimen/status_bar_icon_size"
+ android:layout_height="@dimen/status_bar_icon_size"
+ android:theme="?attr/lightIconTheme"/>
+ </FrameLayout>
+
+ <View
+ android:id="@+id/wifi_signal_spacer"
+ android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
+ android:layout_height="4dp"
+ android:visibility="gone"/>
+
+ <ViewStub
+ android:id="@+id/connected_device_signals_stub"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout="@layout/connected_device_signal"/>
+
+ <View
+ android:id="@+id/wifi_airplane_spacer"
+ android:layout_width="@dimen/status_bar_airplane_spacer_width"
+ android:layout_height="4dp"
+ android:visibility="gone"
+ />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+</com.android.systemui.statusbar.StatusBarWifiView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
new file mode 100644
index 000000000000..0594dce2ba22
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -0,0 +1,91 @@
+<?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
+ -->
+
+<!-- This is the combined status bar / notification panel window. -->
+<com.android.systemui.statusbar.phone.StatusBarWindowView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:sysui="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <com.android.systemui.statusbar.BackDropView
+ android:id="@+id/backdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ sysui:ignoreRightInset="true"
+ >
+ <ImageView android:id="@+id/backdrop_back"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"/>
+ <ImageView android:id="@+id/backdrop_front"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
+ android:visibility="invisible"/>
+ </com.android.systemui.statusbar.BackDropView>
+
+ <com.android.systemui.statusbar.ScrimView
+ android:id="@+id/scrim_behind"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ sysui:ignoreRightInset="true"
+ />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/status_bar_height"
+ android:orientation="vertical"
+ >
+ <FrameLayout
+ android:id="@+id/status_bar_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ />
+
+ <include layout="@layout/car_top_navigation_bar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ />
+ </LinearLayout>
+
+ <include layout="@layout/brightness_mirror"/>
+
+ <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/car_fullscreen_user_switcher"/>
+
+ <include layout="@layout/status_bar_expanded"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"/>
+
+ <com.android.systemui.statusbar.ScrimView
+ android:id="@+id/scrim_in_front"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:importantForAccessibility="no"
+ sysui:ignoreRightInset="true"
+ />
+
+</com.android.systemui.statusbar.phone.StatusBarWindowView>
diff --git a/packages/CarSystemUI/res/layout/system_icons.xml b/packages/CarSystemUI/res/layout/system_icons.xml
new file mode 100644
index 000000000000..a7dd65eab550
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/system_icons.xml
@@ -0,0 +1,40 @@
+<?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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/system_icons"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical">
+
+ <com.android.systemui.statusbar.phone.StatusIconContainer
+ android:id="@+id/statusIcons"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:paddingEnd="4dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ />
+
+ <com.android.systemui.BatteryMeterView
+ android:id="@+id/battery"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ />
+</LinearLayout> \ No newline at end of file
diff --git a/packages/CarSystemUI/res/values-night/colors.xml b/packages/CarSystemUI/res/values-night/colors.xml
new file mode 100644
index 000000000000..dad94a894603
--- /dev/null
+++ b/packages/CarSystemUI/res/values-night/colors.xml
@@ -0,0 +1,24 @@
+<?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
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="car_accent">#356FE5</color>
+ <color name="status_bar_background_color">#ff000000</color>
+ <color name="system_bar_background_opaque">#ff0c1013</color>
+
+ <!-- The color of the ripples on the untinted notifications -->
+ <color name="notification_ripple_untinted_color">@color/ripple_material_dark</color>
+</resources>
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
new file mode 100644
index 000000000000..617873865289
--- /dev/null
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 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.
+ -->
+
+<resources>
+
+ <!-- Custom attributes to configure hvac values -->
+ <declare-styleable name="AnimatedTemperatureView">
+ <attr name="hvacAreaId" format="integer"/>
+ <attr name="hvacPropertyId" format="integer"/>
+ <attr name="hvacTempFormat" format="string"/>
+ <!-- how far away the animations should center around -->
+ <attr name="hvacPivotOffset" format="dimension"/>
+ <attr name="hvacMinValue" format="float"/>
+ <attr name="hvacMaxValue" format="float"/>
+ <attr name="hvacMinText" format="string|reference"/>
+ <attr name="hvacMaxText" format="string|reference"/>
+ <attr name="android:gravity"/>
+ <attr name="android:minEms"/>
+ <attr name="android:textAppearance"/>
+ </declare-styleable>
+</resources>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
new file mode 100644
index 000000000000..e9ddbaf42b8c
--- /dev/null
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -0,0 +1,54 @@
+<?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
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <color name="nav_bar_ripple_background_color">#40ffffff</color>
+ <color name="car_accent">#356FE5</color>
+ <!-- colors for user switcher -->
+ <color name="car_user_switcher_background_color">#000000</color>
+ <color name="car_user_switcher_name_text_color">@color/car_title2_light</color>
+ <color name="car_user_switcher_add_user_background_color">#131313</color>
+ <color name="car_nav_icon_fill_color">@color/car_grey_50</color>
+ <!-- colors for seekbar -->
+ <color name="car_seekbar_track_background">#131315</color>
+ <color name="car_seekbar_track_secondary_progress">@color/car_accent</color>
+ <!-- colors for volume dialog tint -->
+ <color name="car_volume_dialog_tint">@color/car_tint_light</color>
+
+ <!-- System ui can't depend on car libs so redefine. -->
+ <color name="car_grey_50">#fffafafa</color>
+
+ <color name="docked_divider_background">@color/car_grey_50</color>
+ <color name="system_bar_background_opaque">#ff172026</color>
+
+ <color name="status_bar_background_color">#33000000</color>
+ <drawable name="system_bar_background">@color/status_bar_background_color</drawable>
+
+ <!-- The scrim color for the background of the notifications shade. -->
+ <color name="scrim_behind_color">#172026</color>
+
+ <!-- The color of the dividing line between grouped notifications. -->
+ <color name="notification_divider_color">@*android:color/notification_action_list</color>
+
+ <!-- The color of the ripples on the untinted notifications -->
+ <color name="notification_ripple_untinted_color">@color/ripple_material_light</color>
+
+ <color name="car_teal_700">#ff00796b</color>
+ <color name="car_grey_300">#ffe0e0e0</color>
+ <color name="car_grey_900">#ff212121</color>
+
+ <color name="keyguard_button_text_color">@android:color/black</color>
+</resources>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
new file mode 100644
index 000000000000..452d61df5322
--- /dev/null
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -0,0 +1,31 @@
+<?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
+ -->
+<resources>
+ <string name="config_statusBarComponent" translatable="false">
+ com.android.systemui.statusbar.car.CarStatusBar
+ </string>
+ <string name="config_systemUIFactoryComponent" translatable="false">
+ com.android.systemui.CarSystemUIFactory
+ </string>
+ <bool name="config_enableFullscreenUserSwitcher">true</bool>
+
+ <!-- configure which system ui bars should be displayed -->
+ <bool name="config_enableLeftNavigationBar">false</bool>
+ <bool name="config_enableRightNavigationBar">false</bool>
+ <bool name="config_enableBottomNavigationBar">true</bool>
+
+</resources>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
new file mode 100644
index 000000000000..3829aa3685b6
--- /dev/null
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -0,0 +1,62 @@
+<?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
+ -->
+<resources>
+ <!--
+ Note: status bar height and navigation bar heights are defined
+ in frameworks/base/core package and thus will have no effect if
+ set here. See car_product overlay for car specific defaults-->
+
+ <dimen name="status_bar_icon_drawing_size_dark">36dp</dimen>
+ <dimen name="status_bar_icon_drawing_size">36dp</dimen>
+ <dimen name="car_qs_header_system_icons_area_height">96dp</dimen>
+ <!-- The amount by which to scale up the status bar icons. -->
+ <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.75</item>
+
+ <dimen name="car_primary_icon_size">36dp</dimen>
+
+ <!-- dimensions for the car user switcher -->
+ <dimen name="car_user_switcher_name_text_size">@dimen/car_title2_size</dimen>
+ <dimen name="car_user_switcher_vertical_spacing_between_users">124dp</dimen>
+
+ <!--These values represent MIN and MAX for hvac-->
+ <item name="hvac_min_value" format="float" type="dimen">0</item>
+ <item name="hvac_max_value" format="float" type="dimen">126</item>
+
+ <!-- Largest size an avatar might need to be drawn in the user picker, status bar, or
+ quick settings header -->
+ <dimen name="max_avatar_size">128dp</dimen>
+
+ <!-- Standard image button size for volume dialog buttons -->
+ <dimen name="volume_button_size">84dp</dimen>
+ <!-- The maximum width allowed for the volume dialog. For auto, we allow this to span a good
+ deal of the screen. This value accounts for the side margins. -->
+ <dimen name="volume_dialog_panel_width">1920dp</dimen>
+ <dimen name="volume_dialog_side_margin">@dimen/side_margin</dimen>
+
+ <dimen name="volume_dialog_elevation">6dp</dimen>
+
+ <dimen name="volume_dialog_row_margin_end">@dimen/car_keyline_3</dimen>
+
+ <dimen name="volume_dialog_row_padding_end">0dp</dimen>
+
+ <dimen name="line_item_height">128dp</dimen>
+ <dimen name="volume_icon_size">96dp</dimen>
+ <dimen name="side_margin">148dp</dimen>
+ <dimen name="car_keyline_1">24dp</dimen>
+ <dimen name="car_keyline_2">96dp</dimen>
+ <dimen name="car_keyline_3">128dp</dimen>
+</resources>
diff --git a/packages/CarSystemUI/res/values/integers.xml b/packages/CarSystemUI/res/values/integers.xml
new file mode 100644
index 000000000000..8b87c740425f
--- /dev/null
+++ b/packages/CarSystemUI/res/values/integers.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+ <integer name="user_fullscreen_switcher_num_col">2</integer>
+</resources>
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
new file mode 100644
index 000000000000..0368e61978fd
--- /dev/null
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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.
+ -->
+
+<resources>
+ <!-- String to represent lowest setting of an HVAC system [CHAR LIMIT=5]-->
+ <string name="hvac_min_text">Min</string>
+ <!-- String to represent largest setting of an HVAC system [CHAR LIMIT=5]-->
+ <string name="hvac_max_text">Max</string>
+</resources>
diff --git a/packages/CarSystemUI/res/values/styles.xml b/packages/CarSystemUI/res/values/styles.xml
new file mode 100644
index 000000000000..22a699c3c776
--- /dev/null
+++ b/packages/CarSystemUI/res/values/styles.xml
@@ -0,0 +1,54 @@
+<?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
+ -->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- The style for the volume icons in the volume dialog. This style makes the icon scale to
+ fit its container since auto wants the icon to be larger. The padding is added to make it
+ so the icon does not press along the edges of the dialog. -->
+ <style name="VolumeButtons" parent="@android:style/Widget.Material.Button.Borderless">
+ <item name="android:background">@drawable/btn_borderless_rect</item>
+ <item name="android:scaleType">fitCenter</item>
+ <item name="android:padding">22dp</item>
+ </style>
+
+ <style name="TextAppearance.StatusBar.Clock"
+ parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <item name="android:textSize">42sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ <item name="android:textColor">@color/car_grey_50</item>
+ </style>
+
+ <style name="TextAppearance.Car.Status">
+ <item name="android:textSize">@dimen/car_body2_size</item>
+ <item name="android:textColor">@color/car_grey_50</item>
+ </style>
+
+ <style name="CarNavigationBarButtonTheme">
+ <item name="android:colorControlHighlight">@color/nav_bar_ripple_background_color</item>
+ </style>
+
+ <style name="Theme.Car.DialogListView" parent="@style/Theme.Car.NoActionBar">
+ <item name="listItemBackgroundColor">@android:color/black</item>
+ </style>
+
+ <style name="NavigationBarButton">
+ <item name="android:layout_height">96dp</item>
+ <item name="android:layout_width">96dp</item>
+ <item name="android:background">@drawable/nav_button_background</item>
+ </style>
+
+</resources>
diff --git a/packages/CarSystemUI/res/xml/car_volume_items.xml b/packages/CarSystemUI/res/xml/car_volume_items.xml
new file mode 100644
index 000000000000..8715946aac26
--- /dev/null
+++ b/packages/CarSystemUI/res/xml/car_volume_items.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 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.
+ -->
+
+<!--
+ Defines all possible items on car volume settings UI, keyed by usage.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+ <item car:usage="unknown"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="media"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="voice_communication"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="voice_communication_signalling"
+ car:icon="@*android:drawable/ic_audio_ring_notif"/>
+ <item car:usage="alarm"
+ car:icon="@drawable/ic_volume_alarm"/>
+ <item car:usage="notification"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_ringtone"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_request"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_instant"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_communication_delayed"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="notification_event"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="assistance_accessibility"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="assistance_navigation_guidance"
+ car:icon="@drawable/car_ic_navigation"/>
+ <item car:usage="assistance_sonification"
+ car:icon="@drawable/car_ic_notification"/>
+ <item car:usage="game"
+ car:icon="@drawable/car_ic_music"/>
+ <item car:usage="assistant"
+ car:icon="@drawable/car_ic_music"/>
+</carVolumeItems>
+
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
new file mode 100644
index 000000000000..1d39f72b12dc
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.util.ArrayMap;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.Dependency.DependencyProvider;
+import com.android.systemui.car.CarNotificationEntryManager;
+import com.android.systemui.statusbar.NotificationEntryManager;
+import com.android.systemui.statusbar.car.CarFacetButtonController;
+import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+
+/**
+ * Class factory to provide car specific SystemUI components.
+ */
+public class CarSystemUIFactory extends SystemUIFactory {
+
+ public StatusBarKeyguardViewManager createStatusBarKeyguardViewManager(Context context,
+ ViewMediatorCallback viewMediatorCallback, LockPatternUtils lockPatternUtils) {
+ return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
+ }
+
+ @Override
+ public void injectDependencies(ArrayMap<Object, DependencyProvider> providers,
+ Context context) {
+ super.injectDependencies(providers, context);
+ providers.put(NotificationEntryManager.class,
+ () -> new CarNotificationEntryManager(context));
+ providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context));
+ providers.put(HvacController.class, () -> new HvacController(context));
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
new file mode 100644
index 000000000000..27d31064955d
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import android.animation.ObjectAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import com.android.systemui.statusbar.car.hvac.TemperatureView;
+import com.android.systemui.statusbar.car.hvac.HvacController;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextSwitcher;
+import android.widget.TextView;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+
+/**
+ * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in
+ * the XML.
+ * XML properties:
+ * hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385)
+ * hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
+ * hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol)
+ * hvacOrientaion = Example: left
+ * <p>
+ * Note: It registers itself with {@link HvacController}
+ */
+public class AnimatedTemperatureView extends FrameLayout implements TemperatureView {
+
+ private static final float TEMPERATURE_EQUIVALENT_DELTA = .01f;
+ private static final Property<ColorDrawable, Integer> COLOR_PROPERTY =
+ new Property<ColorDrawable, Integer>(Integer.class, "color") {
+
+ @Override
+ public Integer get(ColorDrawable object) {
+ return object.getColor();
+ }
+
+ @Override
+ public void set(ColorDrawable object, Integer value) {
+ object.setColor(value);
+ }
+ };
+
+ static boolean isHorizontal(int gravity) {
+ return Gravity.isHorizontal(gravity)
+ && (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.CENTER_HORIZONTAL;
+ }
+
+ @SuppressLint("RtlHardcoded")
+ static boolean isLeft(int gravity, int layoutDirection) {
+ return Gravity
+ .getAbsoluteGravity(gravity & Gravity.HORIZONTAL_GRAVITY_MASK, layoutDirection)
+ == Gravity.LEFT;
+ }
+
+ static boolean isVertical(int gravity) {
+ return Gravity.isVertical(gravity)
+ && (gravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.CENTER_VERTICAL;
+ }
+
+ static boolean isTop(int gravity) {
+ return (gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.TOP;
+ }
+
+ private final int mAreaId;
+ private final int mPropertyId;
+ private final int mPivotOffset;
+ private final int mGravity;
+ private final int mTextAppearanceRes;
+ private final int mMinEms;
+ private final Rect mPaddingRect;
+ private final float mMinValue;
+ private final float mMaxValue;
+
+ private final ColorDrawable mBackgroundColor;
+
+ private final TemperatureColorStore mColorStore = new TemperatureColorStore();
+ private final TemperatureBackgroundAnimator mBackgroundAnimator;
+ private final TemperatureTextAnimator mTextAnimator;
+
+ public AnimatedTemperatureView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray typedArray = context.obtainStyledAttributes(attrs,
+ R.styleable.AnimatedTemperatureView);
+ mAreaId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacAreaId, -1);
+ mPropertyId = typedArray.getInt(R.styleable.AnimatedTemperatureView_hvacPropertyId, -1);
+ mPivotOffset =
+ typedArray.getDimensionPixelOffset(
+ R.styleable.AnimatedTemperatureView_hvacPivotOffset, 0);
+ mGravity = typedArray.getInt(R.styleable.AnimatedTemperatureView_android_gravity,
+ Gravity.START);
+ mTextAppearanceRes =
+ typedArray.getResourceId(R.styleable.AnimatedTemperatureView_android_textAppearance,
+ 0);
+ mMinEms = typedArray.getInteger(R.styleable.AnimatedTemperatureView_android_minEms, 0);
+ mMinValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMinValue,
+ Float.NaN);
+ mMaxValue = typedArray.getFloat(R.styleable.AnimatedTemperatureView_hvacMaxValue,
+ Float.NaN);
+
+
+ mPaddingRect =
+ new Rect(getPaddingLeft(), getPaddingTop(), getPaddingRight(), getPaddingBottom());
+ setPadding(0, 0, 0, 0);
+
+ setClipChildren(false);
+ setClipToPadding(false);
+
+ // init Views
+ TextSwitcher textSwitcher = new TextSwitcher(context);
+ textSwitcher.setFactory(this::generateTextView);
+ ImageView background = new ImageView(context);
+ mBackgroundColor = new ColorDrawable(Color.TRANSPARENT);
+ background.setImageDrawable(mBackgroundColor);
+ background.setVisibility(View.GONE);
+
+ mBackgroundAnimator = new TemperatureBackgroundAnimator(this, background);
+
+
+ String format = typedArray.getString(R.styleable.AnimatedTemperatureView_hvacTempFormat);
+ format = (format == null) ? "%.1f\u00B0" : format;
+ CharSequence minText = typedArray.getString(
+ R.styleable.AnimatedTemperatureView_hvacMinText);
+ CharSequence maxText = typedArray.getString(
+ R.styleable.AnimatedTemperatureView_hvacMaxText);
+ mTextAnimator = new TemperatureTextAnimator(this, textSwitcher, format, mPivotOffset,
+ minText, maxText);
+
+ addView(background, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ addView(textSwitcher, ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+
+ typedArray.recycle();
+
+ // register with controller
+ HvacController hvacController = Dependency.get(HvacController.class);
+ hvacController.addHvacTextView(this);
+ }
+
+ private TextView generateTextView() {
+ TextView textView = new TextView(getContext());
+ textView.setTextAppearance(mTextAppearanceRes);
+ textView.setAllCaps(true);
+ textView.setMinEms(mMinEms);
+ textView.setGravity(mGravity);
+ textView.setPadding(mPaddingRect.left, mPaddingRect.top, mPaddingRect.right,
+ mPaddingRect.bottom);
+ textView.getViewTreeObserver()
+ .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ if (isHorizontal(mGravity)) {
+ if (isLeft(mGravity, getLayoutDirection())) {
+ textView.setPivotX(-mPivotOffset);
+ } else {
+ textView.setPivotX(textView.getWidth() + mPivotOffset);
+ }
+ }
+ textView.getViewTreeObserver().removeOnPreDrawListener(this);
+ return false;
+ }
+ });
+ textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+
+ return textView;
+ }
+
+ /**
+ * Formats the float for display
+ *
+ * @param temp - The current temp or NaN
+ */
+ @Override
+ public void setTemp(float temp) {
+ mTextAnimator.setTemp(temp);
+ if (Float.isNaN(temp)) {
+ mBackgroundAnimator.hideCircle();
+ return;
+ }
+ int color;
+ if (isMinValue(temp)) {
+ color = mColorStore.getMinColor();
+ } else if (isMaxValue(temp)) {
+ color = mColorStore.getMaxColor();
+ } else {
+ color = mColorStore.getColorForTemperature(temp);
+ }
+ if (mBackgroundAnimator.isOpen()) {
+ ObjectAnimator colorAnimator =
+ ObjectAnimator.ofInt(mBackgroundColor, COLOR_PROPERTY, color);
+ colorAnimator.setEvaluator((fraction, startValue, endValue) -> mColorStore
+ .lerpColor(fraction, (int) startValue, (int) endValue));
+ colorAnimator.start();
+ } else {
+ mBackgroundColor.setColor(color);
+ }
+
+ mBackgroundAnimator.animateOpen();
+ }
+
+ boolean isMinValue(float temp) {
+ return !Float.isNaN(mMinValue) && isApproxEqual(temp, mMinValue);
+ }
+
+ boolean isMaxValue(float temp) {
+ return !Float.isNaN(mMaxValue) && isApproxEqual(temp, mMaxValue);
+ }
+
+ private boolean isApproxEqual(float left, float right) {
+ return Math.abs(left - right) <= TEMPERATURE_EQUIVALENT_DELTA;
+ }
+
+ int getGravity() {
+ return mGravity;
+ }
+
+ int getPivotOffset() {
+ return mPivotOffset;
+ }
+
+ Rect getPaddingRect() {
+ return mPaddingRect;
+ }
+
+ /**
+ * @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (358614275)
+ */
+ @Override
+ public int getPropertyId() {
+ return mPropertyId;
+ }
+
+ /**
+ * @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
+ */
+ @Override
+ public int getAreaId() {
+ return mAreaId;
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mBackgroundAnimator.stopAnimations();
+ }
+
+}
+
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
new file mode 100644
index 000000000000..0bc94b500387
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isTop;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isVertical;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.IntDef;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.animation.AnticipateInterpolator;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Controls circular reveal animation of temperature background
+ */
+class TemperatureBackgroundAnimator {
+
+ private static final AnticipateInterpolator ANTICIPATE_INTERPOLATOR =
+ new AnticipateInterpolator();
+ private static final float MAX_OPACITY = .6f;
+
+ private final View mAnimatedView;
+
+ private int mPivotX;
+ private int mPivotY;
+ private int mGoneRadius;
+ private int mOvershootRadius;
+ private int mRestingRadius;
+ private int mBumpRadius;
+
+ @CircleState
+ private int mCircleState;
+
+ private Animator mCircularReveal;
+ private boolean mAnimationsReady;
+
+ @IntDef({CircleState.GONE, CircleState.ENTERING, CircleState.OVERSHOT, CircleState.RESTING,
+ CircleState.RESTED, CircleState.BUMPING, CircleState.BUMPED, CircleState.EXITING})
+ private @interface CircleState {
+ int GONE = 0;
+ int ENTERING = 1;
+ int OVERSHOT = 2;
+ int RESTING = 3;
+ int RESTED = 4;
+ int BUMPING = 5;
+ int BUMPED = 6;
+ int EXITING = 7;
+ }
+
+ TemperatureBackgroundAnimator(
+ AnimatedTemperatureView parent,
+ ImageView animatedView) {
+ mAnimatedView = animatedView;
+ mAnimatedView.setAlpha(0);
+
+ parent.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ setupAnimations(parent.getGravity(), parent.getPivotOffset(),
+ parent.getPaddingRect(), parent.getWidth(), parent.getHeight()));
+ }
+
+ private void setupAnimations(int gravity, int pivotOffset, Rect paddingRect,
+ int width, int height) {
+ int padding;
+ if (isHorizontal(gravity)) {
+ mGoneRadius = pivotOffset;
+ if (isLeft(gravity, mAnimatedView.getLayoutDirection())) {
+ mPivotX = -pivotOffset;
+ padding = paddingRect.right;
+ } else {
+ mPivotX = width + pivotOffset;
+ padding = paddingRect.left;
+ }
+ mPivotY = height / 2;
+ mOvershootRadius = pivotOffset + width;
+ } else if (isVertical(gravity)) {
+ mGoneRadius = pivotOffset;
+ if (isTop(gravity)) {
+ mPivotY = -pivotOffset;
+ padding = paddingRect.bottom;
+ } else {
+ mPivotY = height + pivotOffset;
+ padding = paddingRect.top;
+ }
+ mPivotX = width / 2;
+ mOvershootRadius = pivotOffset + height;
+ } else {
+ mPivotX = width / 2;
+ mPivotY = height / 2;
+ mGoneRadius = 0;
+ if (width > height) {
+ mOvershootRadius = height;
+ padding = Math.max(paddingRect.top, paddingRect.bottom);
+ } else {
+ mOvershootRadius = width;
+ padding = Math.max(paddingRect.left, paddingRect.right);
+ }
+ }
+ mRestingRadius = mOvershootRadius - padding;
+ mBumpRadius = mOvershootRadius - padding / 3;
+ mAnimationsReady = true;
+ }
+
+ boolean isOpen() {
+ return mCircleState != CircleState.GONE;
+ }
+
+ void animateOpen() {
+ if (!mAnimationsReady || mCircleState == CircleState.ENTERING) {
+ return;
+ }
+
+ AnimatorSet set = new AnimatorSet();
+ List<Animator> animators = new ArrayList<>();
+ switch (mCircleState) {
+ case CircleState.ENTERING:
+ throw new AssertionError("Should not be able to reach this statement");
+ case CircleState.GONE: {
+ Animator startCircle = createEnterAnimator();
+ markState(startCircle, CircleState.ENTERING);
+ animators.add(startCircle);
+ Animator holdOvershoot = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius,
+ mOvershootRadius);
+ holdOvershoot.setDuration(50);
+ markState(holdOvershoot, CircleState.OVERSHOT);
+ animators.add(holdOvershoot);
+ Animator rest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mOvershootRadius,
+ mRestingRadius);
+ markState(rest, CircleState.RESTING);
+ animators.add(rest);
+ Animator holdRest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius,
+ mRestingRadius);
+ markState(holdRest, CircleState.RESTED);
+ holdRest.setDuration(1000);
+ animators.add(holdRest);
+ Animator exit = createExitAnimator(mRestingRadius);
+ markState(exit, CircleState.EXITING);
+ animators.add(exit);
+ }
+ break;
+ case CircleState.RESTED:
+ case CircleState.RESTING:
+ case CircleState.EXITING:
+ case CircleState.OVERSHOT:
+ int startRadius =
+ mCircleState == CircleState.OVERSHOT ? mOvershootRadius : mRestingRadius;
+ Animator bump = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius,
+ mBumpRadius);
+ bump.setDuration(50);
+ markState(bump, CircleState.BUMPING);
+ animators.add(bump);
+ // fallthrough intentional
+ case CircleState.BUMPED:
+ case CircleState.BUMPING:
+ Animator holdBump = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius,
+ mBumpRadius);
+ holdBump.setDuration(100);
+ markState(holdBump, CircleState.BUMPED);
+ animators.add(holdBump);
+ Animator rest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mBumpRadius,
+ mRestingRadius);
+ markState(rest, CircleState.RESTING);
+ animators.add(rest);
+ Animator holdRest = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mRestingRadius,
+ mRestingRadius);
+ holdRest.setDuration(1000);
+ markState(holdRest, CircleState.RESTED);
+ animators.add(holdRest);
+ Animator exit = createExitAnimator(mRestingRadius);
+ markState(exit, CircleState.EXITING);
+ animators.add(exit);
+ break;
+ }
+ set.playSequentially(animators);
+ set.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ if (mCircularReveal != null) {
+ mCircularReveal.cancel();
+ }
+ mCircularReveal = animation;
+ mAnimatedView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) {
+ return;
+ }
+ mCircularReveal = null;
+ mCircleState = CircleState.GONE;
+ mAnimatedView.setVisibility(View.GONE);
+ }
+ });
+
+ set.start();
+ }
+
+ private Animator createEnterAnimator() {
+ AnimatorSet animatorSet = new AnimatorSet();
+ Animator circularReveal = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, mGoneRadius,
+ mOvershootRadius);
+ Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, MAX_OPACITY);
+ animatorSet.playTogether(circularReveal, fade);
+ return animatorSet;
+ }
+
+ private Animator createExitAnimator(int startRadius) {
+ AnimatorSet animatorSet = new AnimatorSet();
+ Animator circularHide = ViewAnimationUtils
+ .createCircularReveal(mAnimatedView, mPivotX, mPivotY, startRadius,
+ (mGoneRadius + startRadius) / 2);
+ circularHide.setInterpolator(ANTICIPATE_INTERPOLATOR);
+ Animator fade = ObjectAnimator.ofFloat(mAnimatedView, View.ALPHA, 0);
+ fade.setStartDelay(50);
+ animatorSet.playTogether(circularHide, fade);
+ return animatorSet;
+ }
+
+ void hideCircle() {
+ if (!mAnimationsReady || mCircleState == CircleState.GONE
+ || mCircleState == CircleState.EXITING) {
+ return;
+ }
+
+ int startRadius;
+ switch (mCircleState) {
+ // Unreachable, but here to exhaust switch cases
+ //noinspection ConstantConditions
+ case CircleState.EXITING:
+ //noinspection ConstantConditions
+ case CircleState.GONE:
+ throw new AssertionError("Should not be able to reach this statement");
+ case CircleState.BUMPED:
+ case CircleState.BUMPING:
+ startRadius = mBumpRadius;
+ break;
+ case CircleState.OVERSHOT:
+ startRadius = mOvershootRadius;
+ break;
+ case CircleState.ENTERING:
+ case CircleState.RESTED:
+ case CircleState.RESTING:
+ startRadius = mRestingRadius;
+ break;
+ default:
+ throw new IllegalStateException("Unknown CircleState: " + mCircleState);
+ }
+
+ Animator hideAnimation = createExitAnimator(startRadius);
+ if (startRadius == mRestingRadius) {
+ hideAnimation.setInterpolator(ANTICIPATE_INTERPOLATOR);
+ }
+ hideAnimation.addListener(new AnimatorListenerAdapter() {
+ private boolean mCanceled = false;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCircleState = CircleState.EXITING;
+ if (mCircularReveal != null) {
+ mCircularReveal.cancel();
+ }
+ mCircularReveal = animation;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCanceled) {
+ return;
+ }
+ mCircularReveal = null;
+ mCircleState = CircleState.GONE;
+ mAnimatedView.setVisibility(View.GONE);
+ }
+ });
+ hideAnimation.start();
+ }
+
+ void stopAnimations() {
+ if (mCircularReveal != null) {
+ mCircularReveal.end();
+ }
+ }
+
+ private void markState(Animator animator, @CircleState int startState) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCircleState = startState;
+ }
+ });
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
new file mode 100644
index 000000000000..a40ffaf850c5
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import android.graphics.Color;
+
+/**
+ * Contains the logic for mapping colors to temperatures
+ */
+class TemperatureColorStore {
+
+ private static class TemperatureColorValue {
+ final float mTemperature;
+ final int mColor;
+
+ private TemperatureColorValue(float temperature, int color) {
+ this.mTemperature = temperature;
+ this.mColor = color;
+ }
+
+ float getTemperature() {
+ return mTemperature;
+ }
+
+ int getColor() {
+ return mColor;
+ }
+ }
+
+ private static TemperatureColorValue tempToColor(float temperature, int color) {
+ return new TemperatureColorValue(temperature, color);
+ }
+
+ private static final int COLOR_COLDEST = 0xFF406DFF;
+ private static final int COLOR_COLD = 0xFF4094FF;
+ private static final int COLOR_NEUTRAL = 0xFFF4F4F4;
+ private static final int COLOR_WARM = 0xFFFF550F;
+ private static final int COLOR_WARMEST = 0xFFFF0000;
+ // must be sorted by temperature
+ private static final TemperatureColorValue[] sTemperatureColorValues =
+ {
+ // Celsius
+ tempToColor(19, COLOR_COLDEST),
+ tempToColor(21, COLOR_COLD),
+ tempToColor(23, COLOR_NEUTRAL),
+ tempToColor(25, COLOR_WARM),
+ tempToColor(27, COLOR_WARMEST),
+
+ // Switch over
+ tempToColor(45, COLOR_WARMEST),
+ tempToColor(45.00001f, COLOR_COLDEST),
+
+ // Farenheight
+ tempToColor(66, COLOR_COLDEST),
+ tempToColor(70, COLOR_COLD),
+ tempToColor(74, COLOR_NEUTRAL),
+ tempToColor(76, COLOR_WARM),
+ tempToColor(80, COLOR_WARMEST)
+ };
+
+ private static final int COLOR_UNSET = Color.BLACK;
+
+ private final float[] mTempHsv1 = new float[3];
+ private final float[] mTempHsv2 = new float[3];
+ private final float[] mTempHsv3 = new float[3];
+
+ int getMinColor() {
+ return COLOR_COLDEST;
+ }
+
+ int getMaxColor() {
+ return COLOR_WARMEST;
+ }
+
+ int getColorForTemperature(float temperature) {
+ if (Float.isNaN(temperature)) {
+ return COLOR_UNSET;
+ }
+ TemperatureColorValue bottomValue = sTemperatureColorValues[0];
+ if (temperature <= bottomValue.getTemperature()) {
+ return bottomValue.getColor();
+ }
+ TemperatureColorValue topValue =
+ sTemperatureColorValues[sTemperatureColorValues.length - 1];
+ if (temperature >= topValue.getTemperature()) {
+ return topValue.getColor();
+ }
+
+ int index = binarySearch(temperature);
+ if (index >= 0) {
+ return sTemperatureColorValues[index].getColor();
+ }
+
+ index = -index - 1; // move to the insertion point
+
+ TemperatureColorValue startValue = sTemperatureColorValues[index - 1];
+ TemperatureColorValue endValue = sTemperatureColorValues[index];
+ float fraction = (temperature - startValue.getTemperature()) / (endValue.getTemperature()
+ - startValue.getTemperature());
+ return lerpColor(fraction, startValue.getColor(), endValue.getColor());
+ }
+
+ int lerpColor(float fraction, int startColor, int endColor) {
+ float[] startHsv = mTempHsv1;
+ Color.colorToHSV(startColor, startHsv);
+ float[] endHsv = mTempHsv2;
+ Color.colorToHSV(endColor, endHsv);
+
+ // If a target color is white/gray, it should use the same hue as the other target
+ if (startHsv[1] == 0) {
+ startHsv[0] = endHsv[0];
+ }
+ if (endHsv[1] == 0) {
+ endHsv[0] = startHsv[0];
+ }
+
+ float[] outColor = mTempHsv3;
+ outColor[0] = hueLerp(fraction, startHsv[0], endHsv[0]);
+ outColor[1] = lerp(fraction, startHsv[1], endHsv[1]);
+ outColor[2] = lerp(fraction, startHsv[2], endHsv[2]);
+
+ return Color.HSVToColor(outColor);
+ }
+
+ private float hueLerp(float fraction, float start, float end) {
+ // If in flat part of curve, no interpolation necessary
+ if (start == end) {
+ return start;
+ }
+
+ // If the hues are more than 180 degrees apart, go the other way around the color wheel
+ // by moving the smaller value above 360
+ if (Math.abs(start - end) > 180f) {
+ if (start < end) {
+ start += 360f;
+ } else {
+ end += 360f;
+ }
+ }
+ // Lerp and ensure the final output is within [0, 360)
+ return lerp(fraction, start, end) % 360f;
+
+ }
+
+ private float lerp(float fraction, float start, float end) {
+ // If in flat part of curve, no interpolation necessary
+ if (start == end) {
+ return start;
+ }
+
+ // If outside bounds, use boundary value
+ if (fraction >= 1) {
+ return end;
+ }
+ if (fraction <= 0) {
+ return start;
+ }
+
+ return (end - start) * fraction + start;
+ }
+
+ private int binarySearch(float temperature) {
+ int low = 0;
+ int high = sTemperatureColorValues.length;
+
+ while (low <= high) {
+ int mid = (low + high) >>> 1;
+ float midVal = sTemperatureColorValues[mid].getTemperature();
+
+ if (midVal < temperature) {
+ low = mid + 1; // Neither val is NaN, thisVal is smaller
+ } else if (midVal > temperature) {
+ high = mid - 1; // Neither val is NaN, thisVal is larger
+ } else {
+ int midBits = Float.floatToIntBits(midVal);
+ int keyBits = Float.floatToIntBits(temperature);
+ if (midBits == keyBits) { // Values are equal
+ return mid; // Key found
+ } else if (midBits < keyBits) { // (-0.0, 0.0) or (!NaN, NaN)
+ low = mid + 1;
+ } else { /* (0.0, -0.0) or (NaN, !NaN)*/
+ high = mid - 1;
+ }
+ }
+ }
+ return -(low + 1); // key not found.
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
new file mode 100644
index 000000000000..8ee5ef6badc3
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.hvac;
+
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+
+import android.annotation.NonNull;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.RotateAnimation;
+import android.view.animation.TranslateAnimation;
+import android.widget.TextSwitcher;
+
+/**
+ * Controls animating TemperatureView's text
+ */
+class TemperatureTextAnimator {
+
+ private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
+ new DecelerateInterpolator();
+ private static final AccelerateDecelerateInterpolator ACCELERATE_DECELERATE_INTERPOLATOR =
+ new AccelerateDecelerateInterpolator();
+
+ private static final int ROTATION_DEGREES = 15;
+ private static final int DURATION_MILLIS = 200;
+
+ private AnimatedTemperatureView mParent;
+ private final TextSwitcher mTextSwitcher;
+ private final String mTempFormat;
+ private final int mPivotOffset;
+ private final CharSequence mMinText;
+ private final CharSequence mMaxText;
+
+ private Animation mTextInAnimationUp;
+ private Animation mTextOutAnimationUp;
+ private Animation mTextInAnimationDown;
+ private Animation mTextOutAnimationDown;
+ private Animation mTextFadeInAnimation;
+ private Animation mTextFadeOutAnimation;
+
+ private float mLastTemp = Float.NaN;
+
+ TemperatureTextAnimator(AnimatedTemperatureView parent, TextSwitcher textSwitcher,
+ String tempFormat, int pivotOffset,
+ CharSequence minText, CharSequence maxText) {
+ mParent = parent;
+ mTextSwitcher = textSwitcher;
+ mTempFormat = tempFormat;
+ mPivotOffset = pivotOffset;
+ mMinText = minText;
+ mMaxText = maxText;
+
+ mParent.addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+ setupAnimations(mParent.getGravity()));
+ }
+
+ void setTemp(float temp) {
+ if (Float.isNaN(temp)) {
+ mTextSwitcher.setInAnimation(mTextFadeInAnimation);
+ mTextSwitcher.setOutAnimation(mTextFadeOutAnimation);
+ mTextSwitcher.setText("--");
+ mLastTemp = temp;
+ return;
+ }
+ boolean isMinValue = mParent.isMinValue(temp);
+ boolean isMaxValue = mParent.isMaxValue(temp);
+ if (Float.isNaN(mLastTemp)) {
+ mTextSwitcher.setInAnimation(mTextFadeInAnimation);
+ mTextSwitcher.setOutAnimation(mTextFadeOutAnimation);
+ } else if (!isMinValue && (isMaxValue || temp > mLastTemp)) {
+ mTextSwitcher.setInAnimation(mTextInAnimationUp);
+ mTextSwitcher.setOutAnimation(mTextOutAnimationUp);
+ } else {
+ mTextSwitcher.setInAnimation(mTextInAnimationDown);
+ mTextSwitcher.setOutAnimation(mTextOutAnimationDown);
+ }
+ CharSequence text;
+ if (isMinValue) {
+ text = mMinText;
+ } else if (isMaxValue) {
+ text = mMaxText;
+ } else {
+ text = String.format(mTempFormat, temp);
+ }
+ mTextSwitcher.setText(text);
+ mLastTemp = temp;
+ }
+
+ private void setupAnimations(int gravity) {
+ mTextFadeInAnimation = createFadeAnimation(true);
+ mTextFadeOutAnimation = createFadeAnimation(false);
+ if (!isHorizontal(gravity)) {
+ mTextInAnimationUp = createTranslateFadeAnimation(true, true);
+ mTextOutAnimationUp = createTranslateFadeAnimation(false, true);
+ mTextInAnimationDown = createTranslateFadeAnimation(true, false);
+ mTextOutAnimationDown = createTranslateFadeAnimation(false, false);
+ } else {
+ boolean isLeft = isLeft(gravity, mTextSwitcher.getLayoutDirection());
+ mTextInAnimationUp = createRotateFadeAnimation(true, isLeft, true);
+ mTextOutAnimationUp = createRotateFadeAnimation(false, isLeft, true);
+ mTextInAnimationDown = createRotateFadeAnimation(true, isLeft, false);
+ mTextOutAnimationDown = createRotateFadeAnimation(false, isLeft, false);
+ }
+ }
+
+ @NonNull
+ private Animation createFadeAnimation(boolean in) {
+ AnimationSet set = new AnimationSet(true);
+ AlphaAnimation alphaAnimation = new AlphaAnimation(in ? 0 : 1, in ? 1 : 0);
+ alphaAnimation.setDuration(DURATION_MILLIS);
+ set.addAnimation(new RotateAnimation(0, 0)); // Undo any previous rotation
+ set.addAnimation(alphaAnimation);
+ return set;
+ }
+
+ @NonNull
+ private Animation createTranslateFadeAnimation(boolean in, boolean up) {
+ AnimationSet set = new AnimationSet(true);
+ set.setInterpolator(ACCELERATE_DECELERATE_INTERPOLATOR);
+ set.setDuration(DURATION_MILLIS);
+ int fromYDelta = in ? (up ? 1 : -1) : 0;
+ int toYDelta = in ? 0 : (up ? -1 : 1);
+ set.addAnimation(
+ new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0,
+ Animation.RELATIVE_TO_SELF, fromYDelta, Animation.RELATIVE_TO_SELF,
+ toYDelta));
+ set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0));
+ return set;
+ }
+
+ @NonNull
+ private Animation createRotateFadeAnimation(boolean in, boolean isLeft, boolean up) {
+ AnimationSet set = new AnimationSet(true);
+ set.setInterpolator(DECELERATE_INTERPOLATOR);
+ set.setDuration(DURATION_MILLIS);
+
+ float degrees = isLeft == up ? -ROTATION_DEGREES : ROTATION_DEGREES;
+ int pivotX = isLeft ? -mPivotOffset : mParent.getWidth() + mPivotOffset;
+ set.addAnimation(
+ new RotateAnimation(in ? -degrees : 0f, in ? 0f : degrees, Animation.ABSOLUTE,
+ pivotX, Animation.ABSOLUTE, 0f));
+ set.addAnimation(new AlphaAnimation(in ? 0 : 1, in ? 1 : 0));
+ return set;
+ }
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index fdd6a9caefa3..a539b1f2ba60 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -19,7 +19,9 @@ package android.ext.services.notification;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
+import android.annotation.NonNull;
import android.app.INotificationManager;
+import android.app.Notification;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -80,6 +82,7 @@ public class Assistant extends NotificationAssistantService {
private float mDismissToViewRatioLimit;
private int mStreakLimit;
+ private SmartActionsHelper mSmartActionsHelper;
// key : impressions tracker
// TODO: prune deleted channels and apps
@@ -99,6 +102,7 @@ public class Assistant extends NotificationAssistantService {
// Contexts are correctly hooked up by the creation step, which is required for the observer
// to be hooked up/initialized.
new SettingsObserver(mHandler);
+ mSmartActionsHelper = new SmartActionsHelper();
}
private void loadFile() {
@@ -187,7 +191,26 @@ public class Assistant extends NotificationAssistantService {
@Override
public Adjustment onNotificationEnqueued(StatusBarNotification sbn) {
if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey());
- return null;
+ ArrayList<Notification.Action> actions =
+ mSmartActionsHelper.suggestActions(this, sbn);
+ if (actions.isEmpty()) {
+ return null;
+ }
+ return createEnqueuedNotificationAdjustment(sbn, actions);
+ }
+
+ /** A convenience helper for creating an adjustment for an SBN. */
+ private Adjustment createEnqueuedNotificationAdjustment(
+ @NonNull StatusBarNotification statusBarNotification,
+ @NonNull ArrayList<Notification.Action> smartActions) {
+ Bundle signals = new Bundle();
+ signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
+ return new Adjustment(
+ statusBarNotification.getPackageName(),
+ statusBarNotification.getKey(),
+ signals,
+ "smart action" /* explanation */,
+ statusBarNotification.getUserId());
}
@Override
@@ -378,4 +401,4 @@ public class Assistant extends NotificationAssistantService {
}
}
}
-} \ No newline at end of file
+}
diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
new file mode 100644
index 000000000000..1754461b844d
--- /dev/null
+++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.ext.services.notification;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.RemoteAction;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.view.textclassifier.TextClassification;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassifier;
+import android.view.textclassifier.TextLinks;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class SmartActionsHelper {
+ private static final ArrayList<Notification.Action> EMPTY_LIST = new ArrayList<>();
+
+ // If a notification has any of these flags set, it's inelgibile for actions being added.
+ private static final int FLAG_MASK_INELGIBILE_FOR_ACTIONS =
+ Notification.FLAG_ONGOING_EVENT
+ | Notification.FLAG_FOREGROUND_SERVICE
+ | Notification.FLAG_GROUP_SUMMARY
+ | Notification.FLAG_NO_CLEAR;
+ private static final int MAX_ACTION_EXTRACTION_TEXT_LENGTH = 400;
+ private static final int MAX_ACTIONS_PER_LINK = 1;
+ private static final int MAX_SMART_ACTIONS = Notification.MAX_ACTION_BUTTONS;
+
+ SmartActionsHelper() {}
+
+ /**
+ * Adds action adjustments based on the notification contents.
+ *
+ * TODO: Once we have a API in {@link TextClassificationManager} to predict smart actions
+ * from notification text / message, we can replace most of the code here by consuming that API.
+ */
+ @NonNull
+ ArrayList<Notification.Action> suggestActions(
+ @Nullable Context context, @NonNull StatusBarNotification sbn) {
+ if (!isEligibleForActionAdjustment(sbn)) {
+ return EMPTY_LIST;
+ }
+ if (context == null) {
+ return EMPTY_LIST;
+ }
+ TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class);
+ if (tcm == null) {
+ return EMPTY_LIST;
+ }
+ Notification.Action[] actions = sbn.getNotification().actions;
+ int numOfExistingActions = actions == null ? 0: actions.length;
+ int maxSmartActions = MAX_SMART_ACTIONS - numOfExistingActions;
+ return suggestActionsFromText(
+ tcm,
+ getMostSalientActionText(sbn.getNotification()), maxSmartActions);
+ }
+
+ /**
+ * Returns whether a notification is eligible for action adjustments.
+ *
+ * <p>We exclude system notifications, those that get refreshed frequently, or ones that relate
+ * to fundamental phone functionality where any error would result in a very negative user
+ * experience.
+ */
+ private boolean isEligibleForActionAdjustment(@NonNull StatusBarNotification sbn) {
+ Notification notification = sbn.getNotification();
+ String pkg = sbn.getPackageName();
+ if (notification.actions != null
+ && notification.actions.length >= Notification.MAX_ACTION_BUTTONS) {
+ return false;
+ }
+ if (0 != (notification.flags & FLAG_MASK_INELGIBILE_FOR_ACTIONS)) {
+ return false;
+ }
+ if (TextUtils.isEmpty(pkg) || pkg.equals("android")) {
+ return false;
+ }
+ // For now, we are only interested in messages.
+ return Notification.CATEGORY_MESSAGE.equals(notification.category)
+ || Notification.MessagingStyle.class.equals(notification.getNotificationStyle());
+ }
+
+ /** Returns the text most salient for action extraction in a notification. */
+ @Nullable
+ private CharSequence getMostSalientActionText(@NonNull Notification notification) {
+ /* If it's messaging style, use the most recent message. */
+ Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ if (messages != null && messages.length != 0) {
+ Bundle lastMessage = (Bundle) messages[messages.length - 1];
+ CharSequence lastMessageText =
+ lastMessage.getCharSequence(Notification.MessagingStyle.Message.KEY_TEXT);
+ if (!TextUtils.isEmpty(lastMessageText)) {
+ return lastMessageText;
+ }
+ }
+
+ // Fall back to using the normal text.
+ return notification.extras.getCharSequence(Notification.EXTRA_TEXT);
+ }
+
+ /** Returns a list of actions to act on entities in a given piece of text. */
+ @NonNull
+ private ArrayList<Notification.Action> suggestActionsFromText(
+ @NonNull TextClassificationManager tcm, @Nullable CharSequence text,
+ int maxSmartActions) {
+ if (TextUtils.isEmpty(text)) {
+ return EMPTY_LIST;
+ }
+ TextClassifier textClassifier = tcm.getTextClassifier();
+
+ // We want to process only text visible to the user to avoid confusing suggestions, so we
+ // truncate the text to a reasonable length. This is particularly important for e.g.
+ // email apps that sometimes include the text for the entire thread.
+ text = text.subSequence(0, Math.min(text.length(), MAX_ACTION_EXTRACTION_TEXT_LENGTH));
+
+ // Extract all entities.
+ TextLinks.Request textLinksRequest = new TextLinks.Request.Builder(text)
+ .setEntityConfig(
+ TextClassifier.EntityConfig.createWithHints(
+ Collections.singletonList(
+ TextClassifier.HINT_TEXT_IS_NOT_EDITABLE)))
+ .build();
+ TextLinks links = textClassifier.generateLinks(textLinksRequest);
+ ArrayMap<String, Integer> entityTypeFrequency = getEntityTypeFrequency(links);
+
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ // Ignore any entity type for which we have too many entities. This is to handle the
+ // case where a notification contains e.g. a list of phone numbers. In such cases, the
+ // user likely wants to act on the whole list rather than an individual entity.
+ if (link.getEntityCount() == 0
+ || entityTypeFrequency.get(link.getEntity(0)) != 1) {
+ continue;
+ }
+
+ // Generate the actions, and add the most prominent ones to the action bar.
+ TextClassification classification =
+ textClassifier.classifyText(
+ new TextClassification.Request.Builder(
+ text, link.getStart(), link.getEnd()).build());
+ int numOfActions = Math.min(
+ MAX_ACTIONS_PER_LINK, classification.getActions().size());
+ for (int i = 0; i < numOfActions; ++i) {
+ RemoteAction action = classification.getActions().get(i);
+ actions.add(
+ new Notification.Action.Builder(
+ action.getIcon(),
+ action.getTitle(),
+ action.getActionIntent())
+ .build());
+ // We have enough smart actions.
+ if (actions.size() >= maxSmartActions) {
+ return actions;
+ }
+ }
+ }
+ return actions;
+ }
+
+ /**
+ * Given the links extracted from a piece of text, returns the frequency of each entity
+ * type.
+ */
+ @NonNull
+ private ArrayMap<String, Integer> getEntityTypeFrequency(@NonNull TextLinks links) {
+ ArrayMap<String, Integer> entityTypeCount = new ArrayMap<>();
+ for (TextLinks.TextLink link : links.getLinks()) {
+ if (link.getEntityCount() == 0) {
+ continue;
+ }
+ String entityType = link.getEntity(0);
+ if (entityTypeCount.containsKey(entityType)) {
+ entityTypeCount.put(entityType, entityTypeCount.get(entityType) + 1);
+ } else {
+ entityTypeCount.put(entityType, 1);
+ }
+ }
+ return entityTypeCount;
+ }
+}
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
deleted file mode 100644
index b6a5eb5fa48c..000000000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
deleted file mode 100644
index 4e36bd29db9e..000000000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970d6b3f..000000000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png
deleted file mode 100644
index bb9d855f7769..000000000000
--- a/packages/PrintSpooler/res/drawable-hdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png
deleted file mode 100644
index 2757db05d657..000000000000
--- a/packages/PrintSpooler/res/drawable-hdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
deleted file mode 100644
index 428a94663194..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
deleted file mode 100644
index 3220eeafa859..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_less_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
deleted file mode 100644
index fbbd094bdc53..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png b/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
deleted file mode 100644
index 5530f52e6b0c..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_expand_more_24dp.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970d6b3f..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png
deleted file mode 100644
index bd611e8e24d2..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png
deleted file mode 100644
index c1b380a7778f..000000000000
--- a/packages/PrintSpooler/res/drawable-mdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
deleted file mode 100644
index 6161c209482d..000000000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
deleted file mode 100644
index 3a89805d503a..000000000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
deleted file mode 100644
index 5e54970d6b3f..000000000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_grayedout_printer.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png b/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png
deleted file mode 100644
index a7fdc0dfcb1d..000000000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/ic_restart.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png b/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png
deleted file mode 100644
index fedc00e22e2a..000000000000
--- a/packages/PrintSpooler/res/drawable-xhdpi/stat_notify_cancelling.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
deleted file mode 100644
index 52a52d9fa7a6..000000000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
deleted file mode 100644
index 15e6abd447fd..000000000000
--- a/packages/PrintSpooler/res/drawable-xxhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
deleted file mode 100644
index 46811a1269ad..000000000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_less.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png b/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
deleted file mode 100644
index 141f28b26264..000000000000
--- a/packages/PrintSpooler/res/drawable-xxxhdpi/ic_expand_more.png
+++ /dev/null
Binary files differ
diff --git a/packages/PrintSpooler/res/drawable/ic_clear.xml b/packages/PrintSpooler/res/drawable/ic_clear.xml
new file mode 100644
index 000000000000..076e8ef37ad0
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_clear.xml
@@ -0,0 +1,26 @@
+<?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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
+ android:fillColor="?android:colorForeground"/>
+</vector>
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_less.xml b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
index 6f1ece17d393..c3e87dc9c4c3 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_less.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_less.xml
@@ -1,43 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true">
-
- <item
- android:state_checked="true">
- <bitmap
- android:src="@drawable/ic_expand_less"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item
- android:state_pressed="true">
- <bitmap
- android:src="@drawable/ic_expand_less"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item>
- <bitmap
- android:src="@drawable/ic_expand_less"
- android:tint="?android:attr/colorControlNormal">
- </bitmap>
- </item>
-
-</selector>
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"
+ android:fillColor="?android:colorForeground" />
+</vector> \ No newline at end of file
diff --git a/packages/PrintSpooler/res/drawable/ic_expand_more.xml b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
index 8d7145278988..3895144a5c1c 100644
--- a/packages/PrintSpooler/res/drawable/ic_expand_more.xml
+++ b/packages/PrintSpooler/res/drawable/ic_expand_more.xml
@@ -1,43 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true">
-
- <item
- android:state_checked="true">
- <bitmap
- android:src="@drawable/ic_expand_more"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item
- android:state_pressed="true">
- <bitmap
- android:src="@drawable/ic_expand_more"
- android:tint="?android:attr/colorControlActivated">
- </bitmap>
- </item>
-
- <item>
- <bitmap
- android:src="@drawable/ic_expand_more"
- android:tint="?android:attr/colorControlNormal">
- </bitmap>
- </item>
-
-</selector>
+<!--
+ 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M7.41,8.59L12,13.17l4.59,-4.58L18,10l-6,6l-6,-6L7.41,8.59z"
+ android:fillColor="?android:colorForeground" />
+</vector> \ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/preview_page.xml b/packages/PrintSpooler/res/layout/preview_page.xml
index aafdd8fc035c..8db347e3db4f 100644
--- a/packages/PrintSpooler/res/layout/preview_page.xml
+++ b/packages/PrintSpooler/res/layout/preview_page.xml
@@ -44,7 +44,7 @@
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary">
+ android:textColor="@android:color/white">
</TextView>
<ImageView
diff --git a/packages/PrintSpooler/res/layout/preview_page_error.xml b/packages/PrintSpooler/res/layout/preview_page_error.xml
index 4e9fb7787010..99ab99d07c40 100644
--- a/packages/PrintSpooler/res/layout/preview_page_error.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_error.xml
@@ -21,11 +21,14 @@
android:gravity="center">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@drawable/print_warning"
- android:contentDescription="@null" />
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dip"
+ android:src="@*android:drawable/ic_print_error"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="@android:color/black"
+ android:importantForAccessibility="no" />
<TextView
android:layout_width="wrap_content"
diff --git a/packages/PrintSpooler/res/layout/preview_page_loading.xml b/packages/PrintSpooler/res/layout/preview_page_loading.xml
index 1af3a17ca227..918edd987a79 100644
--- a/packages/PrintSpooler/res/layout/preview_page_loading.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_loading.xml
@@ -19,14 +19,13 @@
android:layout_height="fill_parent">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_margin="36dip"
+ android:layout_width="120dp"
+ android:layout_height="110dp"
android:layout_gravity="center"
- android:src="@drawable/ic_grayedout_printer"
- android:contentDescription="@null"
- android:scaleType="centerInside"
- android:adjustViewBounds="true">
- </ImageView>
+ android:src="@*android:drawable/ic_print"
+ android:scaleType="fitCenter"
+ android:alpha="0.1"
+ android:tint="@android:color/black"
+ android:importantForAccessibility="no" />
</FrameLayout>
diff --git a/packages/PrintSpooler/res/layout/preview_page_selected.xml b/packages/PrintSpooler/res/layout/preview_page_selected.xml
index 77f4727434e6..6727142dfc8b 100644
--- a/packages/PrintSpooler/res/layout/preview_page_selected.xml
+++ b/packages/PrintSpooler/res/layout/preview_page_selected.xml
@@ -42,7 +42,7 @@
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimary">
+ android:textColor="@android:color/white">
</TextView>
<ImageView
diff --git a/packages/PrintSpooler/res/layout/print_activity.xml b/packages/PrintSpooler/res/layout/print_activity.xml
index 774f320d6fd2..212f398aa163 100644
--- a/packages/PrintSpooler/res/layout/print_activity.xml
+++ b/packages/PrintSpooler/res/layout/print_activity.xml
@@ -29,7 +29,7 @@
android:paddingStart="8dip"
android:layout_marginEnd="16dp"
android:elevation="@dimen/preview_controls_elevation"
- android:background="?android:attr/colorPrimary">
+ style="?android:actionBarStyle">
<com.android.printspooler.widget.ClickInterceptSpinner
android:id="@+id/destination_spinner"
@@ -55,7 +55,7 @@
android:paddingBottom="8dip"
android:orientation="horizontal"
android:elevation="@dimen/preview_controls_elevation"
- android:background="?android:attr/colorPrimary">
+ style="?android:actionBarStyle">
<TextView
android:layout_width="wrap_content"
@@ -121,7 +121,6 @@
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:animateLayoutChanges="true"
- android:background="@color/print_preview_background_color"
android:gravity="center">
<!-- Error message added here -->
diff --git a/packages/PrintSpooler/res/layout/print_activity_controls.xml b/packages/PrintSpooler/res/layout/print_activity_controls.xml
index 69d4f914a453..3aafc99da454 100644
--- a/packages/PrintSpooler/res/layout/print_activity_controls.xml
+++ b/packages/PrintSpooler/res/layout/print_activity_controls.xml
@@ -22,7 +22,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:elevation="@dimen/preview_controls_elevation"
- android:background="?android:attr/colorPrimary">
+ style="?android:actionBarStyle">
<LinearLayout
android:id="@+id/draggable_content"
diff --git a/packages/PrintSpooler/res/layout/print_error_fragment.xml b/packages/PrintSpooler/res/layout/print_error_fragment.xml
index 3ea2abdb5b5a..9d9dd0100481 100644
--- a/packages/PrintSpooler/res/layout/print_error_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_error_fragment.xml
@@ -16,35 +16,39 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dip"
+ android:layout_marginEnd="16dip"
android:gravity="center"
android:orientation="vertical">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@drawable/ic_grayedout_printer"
- android:contentDescription="@null">
- </ImageView>
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dp"
+ android:src="@*android:drawable/ic_print_error"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="?android:colorForeground"
+ android:importantForAccessibility="no" />
<TextView
android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginStart="16dip"
- android:layout_marginEnd="16dip"
- android:gravity="center_horizontal"
- android:text="@string/print_error_default_message"
- android:textAppearance="?android:attr/textAppearanceLargeInverse">
- </TextView>
+ android:layout_marginBottom="16dp"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_error_default_message" />
<Button
android:id="@+id/action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/print_error_retry">
- </Button>
+ style="?android:attr/borderlessButtonStyle"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_add_printer" />
</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/print_progress_fragment.xml b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
index 3b010f813e06..890716051d55 100644
--- a/packages/PrintSpooler/res/layout/print_progress_fragment.xml
+++ b/packages/PrintSpooler/res/layout/print_progress_fragment.xml
@@ -15,34 +15,39 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<ImageView
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dp"
+ android:src="@*android:drawable/ic_print"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="?android:colorForeground"
+ android:importantForAccessibility="no" />
+
+ <TextView
+ android:id="@+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@drawable/ic_grayedout_printer"
- android:contentDescription="@null">
- </ImageView>
+ android:layout_marginBottom="16dp"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_preparing_preview" />
<ProgressBar
- android:layout_width="fill_parent"
+ android:layout_width="300dp"
android:layout_height="wrap_content"
android:indeterminate="true"
- style="?android:attr/progressBarStyleHorizontal">
- </ProgressBar>
-
- <TextView
- android:id="@+id/message"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceLargeInverse"
- android:text="@string/print_preparing_preview">
- </TextView>
+ android:importantForAccessibility="no"
+ style="?android:attr/progressBarStyleHorizontal" />
</LinearLayout>
diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml
index 91beff6993d2..681924b334d0 100644
--- a/packages/PrintSpooler/res/layout/select_printer_activity.xml
+++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml
@@ -31,45 +31,48 @@
android:visibility="gone">
<LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
android:gravity="center"
android:orientation="vertical">
<ImageView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dip"
- android:src="@*android:drawable/ic_grayedout_printer"
- android:importantForAccessibility="no">
- </ImageView>
+ android:layout_width="120dp"
+ android:layout_height="110dp"
+ android:layout_marginBottom="12dp"
+ android:src="@*android:drawable/ic_print"
+ android:scaleType="fitEnd"
+ android:alpha="0.1"
+ android:tint="?android:colorForeground"
+ android:importantForAccessibility="no" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:gravity="center"
android:textAppearance="?android:attr/textAppearanceLarge"
android:textColor="?android:attr/textColorSecondary"
- android:text="@string/print_searching_for_printers">
- </TextView>
+ android:text="@string/print_searching_for_printers" />
<ProgressBar
android:id="@+id/progress_bar"
- android:layout_width="fill_parent"
+ android:layout_width="300dp"
android:layout_height="wrap_content"
android:indeterminate="true"
- style="?android:attr/progressBarStyleHorizontal">
- </ProgressBar>
+ android:importantForAccessibility="no"
+ style="?android:attr/progressBarStyleHorizontal" />
<Button
- android:id="@+id/button"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="?android:attr/buttonBarButtonStyle"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:text="@string/print_add_printer"
- android:textAllCaps="true" />
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="?android:attr/borderlessButtonStyle"
+ android:textColor="?android:attr/textColorSecondary"
+ android:text="@string/print_add_printer" />
</LinearLayout>
diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml
index 68bc6f277ad2..cb9e8866a396 100644
--- a/packages/PrintSpooler/res/values/colors.xml
+++ b/packages/PrintSpooler/res/values/colors.xml
@@ -18,8 +18,6 @@
<color name="print_preview_scrim_color">#99000000</color>
- <color name="print_preview_background_color">#F2F1F2</color>
-
<color name="unselected_page_background_color">#C0C0C0</color>
<color name="material_grey_500">#ffa3a3a3</color>
diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml
index a968ffa9ed1d..844e9c9f0030 100644
--- a/packages/PrintSpooler/res/values/themes.xml
+++ b/packages/PrintSpooler/res/values/themes.xml
@@ -21,17 +21,14 @@
</style>
<style name="Theme.SelectPrinterActivity"
- parent="android:style/Theme.DeviceDefault.Light.DarkActionBar">
+ parent="android:style/Theme.DeviceDefault.Light">
<item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
</style>
- <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault">
+ <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault.Light">
<item name="android:windowIsTranslucent">true</item>
- <item name="android:windowBackground">@android:color/transparent</item>
- <item name="android:windowContentOverlay">@null</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
- <item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 9d737e0cd9d5..abdfad5bb27a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -166,7 +166,7 @@ final class NotificationController {
*/
private Action createCancelAction(PrintJobInfo printJob) {
return new Action.Builder(
- Icon.createWithResource(mContext, R.drawable.stat_notify_cancelling),
+ Icon.createWithResource(mContext, R.drawable.ic_clear),
mContext.getString(R.string.cancel), createCancelIntent(printJob)).build();
}
@@ -225,7 +225,7 @@ final class NotificationController {
private void createFailedNotification(PrintJobInfo printJob) {
Action.Builder restartActionBuilder = new Action.Builder(
- Icon.createWithResource(mContext, R.drawable.ic_restart),
+ Icon.createWithResource(mContext, com.android.internal.R.drawable.ic_restart),
mContext.getString(R.string.restart), createRestartIntent(printJob.getId()));
createNotification(printJob, createCancelAction(printJob), restartActionBuilder.build());
@@ -317,7 +317,7 @@ final class NotificationController {
if (!printJob.isCancelling()) {
return com.android.internal.R.drawable.ic_print;
} else {
- return R.drawable.stat_notify_cancelling;
+ return R.drawable.ic_clear;
}
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 06fbf9f6e1c1..59f272ff70f6 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -297,7 +297,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
+ " cannot be null");
}
- mCallingPackageName = extras.getString(DocumentsContract.EXTRA_PACKAGE_NAME);
+ mCallingPackageName = extras.getString(Intent.EXTRA_PACKAGE_NAME);
if (savedInstanceState == null) {
MetricsLogger.action(this, MetricsEvent.PRINT_PREVIEW, mCallingPackageName);
@@ -715,7 +715,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, info.getName());
- intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, mCallingPackageName);
try {
startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE);
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 2488f55ad6bc..50d9c17bec23 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -398,8 +398,8 @@
<string name="enabled_by_admin" msgid="5302986023578399263">"Activada per l\'administrador"</string>
<string name="disabled_by_admin" msgid="8505398946020816620">"Desactivada per l\'administrador"</string>
<string name="disabled" msgid="9206776641295849915">"Desactivat"</string>
- <string name="external_source_trusted" msgid="2707996266575928037">"Permeses"</string>
- <string name="external_source_untrusted" msgid="2677442511837596726">"No permeses"</string>
+ <string name="external_source_trusted" msgid="2707996266575928037">"Amb permís"</string>
+ <string name="external_source_untrusted" msgid="2677442511837596726">"Sense permís"</string>
<string name="install_other_apps" msgid="6986686991775883017">"Instal·lar aplicacions desconegudes"</string>
<string name="home" msgid="3256884684164448244">"Pàgina d\'inici de configuració"</string>
<string-array name="battery_labels">
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 3e2afed2d11f..ca6fe8829406 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -320,7 +320,7 @@
<string name="show_notification_channel_warnings_summary" msgid="5536803251863694895">"अनुप्रयोगले कुनै मान्य च्यानल बिना सूचना पोस्ट गर्दा स्क्रिनमा चेतावनी देखाउँछ"</string>
<string name="force_allow_on_external" msgid="3215759785081916381">"बाह्यमा बल प्रयोगको अनुमति प्राप्त अनुप्रयोगहरू"</string>
<string name="force_allow_on_external_summary" msgid="3640752408258034689">"म्यानिफेेस्टका मानहरूको ख्याल नगरी कुनै पनि अनुप्रयोगलाई बाह्य भण्डारणमा लेख्न सकिने खाले बनाउँछ"</string>
- <string name="force_resizable_activities" msgid="8615764378147824985">"गतिविधिहरू रिसाइज गर्नको लागि बाध्य गर्नुहोस्"</string>
+ <string name="force_resizable_activities" msgid="8615764378147824985">"आकार बदल्न योग्य हुने बनाउन गतिविधिहरूलाई बाध्यात्मक बनाउनुहोस्।"</string>
<string name="force_resizable_activities_summary" msgid="6667493494706124459">"म्यानिफेेस्ट मानहरूको ख्याल नगरी, बहु-विन्डोको लागि सबै रिसाइज गर्न सकिने गतिविधिहरू बनाउनुहोस्।"</string>
<string name="enable_freeform_support" msgid="1461893351278940416">"फ्रिफर्म विन्डोहरू सक्रिय गर्नुहोस्"</string>
<string name="enable_freeform_support_summary" msgid="8247310463288834487">"प्रयोगात्मक फ्रिफर्म विन्डोहरूका लागि समर्थन सक्रिय गर्नुहोस्।"</string>
diff --git a/packages/SettingsLib/res/values-ta/arrays.xml b/packages/SettingsLib/res/values-ta/arrays.xml
index 7b69c7859802..54adf826076c 100644
--- a/packages/SettingsLib/res/values-ta/arrays.xml
+++ b/packages/SettingsLib/res/values-ta/arrays.xml
@@ -22,7 +22,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array name="wifi_status">
<item msgid="1922181315419294640"></item>
- <item msgid="8934131797783724664">"ஸ்கேன் செய்கிறது…"</item>
+ <item msgid="8934131797783724664">"தேடுகிறது..."</item>
<item msgid="8513729475867537913">"இணைக்கிறது..."</item>
<item msgid="515055375277271756">"அங்கீகரிக்கிறது..."</item>
<item msgid="1943354004029184381">"IP முகவரியைப் பெறுகிறது…"</item>
@@ -36,7 +36,7 @@
</string-array>
<string-array name="wifi_status_with_ssid">
<item msgid="7714855332363650812"></item>
- <item msgid="8878186979715711006">"ஸ்கேன் செய்கிறது…"</item>
+ <item msgid="8878186979715711006">"தேடுகிறது..."</item>
<item msgid="355508996603873860">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> இல் இணைக்கிறது…"</item>
<item msgid="554971459996405634">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> உடன் அங்கீகரிக்கிறது…"</item>
<item msgid="7928343808033020343">"<xliff:g id="NETWORK_NAME">%1$s</xliff:g> இலிருந்து IP முகவரியைப் பெறுகிறது…"</item>
@@ -180,30 +180,30 @@
</string-array>
<string-array name="window_animation_scale_entries">
<item msgid="8134156599370824081">"அனிமேஷனை முடக்கு"</item>
- <item msgid="6624864048416710414">"அனிமேஷன் அளவு .5x"</item>
- <item msgid="2219332261255416635">"அனிமேஷன் அளவு 1x"</item>
- <item msgid="3544428804137048509">"அனிமேஷன் அளவு 1.5x"</item>
- <item msgid="3110710404225974514">"அனிமேஷன் அளவு 2x"</item>
- <item msgid="4402738611528318731">"அனிமேஷன் அளவு 5x"</item>
- <item msgid="6189539267968330656">"அனிமேஷன் அளவு 10x"</item>
+ <item msgid="6624864048416710414">"அனிமேஷன் வேகம் .5x"</item>
+ <item msgid="2219332261255416635">"அனிமேஷன் வேகம் 1x"</item>
+ <item msgid="3544428804137048509">"அனிமேஷன் வேகம் 1.5x"</item>
+ <item msgid="3110710404225974514">"அனிமேஷன் வேகம் 2x"</item>
+ <item msgid="4402738611528318731">"அனிமேஷன் வேகம் 5x"</item>
+ <item msgid="6189539267968330656">"அனிமேஷன் வேகம் 10x"</item>
</string-array>
<string-array name="transition_animation_scale_entries">
<item msgid="8464255836173039442">"அனிமேஷனை முடக்கு"</item>
- <item msgid="3375781541913316411">"அனிமேஷன் அளவு .5x"</item>
- <item msgid="1991041427801869945">"அனிமேஷன் அளவு 1x"</item>
- <item msgid="4012689927622382874">"அனிமேஷன் அளவு 1.5x"</item>
- <item msgid="3289156759925947169">"அனிமேஷன் அளவு 2x"</item>
- <item msgid="7705857441213621835">"அனிமேஷன் அளவு 5x"</item>
- <item msgid="6660750935954853365">"அனிமேஷன் அளவு 10x"</item>
+ <item msgid="3375781541913316411">"அனிமேஷன் வேகம் .5x"</item>
+ <item msgid="1991041427801869945">"அனிமேஷன் வேகம் 1x"</item>
+ <item msgid="4012689927622382874">"அனிமேஷன் வேகம் 1.5x"</item>
+ <item msgid="3289156759925947169">"அனிமேஷன் வேகம் 2x"</item>
+ <item msgid="7705857441213621835">"அனிமேஷன் வேகம் 5x"</item>
+ <item msgid="6660750935954853365">"அனிமேஷன் வேகம் 10x"</item>
</string-array>
<string-array name="animator_duration_scale_entries">
<item msgid="6039901060648228241">"அனிமேஷனை முடக்கு"</item>
- <item msgid="1138649021950863198">"அனிமேஷன் அளவு .5x"</item>
- <item msgid="4394388961370833040">"அனிமேஷன் அளவு 1x"</item>
- <item msgid="8125427921655194973">"அனிமேஷன் அளவு 1.5x"</item>
- <item msgid="3334024790739189573">"அனிமேஷன் அளவு 2x"</item>
- <item msgid="3170120558236848008">"அனிமேஷன் அளவு 5x"</item>
- <item msgid="1069584980746680398">"அனிமேஷன் அளவு 10x"</item>
+ <item msgid="1138649021950863198">"அனிமேஷன் வேகம் .5x"</item>
+ <item msgid="4394388961370833040">"அனிமேஷன் வேகம் 1x"</item>
+ <item msgid="8125427921655194973">"அனிமேஷன் வேகம் 1.5x"</item>
+ <item msgid="3334024790739189573">"அனிமேஷன் வேகம் 2x"</item>
+ <item msgid="3170120558236848008">"அனிமேஷன் வேகம் 5x"</item>
+ <item msgid="1069584980746680398">"அனிமேஷன் வேகம் 10x"</item>
</string-array>
<string-array name="overlay_display_devices_entries">
<item msgid="1606809880904982133">"ஏதுமில்லை"</item>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 828e5427ec37..5f63fee33b43 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -306,8 +306,8 @@
<string name="track_frame_time" msgid="6094365083096851167">"சுயவிவர HWUI ரெண்டரிங்"</string>
<string name="enable_gpu_debug_layers" msgid="3848838293793255097">"GPU பிழைத்திருத்த லேயர்களை இயக்கு"</string>
<string name="enable_gpu_debug_layers_summary" msgid="8009136940671194940">"பிழைத்திருத்த ஆப்ஸிற்கு, GPU பிழைத்திருத்த லேயர்களை ஏற்றுவதற்கு அனுமதி"</string>
- <string name="window_animation_scale_title" msgid="6162587588166114700">"சாளர அனிமேஷன் அளவு"</string>
- <string name="transition_animation_scale_title" msgid="387527540523595875">"அனிமேஷன் மாற்றத்தின் அளவு"</string>
+ <string name="window_animation_scale_title" msgid="6162587588166114700">"சாளர அனிமேஷன் வேகம்"</string>
+ <string name="transition_animation_scale_title" msgid="387527540523595875">"அனிமேஷன் மாற்றத்தின் வேகம்"</string>
<string name="animator_duration_scale_title" msgid="3406722410819934083">"அனிமேட்டர் கால அளவு"</string>
<string name="overlay_display_devices_title" msgid="5364176287998398539">"இரண்டாம்நிலைக் காட்சிகளை உருவகப்படுத்து"</string>
<string name="debug_applications_category" msgid="4206913653849771549">"ஆப்ஸ்"</string>
@@ -358,7 +358,7 @@
<string name="picture_color_mode" msgid="4560755008730283695">"படத்தின் வண்ணப் பயன்முறை"</string>
<string name="picture_color_mode_desc" msgid="1141891467675548590">"sRGBஐப் பயன்படுத்தும்"</string>
<string name="daltonizer_mode_disabled" msgid="7482661936053801862">"முடக்கப்பட்டது"</string>
- <string name="daltonizer_mode_monochromacy" msgid="8485709880666106721">"மோனோகுரோமசி"</string>
+ <string name="daltonizer_mode_monochromacy" msgid="8485709880666106721">"ஒற்றை நிறத் தன்மை"</string>
<string name="daltonizer_mode_deuteranomaly" msgid="5475532989673586329">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
<string name="daltonizer_mode_protanomaly" msgid="8424148009038666065">"நிறம் அடையாளங்காண முடியாமை (சிவப்பு-பச்சை)"</string>
<string name="daltonizer_mode_tritanomaly" msgid="481725854987912389">"நிறம் அடையாளங்காண முடியாமை (நீலம்-மஞ்சள்)"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java
new file mode 100644
index 000000000000..6ac9d4e21f40
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomDialogPreferenceCompat.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.DialogPreference;
+import androidx.preference.PreferenceDialogFragmentCompat;
+
+public class CustomDialogPreferenceCompat extends DialogPreference {
+
+ private CustomPreferenceDialogFragment mFragment;
+ private DialogInterface.OnShowListener mOnShowListener;
+
+ public CustomDialogPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public CustomDialogPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomDialogPreferenceCompat(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomDialogPreferenceCompat(Context context) {
+ super(context);
+ }
+
+ public boolean isDialogOpen() {
+ return getDialog() != null && getDialog().isShowing();
+ }
+
+ public Dialog getDialog() {
+ return mFragment != null ? mFragment.getDialog() : null;
+ }
+
+ public void setOnShowListener(DialogInterface.OnShowListener listner) {
+ mOnShowListener = listner;
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected void onClick(DialogInterface dialog, int which) {
+ }
+
+ protected void onBindDialogView(View view) {
+ }
+
+ private void setFragment(CustomPreferenceDialogFragment fragment) {
+ mFragment = fragment;
+ }
+
+ private DialogInterface.OnShowListener getOnShowListener() {
+ return mOnShowListener;
+ }
+
+ public static class CustomPreferenceDialogFragment extends PreferenceDialogFragmentCompat {
+
+ public static CustomPreferenceDialogFragment newInstance(String key) {
+ final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ private CustomDialogPreferenceCompat getCustomizablePreference() {
+ return (CustomDialogPreferenceCompat) getPreference();
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ getCustomizablePreference().setFragment(this);
+ getCustomizablePreference().onPrepareDialogBuilder(builder, this);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ getCustomizablePreference().onDialogClosed(positiveResult);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ getCustomizablePreference().onBindDialogView(view);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Dialog dialog = super.onCreateDialog(savedInstanceState);
+ dialog.setOnShowListener(getCustomizablePreference().getOnShowListener());
+ return dialog;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+ getCustomizablePreference().onClick(dialog, which);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java
new file mode 100644
index 000000000000..6ddc89af03ad
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/CustomEditTextPreferenceCompat.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib;
+
+import static android.text.InputType.TYPE_CLASS_TEXT;
+import static android.text.InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+import androidx.annotation.CallSuper;
+import androidx.appcompat.app.AlertDialog;
+import androidx.preference.EditTextPreference;
+import androidx.preference.EditTextPreferenceDialogFragmentCompat;
+
+public class CustomEditTextPreferenceCompat extends EditTextPreference {
+
+ private CustomPreferenceDialogFragment mFragment;
+
+ public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomEditTextPreferenceCompat(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomEditTextPreferenceCompat(Context context) {
+ super(context);
+ }
+
+ public EditText getEditText() {
+ if (mFragment != null) {
+ final Dialog dialog = mFragment.getDialog();
+ if (dialog != null) {
+ return (EditText) dialog.findViewById(android.R.id.edit);
+ }
+ }
+ return null;
+ }
+
+ public boolean isDialogOpen() {
+ return getDialog() != null && getDialog().isShowing();
+ }
+
+ public Dialog getDialog() {
+ return mFragment != null ? mFragment.getDialog() : null;
+ }
+
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
+ DialogInterface.OnClickListener listener) {
+ }
+
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ protected void onClick(DialogInterface dialog, int which) {
+ }
+
+ @CallSuper
+ protected void onBindDialogView(View view) {
+ final EditText editText = view.findViewById(android.R.id.edit);
+ if (editText != null) {
+ editText.setInputType(TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_CAP_SENTENCES);
+ editText.requestFocus();
+ }
+ }
+
+ private void setFragment(CustomPreferenceDialogFragment fragment) {
+ mFragment = fragment;
+ }
+
+ public static class CustomPreferenceDialogFragment extends
+ EditTextPreferenceDialogFragmentCompat {
+
+ public static CustomPreferenceDialogFragment newInstance(String key) {
+ final CustomPreferenceDialogFragment fragment = new CustomPreferenceDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+
+ private CustomEditTextPreferenceCompat getCustomizablePreference() {
+ return (CustomEditTextPreferenceCompat) getPreference();
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+ getCustomizablePreference().onBindDialogView(view);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+ getCustomizablePreference().setFragment(this);
+ getCustomizablePreference().onPrepareDialogBuilder(builder, this);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+ getCustomizablePreference().onDialogClosed(positiveResult);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+ getCustomizablePreference().onClick(dialog, which);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index f9aa062b64da..2bd0b27063ea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -24,20 +24,22 @@ import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.Nullable;
import android.app.Activity;
-import androidx.lifecycle.LifecycleOwner;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.LifecycleOwner;
+
/**
* {@link Activity} that has hooks to observe activity lifecycle events.
*/
-public class ObservableActivity extends Activity implements LifecycleOwner {
+public class ObservableActivity extends FragmentActivity implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
index 972e062bb396..869f54f7d13a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableDialogFragment.java
@@ -22,14 +22,15 @@ import static androidx.lifecycle.Lifecycle.Event.ON_RESUME;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
-import android.app.DialogFragment;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.LifecycleOwner;
+
/**
* {@link DialogFragment} that has hooks to observe fragment lifecycle events.
*/
@@ -37,6 +38,10 @@ public class ObservableDialogFragment extends DialogFragment implements Lifecycl
protected final Lifecycle mLifecycle = new Lifecycle(this);
+ public Lifecycle getSettingsLifecycle() {
+ return mLifecycle;
+ }
+
@Override
public void onAttach(Context context) {
super.onAttach(context);
@@ -100,9 +105,4 @@ public class ObservableDialogFragment extends DialogFragment implements Lifecycl
}
return lifecycleHandled;
}
-
- @Override
- public Lifecycle getLifecycle() {
- return mLifecycle;
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
index 55597cc1247a..6ba930dcac96 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableFragment.java
@@ -24,19 +24,20 @@ import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.CallSuper;
-import android.app.Fragment;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LifecycleOwner;
+
public class ObservableFragment extends Fragment implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
index 904681c4aa3c..bd1e5a588968 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservablePreferenceFragment.java
@@ -24,24 +24,25 @@ import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
import android.annotation.CallSuper;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
import android.os.Bundle;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
/**
- * {@link PreferenceFragment} that has hooks to observe fragment lifecycle events.
+ * {@link PreferenceFragmentCompat} that has hooks to observe fragment lifecycle events.
*/
-public abstract class ObservablePreferenceFragment extends PreferenceFragment
+public abstract class ObservablePreferenceFragment extends PreferenceFragmentCompat
implements LifecycleOwner {
private final Lifecycle mLifecycle = new Lifecycle(this);
- public Lifecycle getLifecycle() {
+ public Lifecycle getSettingsLifecycle() {
return mLifecycle;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
index e5d97c9b4a7b..3c0f6fe8ccbb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java
@@ -256,11 +256,12 @@ public class DreamBackend {
}
}
- public void launchSettings(DreamInfo dreamInfo) {
+ public void launchSettings(Context uiContext, DreamInfo dreamInfo) {
logd("launchSettings(%s)", dreamInfo);
- if (dreamInfo == null || dreamInfo.settingsComponentName == null)
+ if (dreamInfo == null || dreamInfo.settingsComponentName == null) {
return;
- mContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
+ }
+ uiContext.startActivity(new Intent().setComponent(dreamInfo.settingsComponentName));
}
public void preview(DreamInfo dreamInfo) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java
new file mode 100644
index 000000000000..ad1368c7731d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManagerCompat.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.inputmethod;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.settingslib.R;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+public class InputMethodAndSubtypeEnablerManagerCompat implements
+ Preference.OnPreferenceChangeListener {
+
+ private final PreferenceFragmentCompat mFragment;
+
+ private boolean mHaveHardKeyboard;
+ private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
+ new HashMap<>();
+ private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
+ private InputMethodManager mImm;
+ // TODO: Change mInputMethodInfoList to Map
+ private List<InputMethodInfo> mInputMethodInfoList;
+ private final Collator mCollator = Collator.getInstance();
+
+ public InputMethodAndSubtypeEnablerManagerCompat(PreferenceFragmentCompat fragment) {
+ mFragment = fragment;
+ mImm = fragment.getContext().getSystemService(InputMethodManager.class);
+
+ mInputMethodInfoList = mImm.getInputMethodList();
+ }
+
+ public void init(PreferenceFragmentCompat fragment, String targetImi, PreferenceScreen root) {
+ final Configuration config = fragment.getResources().getConfiguration();
+ mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
+
+ for (final InputMethodInfo imi : mInputMethodInfoList) {
+ // Add subtype preferences of this IME when it is specified or no IME is specified.
+ if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
+ addInputMethodSubtypePreferences(fragment, imi, root);
+ }
+ }
+ }
+
+ public void refresh(Context context, PreferenceFragmentCompat fragment) {
+ // Refresh internal states in mInputMethodSettingValues to keep the latest
+ // "InputMethodInfo"s and "InputMethodSubtype"s
+ InputMethodSettingValuesWrapper
+ .getInstance(context).refreshAllInputMethodAndSubtypes();
+ InputMethodAndSubtypeUtilCompat.loadInputMethodSubtypeList(fragment,
+ context.getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
+ updateAutoSelectionPreferences();
+ }
+
+ public void save(Context context, PreferenceFragmentCompat fragment) {
+ InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(fragment,
+ context.getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard);
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference pref, final Object newValue) {
+ if (!(newValue instanceof Boolean)) {
+ return true; // Invoke default behavior.
+ }
+ final boolean isChecking = (Boolean) newValue;
+ for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
+ // An auto select subtype preference is changing.
+ if (mAutoSelectionPrefsMap.get(imiId) == pref) {
+ final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
+ autoSelectionPref.setChecked(isChecking);
+ // Enable or disable subtypes depending on the auto selection preference.
+ setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
+ return false;
+ }
+ }
+ // A subtype preference is changing.
+ if (pref instanceof InputMethodSubtypePreference) {
+ final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
+ subtypePref.setChecked(isChecking);
+ if (!subtypePref.isChecked()) {
+ // It takes care of the case where no subtypes are explicitly enabled then the auto
+ // selection preference is going to be checked.
+ updateAutoSelectionPreferences();
+ }
+ return false;
+ }
+ return true; // Invoke default behavior.
+ }
+
+ private void addInputMethodSubtypePreferences(PreferenceFragmentCompat fragment,
+ InputMethodInfo imi, final PreferenceScreen root) {
+ Context prefContext = fragment.getPreferenceManager().getContext();
+
+ final int subtypeCount = imi.getSubtypeCount();
+ if (subtypeCount <= 1) {
+ return;
+ }
+ final String imiId = imi.getId();
+ final PreferenceCategory keyboardSettingsCategory =
+ new PreferenceCategory(prefContext);
+ root.addPreference(keyboardSettingsCategory);
+ final PackageManager pm = prefContext.getPackageManager();
+ final CharSequence label = imi.loadLabel(pm);
+
+ keyboardSettingsCategory.setTitle(label);
+ keyboardSettingsCategory.setKey(imiId);
+ // TODO: Use toggle Preference if images are ready.
+ final TwoStatePreference autoSelectionPref =
+ new SwitchWithNoTextPreference(prefContext);
+ mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
+ keyboardSettingsCategory.addPreference(autoSelectionPref);
+ autoSelectionPref.setOnPreferenceChangeListener(this);
+
+ final PreferenceCategory activeInputMethodsCategory =
+ new PreferenceCategory(prefContext);
+ activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
+ root.addPreference(activeInputMethodsCategory);
+
+ CharSequence autoSubtypeLabel = null;
+ final ArrayList<Preference> subtypePreferences = new ArrayList<>();
+ for (int index = 0; index < subtypeCount; ++index) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(index);
+ if (subtype.overridesImplicitlyEnabledSubtype()) {
+ if (autoSubtypeLabel == null) {
+ autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
+ subtype, prefContext, imi);
+ }
+ } else {
+ final Preference subtypePref = new InputMethodSubtypePreference(
+ prefContext, subtype, imi);
+ subtypePreferences.add(subtypePref);
+ }
+ }
+ subtypePreferences.sort((lhs, rhs) -> {
+ if (lhs instanceof InputMethodSubtypePreference) {
+ return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
+ }
+ return lhs.compareTo(rhs);
+ });
+ for (final Preference pref : subtypePreferences) {
+ activeInputMethodsCategory.addPreference(pref);
+ pref.setOnPreferenceChangeListener(this);
+ InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
+ }
+ mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
+ if (TextUtils.isEmpty(autoSubtypeLabel)) {
+ autoSelectionPref.setTitle(
+ R.string.use_system_language_to_select_input_method_subtypes);
+ } else {
+ autoSelectionPref.setTitle(autoSubtypeLabel);
+ }
+ }
+
+ private boolean isNoSubtypesExplicitlySelected(final String imiId) {
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void setAutoSelectionSubtypesEnabled(final String imiId,
+ final boolean autoSelectionEnabled) {
+ final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
+ if (autoSelectionPref == null) {
+ return;
+ }
+ autoSelectionPref.setChecked(autoSelectionEnabled);
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ for (final Preference pref : subtypePrefs) {
+ if (pref instanceof TwoStatePreference) {
+ // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
+ // implicitly checked subtypes. In case of false, all subtype prefs need to be
+ // enabled.
+ pref.setEnabled(!autoSelectionEnabled);
+ if (autoSelectionEnabled) {
+ ((TwoStatePreference) pref).setChecked(false);
+ }
+ }
+ }
+ if (autoSelectionEnabled) {
+ InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(
+ mFragment, mFragment.getContext().getContentResolver(),
+ mInputMethodInfoList, mHaveHardKeyboard);
+ updateImplicitlyEnabledSubtypes(imiId);
+ }
+ }
+
+ private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
+ // When targetImiId is null, apply to all subtypes of all IMEs
+ for (final InputMethodInfo imi : mInputMethodInfoList) {
+ final String imiId = imi.getId();
+ final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
+ // No need to update implicitly enabled subtypes when the user has unchecked the
+ // "subtype auto selection".
+ if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
+ continue;
+ }
+ if (imiId.equals(targetImiId) || targetImiId == null) {
+ updateImplicitlyEnabledSubtypesOf(imi);
+ }
+ }
+ }
+
+ private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
+ final String imiId = imi.getId();
+ final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
+ final List<InputMethodSubtype> implicitlyEnabledSubtypes =
+ mImm.getEnabledInputMethodSubtypeList(imi, true);
+ if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
+ return;
+ }
+ for (final Preference pref : subtypePrefs) {
+ if (!(pref instanceof TwoStatePreference)) {
+ continue;
+ }
+ final TwoStatePreference subtypePref = (TwoStatePreference) pref;
+ subtypePref.setChecked(false);
+ for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
+ final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
+ if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
+ subtypePref.setChecked(true);
+ break;
+ }
+ }
+ }
+ }
+
+ private void updateAutoSelectionPreferences() {
+ for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
+ setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
+ }
+ updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
new file mode 100644
index 000000000000..9ad2adbcc432
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -0,0 +1,436 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.settingslib.inputmethod;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.icu.text.ListFormatter;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.app.LocaleHelper;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.TwoStatePreference;
+
+// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
+public class InputMethodAndSubtypeUtilCompat {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = "InputMethdAndSubtypeUtlCompat";
+
+ private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
+ private static final char INPUT_METHOD_SEPARATER = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
+ private static final int NOT_A_SUBTYPE_ID = -1;
+
+ private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
+ = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
+
+ private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
+ = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
+
+ // InputMethods and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ public static String buildInputMethodsAndSubtypesString(
+ final HashMap<String, HashSet<String>> imeToSubtypesMap) {
+ final StringBuilder builder = new StringBuilder();
+ for (final String imi : imeToSubtypesMap.keySet()) {
+ if (builder.length() > 0) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ }
+ final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
+ builder.append(imi);
+ for (final String subtypeId : subtypeIdSet) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+ }
+ }
+ return builder.toString();
+ }
+
+ private static String buildInputMethodsString(final HashSet<String> imiList) {
+ final StringBuilder builder = new StringBuilder();
+ for (final String imi : imiList) {
+ if (builder.length() > 0) {
+ builder.append(INPUT_METHOD_SEPARATER);
+ }
+ builder.append(imi);
+ }
+ return builder.toString();
+ }
+
+ private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
+ try {
+ return Settings.Secure.getInt(resolver,
+ Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
+ } catch (SettingNotFoundException e) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ }
+
+ private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
+ return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
+ }
+
+ private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
+ Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
+ }
+
+ // Needs to modify InputMethodManageService if you want to change the format of saved string.
+ static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
+ ContentResolver resolver) {
+ final String enabledInputMethodsStr = Settings.Secure.getString(
+ resolver, Settings.Secure.ENABLED_INPUT_METHODS);
+ if (DEBUG) {
+ Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
+ }
+ return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
+ }
+
+ public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+ final String inputMethodsAndSubtypesString) {
+ final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
+ if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
+ return subtypesMap;
+ }
+ sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
+ while (sStringInputMethodSplitter.hasNext()) {
+ final String nextImsStr = sStringInputMethodSplitter.next();
+ sStringInputMethodSubtypeSplitter.setString(nextImsStr);
+ if (sStringInputMethodSubtypeSplitter.hasNext()) {
+ final HashSet<String> subtypeIdSet = new HashSet<>();
+ // The first element is {@link InputMethodInfoId}.
+ final String imiId = sStringInputMethodSubtypeSplitter.next();
+ while (sStringInputMethodSubtypeSplitter.hasNext()) {
+ subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
+ }
+ subtypesMap.put(imiId, subtypeIdSet);
+ }
+ }
+ return subtypesMap;
+ }
+
+ private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
+ HashSet<String> set = new HashSet<>();
+ String disabledIMEsStr = Settings.Secure.getString(
+ resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
+ if (TextUtils.isEmpty(disabledIMEsStr)) {
+ return set;
+ }
+ sStringInputMethodSplitter.setString(disabledIMEsStr);
+ while(sStringInputMethodSplitter.hasNext()) {
+ set.add(sStringInputMethodSplitter.next());
+ }
+ return set;
+ }
+
+ public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
+ ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
+ boolean hasHardKeyboard) {
+ String currentInputMethodId = Settings.Secure.getString(resolver,
+ Settings.Secure.DEFAULT_INPUT_METHOD);
+ final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
+ final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+ final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
+
+ boolean needsToResetSelectedSubtype = false;
+ for (final InputMethodInfo imi : inputMethodInfos) {
+ final String imiId = imi.getId();
+ final Preference pref = context.findPreference(imiId);
+ if (pref == null) {
+ continue;
+ }
+ // In the choose input method screen or in the subtype enabler screen,
+ // <code>pref</code> is an instance of TwoStatePreference.
+ final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
+ ((TwoStatePreference) pref).isChecked()
+ : enabledIMEsAndSubtypesMap.containsKey(imiId);
+ final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
+ final boolean systemIme = imi.isSystem();
+ if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
+ context.getActivity()).isAlwaysCheckedIme(imi))
+ || isImeChecked) {
+ if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
+ // imiId has just been enabled
+ enabledIMEsAndSubtypesMap.put(imiId, new HashSet<>());
+ }
+ final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
+
+ boolean subtypePrefFound = false;
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
+ final TwoStatePreference subtypePref = (TwoStatePreference) context
+ .findPreference(imiId + subtypeHashCodeStr);
+ // In the Configure input method screen which does not have subtype preferences.
+ if (subtypePref == null) {
+ continue;
+ }
+ if (!subtypePrefFound) {
+ // Once subtype preference is found, subtypeSet needs to be cleared.
+ // Because of system change, hashCode value could have been changed.
+ subtypesSet.clear();
+ // If selected subtype preference is disabled, needs to reset.
+ needsToResetSelectedSubtype = true;
+ subtypePrefFound = true;
+ }
+ // Checking <code>subtypePref.isEnabled()</code> is insufficient to determine
+ // whether the user manually enabled this subtype or not. Implicitly-enabled
+ // subtypes are also checked just as an indicator to users. We also need to
+ // check <code>subtypePref.isEnabled()</code> so that only manually enabled
+ // subtypes can be saved here.
+ if (subtypePref.isEnabled() && subtypePref.isChecked()) {
+ subtypesSet.add(subtypeHashCodeStr);
+ if (isCurrentInputMethod) {
+ if (selectedInputMethodSubtype == subtype.hashCode()) {
+ // Selected subtype is still enabled, there is no need to reset
+ // selected subtype.
+ needsToResetSelectedSubtype = false;
+ }
+ }
+ } else {
+ subtypesSet.remove(subtypeHashCodeStr);
+ }
+ }
+ } else {
+ enabledIMEsAndSubtypesMap.remove(imiId);
+ if (isCurrentInputMethod) {
+ // We are processing the current input method, but found that it's not enabled.
+ // This means that the current input method has been uninstalled.
+ // If currentInputMethod is already uninstalled, InputMethodManagerService will
+ // find the applicable IME from the history and the system locale.
+ if (DEBUG) {
+ Log.d(TAG, "Current IME was uninstalled or disabled.");
+ }
+ currentInputMethodId = null;
+ }
+ }
+ // If it's a disabled system ime, add it to the disabled list so that it
+ // doesn't get enabled automatically on any changes to the package list
+ if (systemIme && hasHardKeyboard) {
+ if (disabledSystemIMEs.contains(imiId)) {
+ if (isImeChecked) {
+ disabledSystemIMEs.remove(imiId);
+ }
+ } else {
+ if (!isImeChecked) {
+ disabledSystemIMEs.add(imiId);
+ }
+ }
+ }
+ }
+
+ final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
+ enabledIMEsAndSubtypesMap);
+ final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
+ if (DEBUG) {
+ Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
+ Log.d(TAG, "--- Save disabled system inputmethod settings. :"
+ + disabledSystemIMEsString);
+ Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
+ Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
+ Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
+ }
+
+ // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
+ // selected. And if the selected subtype of the current input method was disabled,
+ // We should reset the selected input method's subtype.
+ if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
+ if (DEBUG) {
+ Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
+ }
+ putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
+ }
+
+ Settings.Secure.putString(resolver,
+ Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
+ if (disabledSystemIMEsString.length() > 0) {
+ Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
+ disabledSystemIMEsString);
+ }
+ // If the current input method is unset, InputMethodManagerService will find the applicable
+ // IME from the history and the system locale.
+ Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
+ currentInputMethodId != null ? currentInputMethodId : "");
+ }
+
+ public static void loadInputMethodSubtypeList(final PreferenceFragmentCompat context,
+ final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
+ final Map<String, List<Preference>> inputMethodPrefsMap) {
+ final HashMap<String, HashSet<String>> enabledSubtypes =
+ getEnabledInputMethodsAndSubtypeList(resolver);
+
+ for (final InputMethodInfo imi : inputMethodInfos) {
+ final String imiId = imi.getId();
+ final Preference pref = context.findPreference(imiId);
+ if (pref instanceof TwoStatePreference) {
+ final TwoStatePreference subtypePref = (TwoStatePreference) pref;
+ final boolean isEnabled = enabledSubtypes.containsKey(imiId);
+ subtypePref.setChecked(isEnabled);
+ if (inputMethodPrefsMap != null) {
+ for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
+ childPref.setEnabled(isEnabled);
+ }
+ }
+ setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
+ }
+ }
+ updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
+ }
+
+ private static void setSubtypesPreferenceEnabled(final PreferenceFragmentCompat context,
+ final List<InputMethodInfo> inputMethodProperties, final String id,
+ final boolean enabled) {
+ final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
+ for (final InputMethodInfo imi : inputMethodProperties) {
+ if (id.equals(imi.getId())) {
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + subtype.hashCode());
+ if (pref != null) {
+ pref.setEnabled(enabled);
+ }
+ }
+ }
+ }
+ }
+
+ private static void updateSubtypesPreferenceChecked(final PreferenceFragmentCompat context,
+ final List<InputMethodInfo> inputMethodProperties,
+ final HashMap<String, HashSet<String>> enabledSubtypes) {
+ final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
+ for (final InputMethodInfo imi : inputMethodProperties) {
+ final String id = imi.getId();
+ if (!enabledSubtypes.containsKey(id)) {
+ // There is no need to enable/disable subtypes of disabled IMEs.
+ continue;
+ }
+ final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ final String hashCode = String.valueOf(subtype.hashCode());
+ if (DEBUG) {
+ Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
+ + enabledSubtypesSet.contains(hashCode));
+ }
+ final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
+ .findPreference(id + hashCode);
+ if (pref != null) {
+ pref.setChecked(enabledSubtypesSet.contains(hashCode));
+ }
+ }
+ }
+ }
+
+ public static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
+ final String key = pref.getKey();
+ if (pref.isPersistent() || key == null) {
+ return;
+ }
+ final SharedPreferences prefs = pref.getSharedPreferences();
+ if (prefs != null && prefs.contains(key)) {
+ prefs.edit().remove(key).apply();
+ }
+ }
+
+ @NonNull
+ public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
+ @NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
+ if (subtype == null) {
+ return "";
+ }
+ final Locale locale = getDisplayLocale(context);
+ final CharSequence subtypeName = subtype.getDisplayName(context,
+ inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
+ .applicationInfo);
+ return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
+ }
+
+ @NonNull
+ public static String getSubtypeLocaleNameListAsSentence(
+ @NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
+ @NonNull final InputMethodInfo inputMethodInfo) {
+ if (subtypes.isEmpty()) {
+ return "";
+ }
+ final Locale locale = getDisplayLocale(context);
+ final int subtypeCount = subtypes.size();
+ final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
+ for (int i = 0; i < subtypeCount; i++) {
+ subtypeNames[i] = subtypes.get(i).getDisplayName(context,
+ inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
+ .applicationInfo);
+ }
+ return LocaleHelper.toSentenceCase(
+ ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
+ }
+
+ @NonNull
+ private static Locale getDisplayLocale(@Nullable final Context context) {
+ if (context == null) {
+ return Locale.getDefault();
+ }
+ if (context.getResources() == null) {
+ return Locale.getDefault();
+ }
+ final Configuration configuration = context.getResources().getConfiguration();
+ if (configuration == null) {
+ return Locale.getDefault();
+ }
+ final Locale configurationLocale = configuration.getLocales().get(0);
+ if (configurationLocale == null) {
+ return Locale.getDefault();
+ }
+ return configurationLocale;
+ }
+
+ public static boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi) {
+ if (imi.isAuxiliaryIme() || !imi.isSystem()) {
+ return false;
+ }
+ final int subtypeCount = imi.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ final InputMethodSubtype subtype = imi.getSubtypeAt(i);
+ if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
+ && subtype.isAsciiCapable()) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
new file mode 100644
index 000000000000..d7c14ad66baa
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/license/LicenseHtmlLoaderCompat.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.license;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * LicenseHtmlLoader is a loader which loads a license html file from default license xml files.
+ */
+public class LicenseHtmlLoaderCompat extends AsyncLoaderCompat<File> {
+ private static final String TAG = "LicenseHtmlLoaderCompat";
+
+ private static final String[] DEFAULT_LICENSE_XML_PATHS = {
+ "/system/etc/NOTICE.xml.gz",
+ "/vendor/etc/NOTICE.xml.gz",
+ "/odm/etc/NOTICE.xml.gz",
+ "/oem/etc/NOTICE.xml.gz"};
+ private static final String NOTICE_HTML_FILE_NAME = "NOTICE.html";
+
+ private Context mContext;
+
+ public LicenseHtmlLoaderCompat(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public File loadInBackground() {
+ return generateHtmlFromDefaultXmlFiles();
+ }
+
+ @Override
+ protected void onDiscardResult(File f) {
+ }
+
+ private File generateHtmlFromDefaultXmlFiles() {
+ final List<File> xmlFiles = getVaildXmlFiles();
+ if (xmlFiles.isEmpty()) {
+ Log.e(TAG, "No notice file exists.");
+ return null;
+ }
+
+ File cachedHtmlFile = getCachedHtmlFile();
+ if (!isCachedHtmlFileOutdated(xmlFiles, cachedHtmlFile)
+ || generateHtmlFile(xmlFiles, cachedHtmlFile)) {
+ return cachedHtmlFile;
+ }
+
+ return null;
+ }
+
+ @VisibleForTesting
+ List<File> getVaildXmlFiles() {
+ final List<File> xmlFiles = new ArrayList();
+ for (final String xmlPath : DEFAULT_LICENSE_XML_PATHS) {
+ File file = new File(xmlPath);
+ if (file.exists() && file.length() != 0) {
+ xmlFiles.add(file);
+ }
+ }
+ return xmlFiles;
+ }
+
+ @VisibleForTesting
+ File getCachedHtmlFile() {
+ return new File(mContext.getCacheDir(), NOTICE_HTML_FILE_NAME);
+ }
+
+ @VisibleForTesting
+ boolean isCachedHtmlFileOutdated(List<File> xmlFiles, File cachedHtmlFile) {
+ boolean outdated = true;
+ if (cachedHtmlFile.exists() && cachedHtmlFile.length() != 0) {
+ outdated = false;
+ for (File file : xmlFiles) {
+ if (cachedHtmlFile.lastModified() < file.lastModified()) {
+ outdated = true;
+ break;
+ }
+ }
+ }
+ return outdated;
+ }
+
+ @VisibleForTesting
+ boolean generateHtmlFile(List<File> xmlFiles, File htmlFile) {
+ return LicenseHtmlGeneratorFromXml.generateHtml(xmlFiles, htmlFile);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java
new file mode 100644
index 000000000000..3adbd4d01ca0
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/ChartDataLoaderCompat.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.net;
+
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStatsHistory.FIELD_RX_BYTES;
+import static android.net.NetworkStatsHistory.FIELD_TX_BYTES;
+import static android.text.format.DateUtils.HOUR_IN_MILLIS;
+
+import android.content.Context;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStatsHistory;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import com.android.settingslib.AppItem;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * Loader for historical chart data for both network and UID details.
+ */
+public class ChartDataLoaderCompat extends AsyncTaskLoader<ChartData> {
+ private static final String KEY_TEMPLATE = "template";
+ private static final String KEY_APP = "app";
+ private static final String KEY_FIELDS = "fields";
+
+ private final INetworkStatsSession mSession;
+ private final Bundle mArgs;
+
+ public static Bundle buildArgs(NetworkTemplate template, AppItem app) {
+ return buildArgs(template, app, FIELD_RX_BYTES | FIELD_TX_BYTES);
+ }
+
+ public static Bundle buildArgs(NetworkTemplate template, AppItem app, int fields) {
+ final Bundle args = new Bundle();
+ args.putParcelable(KEY_TEMPLATE, template);
+ args.putParcelable(KEY_APP, app);
+ args.putInt(KEY_FIELDS, fields);
+ return args;
+ }
+
+ public ChartDataLoaderCompat(Context context, INetworkStatsSession session, Bundle args) {
+ super(context);
+ mSession = session;
+ mArgs = args;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ @Override
+ public ChartData loadInBackground() {
+ final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+ final AppItem app = mArgs.getParcelable(KEY_APP);
+ final int fields = mArgs.getInt(KEY_FIELDS);
+
+ try {
+ return loadInBackground(template, app, fields);
+ } catch (RemoteException e) {
+ // since we can't do much without history, and we don't want to
+ // leave with half-baked UI, we bail hard.
+ throw new RuntimeException("problem reading network stats", e);
+ }
+ }
+
+ private ChartData loadInBackground(NetworkTemplate template, AppItem app, int fields)
+ throws RemoteException {
+ final ChartData data = new ChartData();
+ data.network = mSession.getHistoryForNetwork(template, fields);
+
+ if (app != null) {
+ // load stats for current uid and template
+ final int size = app.uids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = app.uids.keyAt(i);
+ data.detailDefault = collectHistoryForUid(
+ template, uid, SET_DEFAULT, data.detailDefault);
+ data.detailForeground = collectHistoryForUid(
+ template, uid, SET_FOREGROUND, data.detailForeground);
+ }
+
+ if (size > 0) {
+ data.detail = new NetworkStatsHistory(data.detailForeground.getBucketDuration());
+ data.detail.recordEntireHistory(data.detailDefault);
+ data.detail.recordEntireHistory(data.detailForeground);
+ } else {
+ data.detailDefault = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ data.detailForeground = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ data.detail = new NetworkStatsHistory(HOUR_IN_MILLIS);
+ }
+ }
+
+ return data;
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+
+ /**
+ * Collect {@link NetworkStatsHistory} for the requested UID, combining with
+ * an existing {@link NetworkStatsHistory} if provided.
+ */
+ private NetworkStatsHistory collectHistoryForUid(
+ NetworkTemplate template, int uid, int set, NetworkStatsHistory existing)
+ throws RemoteException {
+ final NetworkStatsHistory history = mSession.getHistoryForUid(
+ template, uid, set, TAG_NONE, FIELD_RX_BYTES | FIELD_TX_BYTES);
+
+ if (existing != null) {
+ existing.recordEntireHistory(history);
+ return existing;
+ } else {
+ return history;
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java
new file mode 100644
index 000000000000..c311337d6cf5
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/net/SummaryForAllUidLoaderCompat.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.net;
+
+import android.content.Context;
+import android.net.INetworkStatsSession;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+public class SummaryForAllUidLoaderCompat extends AsyncTaskLoader<NetworkStats> {
+ private static final String KEY_TEMPLATE = "template";
+ private static final String KEY_START = "start";
+ private static final String KEY_END = "end";
+
+ private final INetworkStatsSession mSession;
+ private final Bundle mArgs;
+
+ public static Bundle buildArgs(NetworkTemplate template, long start, long end) {
+ final Bundle args = new Bundle();
+ args.putParcelable(KEY_TEMPLATE, template);
+ args.putLong(KEY_START, start);
+ args.putLong(KEY_END, end);
+ return args;
+ }
+
+ public SummaryForAllUidLoaderCompat(Context context, INetworkStatsSession session,
+ Bundle args) {
+ super(context);
+ mSession = session;
+ mArgs = args;
+ }
+
+ @Override
+ protected void onStartLoading() {
+ super.onStartLoading();
+ forceLoad();
+ }
+
+ @Override
+ public NetworkStats loadInBackground() {
+ final NetworkTemplate template = mArgs.getParcelable(KEY_TEMPLATE);
+ final long start = mArgs.getLong(KEY_START);
+ final long end = mArgs.getLong(KEY_END);
+
+ try {
+ return mSession.getSummaryForAllUid(template, start, end, false);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ super.onStopLoading();
+ cancelLoad();
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+ cancelLoad();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java
new file mode 100644
index 000000000000..179121739896
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompat.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.suggestions;
+
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.List;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.OnLifecycleEvent;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.Loader;
+
+/**
+ * Manages IPC communication to SettingsIntelligence for suggestion related services.
+ */
+public class SuggestionControllerMixinCompat implements
+ SuggestionController.ServiceConnectionListener, androidx.lifecycle.LifecycleObserver,
+ LoaderManager.LoaderCallbacks<List<Suggestion>> {
+
+ public interface SuggestionControllerHost {
+ /**
+ * Called when suggestion data fetching is ready.
+ */
+ void onSuggestionReady(List<Suggestion> data);
+
+ /**
+ * Returns {@link LoaderManager} associated with the host. If host is not attached to
+ * activity then return null.
+ */
+ @Nullable
+ LoaderManager getLoaderManager();
+ }
+
+ private static final String TAG = "SuggestionCtrlMixin";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final SuggestionController mSuggestionController;
+ private final SuggestionControllerHost mHost;
+
+ private boolean mSuggestionLoaded;
+
+ public SuggestionControllerMixinCompat(Context context, SuggestionControllerHost host,
+ Lifecycle lifecycle, ComponentName componentName) {
+ mContext = context.getApplicationContext();
+ mHost = host;
+ mSuggestionController = new SuggestionController(mContext, componentName,
+ this /* serviceConnectionListener */);
+ if (lifecycle != null) {
+ lifecycle.addObserver(this);
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ public void onStart() {
+ if (DEBUG) {
+ Log.d(TAG, "SuggestionController started");
+ }
+ mSuggestionController.start();
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ public void onStop() {
+ if (DEBUG) {
+ Log.d(TAG, "SuggestionController stopped.");
+ }
+ mSuggestionController.stop();
+ }
+
+ @Override
+ public void onServiceConnected() {
+ final LoaderManager loaderManager = mHost.getLoaderManager();
+ if (loaderManager != null) {
+ loaderManager.restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+ null /* args */, this /* callback */);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ if (DEBUG) {
+ Log.d(TAG, "SuggestionService disconnected");
+ }
+ final LoaderManager loaderManager = mHost.getLoaderManager();
+ if (loaderManager != null) {
+ loaderManager.destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
+ }
+ }
+
+ @Override
+ public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
+ if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) {
+ mSuggestionLoaded = false;
+ return new SuggestionLoaderCompat(mContext, mSuggestionController);
+ }
+ throw new IllegalArgumentException("This loader id is not supported " + id);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> data) {
+ mSuggestionLoaded = true;
+ mHost.onSuggestionReady(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<List<Suggestion>> loader) {
+ mSuggestionLoaded = false;
+ }
+
+ public boolean isSuggestionLoaded() {
+ return mSuggestionLoaded;
+ }
+
+ public void dismissSuggestion(Suggestion suggestion) {
+ mSuggestionController.dismissSuggestions(suggestion);
+ }
+
+ public void launchSuggestion(Suggestion suggestion) {
+ mSuggestionController.launchSuggestion(suggestion);
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java
new file mode 100644
index 000000000000..066de19172de
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoaderCompat.java
@@ -0,0 +1,54 @@
+/*
+ * 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.settingslib.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import com.android.settingslib.utils.AsyncLoaderCompat;
+
+import java.util.List;
+
+public class SuggestionLoaderCompat extends AsyncLoaderCompat<List<Suggestion>> {
+
+ public static final int LOADER_ID_SUGGESTIONS = 42;
+ private static final String TAG = "SuggestionLoader";
+
+ private final SuggestionController mSuggestionController;
+
+ public SuggestionLoaderCompat(Context context, SuggestionController controller) {
+ super(context);
+ mSuggestionController = controller;
+ }
+
+ @Override
+ protected void onDiscardResult(List<Suggestion> result) {
+
+ }
+
+ @Override
+ public List<Suggestion> loadInBackground() {
+ final List<Suggestion> data = mSuggestionController.getSuggestions();
+ if (data == null) {
+ Log.d(TAG, "data is null");
+ } else {
+ Log.d(TAG, "data size " + data.size());
+ }
+ return data;
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java
new file mode 100644
index 000000000000..916d7e312631
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/utils/AsyncLoaderCompat.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.utils;
+
+import android.content.Context;
+
+import androidx.loader.content.AsyncTaskLoader;
+
+/**
+ * This class fills in some boilerplate for AsyncTaskLoader to actually load things.
+ *
+ * Subclasses need to implement {@link AsyncLoaderCompat#loadInBackground()} to perform the actual
+ * background task, and {@link AsyncLoaderCompat#onDiscardResult(T)} to clean up previously loaded
+ * results.
+ *
+ * This loader is based on the MailAsyncTaskLoader from the AOSP EmailUnified repo.
+ *
+ * @param <T> the data type to be loaded.
+ */
+public abstract class AsyncLoaderCompat<T> extends AsyncTaskLoader<T> {
+ private T mResult;
+
+ public AsyncLoaderCompat(final Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (mResult != null) {
+ deliverResult(mResult);
+ }
+
+ if (takeContentChanged() || mResult == null) {
+ forceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void deliverResult(final T data) {
+ if (isReset()) {
+ if (data != null) {
+ onDiscardResult(data);
+ }
+ return;
+ }
+
+ final T oldResult = mResult;
+ mResult = data;
+
+ if (isStarted()) {
+ super.deliverResult(data);
+ }
+
+ if (oldResult != null && oldResult != mResult) {
+ onDiscardResult(oldResult);
+ }
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ onStopLoading();
+
+ if (mResult != null) {
+ onDiscardResult(mResult);
+ }
+ mResult = null;
+ }
+
+ @Override
+ public void onCanceled(final T data) {
+ super.onCanceled(data);
+
+ if (data != null) {
+ onDiscardResult(data);
+ }
+ }
+
+ /**
+ * Called when discarding the load results so subclasses can take care of clean-up or
+ * recycling tasks. This is not called if the same result (by way of pointer equality) is
+ * returned again by a subsequent call to loadInBackground, or if result is null.
+ *
+ * Note that this may be called concurrently with loadInBackground(), and in some circumstances
+ * may be called more than once for a given object.
+ *
+ * @param result The value returned from {@link AsyncLoaderCompat#loadInBackground()} which
+ * is to be discarded.
+ */
+ protected abstract void onDiscardResult(T result);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
new file mode 100644
index 000000000000..260ac838cb32
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/FooterPreferenceMixinCompat.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.widget;
+
+import android.content.Context;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.SetPreferenceScreen;
+
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+public class FooterPreferenceMixinCompat implements LifecycleObserver, SetPreferenceScreen {
+
+ private final PreferenceFragmentCompat mFragment;
+ private FooterPreference mFooterPreference;
+
+ public FooterPreferenceMixinCompat(PreferenceFragmentCompat fragment, Lifecycle lifecycle) {
+ mFragment = fragment;
+ lifecycle.addObserver(this);
+ }
+
+ @Override
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ if (mFooterPreference != null) {
+ preferenceScreen.addPreference(mFooterPreference);
+ }
+ }
+
+ /**
+ * Creates a new {@link FooterPreference}.
+ */
+ public FooterPreference createFooterPreference() {
+ final PreferenceScreen screen = mFragment.getPreferenceScreen();
+ if (mFooterPreference != null && screen != null) {
+ screen.removePreference(mFooterPreference);
+ }
+ mFooterPreference = new FooterPreference(getPrefContext());
+
+ if (screen != null) {
+ screen.addPreference(mFooterPreference);
+ }
+ return mFooterPreference;
+ }
+
+ /**
+ * Returns an UI context with theme properly set for new Preference objects.
+ */
+ private Context getPrefContext() {
+ return mFragment.getPreferenceManager().getContext();
+ }
+
+ public boolean hasFooter() {
+ return mFooterPreference != null;
+ }
+}
+
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java
new file mode 100644
index 000000000000..9ba996752f49
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/CustomEditTextPreferenceComaptTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.EditText;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+public class CustomEditTextPreferenceComaptTest {
+
+ @Mock
+ private View mView;
+
+ private TestPreference mPreference;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mPreference = new TestPreference(RuntimeEnvironment.application);
+ }
+
+ @Test
+ public void bindDialogView_shouldRequestFocus() {
+ final String testText = "";
+ final EditText editText = spy(new EditText(RuntimeEnvironment.application));
+ editText.setText(testText);
+ when(mView.findViewById(android.R.id.edit)).thenReturn(editText);
+
+ mPreference.onBindDialogView(mView);
+
+ verify(editText).requestFocus();
+ }
+
+ @Test
+ public void getEditText_noDialog_shouldNotCrash() {
+ ReflectionHelpers.setField(mPreference, "mFragment",
+ mock(CustomEditTextPreferenceCompat.CustomPreferenceDialogFragment.class));
+
+ mPreference.getEditText();
+
+ // no crash
+ }
+
+ private static class TestPreference extends CustomEditTextPreferenceCompat {
+ public TestPreference(Context context) {
+ super(context);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
index 52068e9842e5..28828a00cce6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/lifecycle/LifecycleTest.java
@@ -18,11 +18,12 @@ package com.android.settingslib.core.lifecycle;
import static androidx.lifecycle.Lifecycle.Event.ON_START;
import static com.google.common.truth.Truth.assertThat;
-import androidx.lifecycle.LifecycleOwner;
import android.content.Context;
+import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+import android.widget.LinearLayout;
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.events.OnAttach;
@@ -34,13 +35,15 @@ import com.android.settingslib.core.lifecycle.events.OnPrepareOptionsMenu;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.testutils.FragmentTestUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.android.controller.ActivityController;
-import org.robolectric.android.controller.FragmentController;
+
+import androidx.lifecycle.LifecycleOwner;
@RunWith(SettingsLibRobolectricTestRunner.class)
public class LifecycleTest {
@@ -64,7 +67,7 @@ public class LifecycleTest {
public TestFragment() {
mFragObserver = new TestObserver();
- getLifecycle().addObserver(mFragObserver);
+ getSettingsLifecycle().addObserver(mFragObserver);
}
}
@@ -74,9 +77,17 @@ public class LifecycleTest {
public TestActivity() {
mActObserver = new TestObserver();
- getLifecycle().addObserver(mActObserver);
+ getSettingsLifecycle().addObserver(mActObserver);
}
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
}
public static class TestObserver implements LifecycleObserver, OnAttach, OnStart, OnResume,
@@ -151,11 +162,9 @@ public class LifecycleTest {
@Test
public void runThroughActivityLifecycles_shouldObserveEverything() {
ActivityController<TestActivity> ac = Robolectric.buildActivity(TestActivity.class);
- TestActivity activity = ac.get();
+ TestActivity activity = ac.setup().get();
- ac.start();
assertThat(activity.mActObserver.mOnStartObserved).isTrue();
- ac.resume();
assertThat(activity.mActObserver.mOnResumeObserved).isTrue();
activity.onCreateOptionsMenu(null);
assertThat(activity.mActObserver.mOnCreateOptionsMenuObserved).isTrue();
@@ -173,50 +182,50 @@ public class LifecycleTest {
@Test
public void runThroughDialogFragmentLifecycles_shouldObserveEverything() {
- FragmentController<TestDialogFragment> fragmentController =
- Robolectric.buildFragment(TestDialogFragment.class);
- TestDialogFragment fragment = fragmentController.get();
+ final TestDialogFragment fragment = new TestDialogFragment();
+ FragmentTestUtils.startFragment(fragment);
- fragmentController.create().start().resume();
fragment.onCreateOptionsMenu(null, null);
fragment.onPrepareOptionsMenu(null);
fragment.onOptionsItemSelected(null);
- fragmentController.pause().stop().destroy();
+ assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+ fragment.onPause();
assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+ fragment.onStop();
assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+ fragment.onDestroy();
assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
}
@Test
public void runThroughFragmentLifecycles_shouldObserveEverything() {
- FragmentController<TestFragment> fragmentController =
- Robolectric.buildFragment(TestFragment.class);
- TestFragment fragment = fragmentController.get();
+ final TestFragment fragment = new TestFragment();
+ FragmentTestUtils.startFragment(fragment);
- fragmentController.create().start().resume();
fragment.onCreateOptionsMenu(null, null);
fragment.onPrepareOptionsMenu(null);
fragment.onOptionsItemSelected(null);
- fragmentController.pause().stop().destroy();
+ assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
+ assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachObserved).isTrue();
assertThat(fragment.mFragObserver.mOnAttachHasContext).isTrue();
assertThat(fragment.mFragObserver.mOnStartObserved).isTrue();
assertThat(fragment.mFragObserver.mOnResumeObserved).isTrue();
+ fragment.onPause();
assertThat(fragment.mFragObserver.mOnPauseObserved).isTrue();
+ fragment.onStop();
assertThat(fragment.mFragObserver.mOnStopObserved).isTrue();
+ fragment.onDestroy();
assertThat(fragment.mFragObserver.mOnDestroyObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnCreateOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnPrepareOptionsMenuObserved).isTrue();
- assertThat(fragment.mFragObserver.mOnOptionsItemSelectedObserved).isTrue();
}
@Test
@@ -237,17 +246,16 @@ public class LifecycleTest {
@Test
public void onOptionItemSelectedShortCircuitsIfAnObserverHandlesTheMenuItem() {
- FragmentController<TestFragment> fragmentController =
- Robolectric.buildFragment(TestFragment.class);
- TestFragment fragment = fragmentController.get();
- OptionItemAccepter accepter = new OptionItemAccepter();
+ final TestFragment fragment = new TestFragment();
+ FragmentTestUtils.startFragment(fragment);
+
+ final OptionItemAccepter accepter = new OptionItemAccepter();
fragment.getLifecycle().addObserver(accepter);
- fragmentController.create().start().resume();
+
fragment.onCreateOptionsMenu(null, null);
fragment.onPrepareOptionsMenu(null);
fragment.onOptionsItemSelected(null);
- fragmentController.pause().stop().destroy();
assertThat(accepter.wasCalled).isFalse();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
new file mode 100644
index 000000000000..ddadac105c18
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompatTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.inputmethod;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+
+@RunWith(RobolectricTestRunner.class)
+public class InputMethodAndSubtypeUtilCompatTest {
+
+ private static final HashSet<String> EMPTY_STRING_SET = new HashSet<>();
+
+ private static HashSet<String> asHashSet(String... strings) {
+ HashSet<String> hashSet = new HashSet<>();
+ for (String s : strings) {
+ hashSet.add(s);
+ }
+ return hashSet;
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_EmptyString() {
+ assertThat(InputMethodAndSubtypeUtilCompat.
+ parseInputMethodsAndSubtypesString("")).isEmpty();
+ assertThat(InputMethodAndSubtypeUtilCompat.
+ parseInputMethodsAndSubtypesString(null)).isEmpty();
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeNoSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString("ime0");
+ assertThat(r).containsExactly("ime0", EMPTY_STRING_SET);
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_MultipleImesNoSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString("ime0:ime1");
+ assertThat(r).containsExactly("ime0", EMPTY_STRING_SET, "ime1", EMPTY_STRING_SET);
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString("ime0;subtype0");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeDuplicateSameSubtypes() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype0");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0"));
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"));
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_MultiplePairsOfImeSubtype() {
+ assertThat(InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0:ime1;subtype1"))
+ .containsExactly("ime0", asHashSet("subtype0"), "ime1", asHashSet("subtype1"));
+ assertThat(InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1:ime1;subtype2"))
+ .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+ "ime1", asHashSet("subtype2"));
+ assertThat(InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1:ime1;subtype1;subtype2"))
+ .containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+ "ime1", asHashSet("subtype1", "subtype2"));
+
+ }
+
+ @Test
+ public void parseInputMethodsAndSubtypesString_MixedImeSubtypePairsAndImeNoSubtype() {
+ HashMap<String, HashSet<String>> r =
+ InputMethodAndSubtypeUtilCompat.parseInputMethodsAndSubtypesString(
+ "ime0;subtype0;subtype1:ime1;subtype1;subtype2:ime2");
+ assertThat(r).containsExactly("ime0", asHashSet("subtype0", "subtype1"),
+ "ime1", asHashSet("subtype1", "subtype2"),
+ "ime2", EMPTY_STRING_SET);
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_EmptyInput() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ assertThat(map).isEmpty();
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_SingleIme() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", new HashSet<>());
+ String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
+ assertThat(result).isEqualTo("ime0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_SingleImeSingleSubtype() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0"));
+ String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
+ assertThat(result).isEqualTo("ime0;subtype0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_SingleImeMultipleSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0", "subtype1"));
+ String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("ime0;subtype0;subtype1|ime0;subtype1;subtype0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_MultipleImesNoSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", EMPTY_STRING_SET);
+ map.put("ime1", EMPTY_STRING_SET);
+ String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("ime0:ime1|ime1:ime0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_MultipleImesWithAndWithoutSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0", "subtype1"));
+ map.put("ime1", EMPTY_STRING_SET);
+ String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("ime0;subtype0;subtype1:ime1|ime0;subtype1;subtype0:ime1"
+ + "|ime1:ime0;subtype0;subtype1|ime1:ime0;subtype1;subtype0");
+ }
+
+ @Test
+ public void buildInputMethodsAndSubtypesString_MultipleImesWithSubtypes() {
+ HashMap<String, HashSet<String>> map = new HashMap<>();
+ map.put("ime0", asHashSet("subtype0", "subtype1"));
+ map.put("ime1", asHashSet("subtype2", "subtype3"));
+ String result = InputMethodAndSubtypeUtilCompat.buildInputMethodsAndSubtypesString(map);
+
+ // We do not expect what order will be used to concatenate items in
+ // InputMethodAndSubtypeUtil.buildInputMethodsAndSubtypesString() hence accept all possible
+ // permutations here.
+ assertThat(result).matches("ime0;subtype0;subtype1:ime1;subtype2;subtype3"
+ + "|ime0;subtype1;subtype0:ime1;subtype2;subtype3"
+ + "|ime0;subtype0;subtype1:ime1;subtype3;subtype2"
+ + "|ime0;subtype1;subtype0:ime1;subtype3;subtype2"
+ + "|ime1;subtype2;subtype3:ime0;subtype0;subtype1"
+ + "|ime2;subtype3;subtype2:ime0;subtype0;subtype1"
+ + "|ime3;subtype2;subtype3:ime0;subtype1;subtype0"
+ + "|ime4;subtype3;subtype2:ime0;subtype1;subtype0");
+ }
+
+ @Test
+ public void isValidSystemNonAuxAsciiCapableIme() {
+ // System IME w/ no subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(true, false)))
+ .isFalse();
+
+ // System IME w/ non-Aux and non-ASCII-capable "keyboard" subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(true, false, createDummySubtype("keyboard", false, false))))
+ .isFalse();
+
+ // System IME w/ non-Aux and ASCII-capable "keyboard" subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(true, false, createDummySubtype("keyboard", false, true))))
+ .isTrue();
+
+ // System IME w/ Aux and ASCII-capable "keyboard" subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(true, true, createDummySubtype("keyboard", true, true))))
+ .isFalse();
+
+ // System IME w/ non-Aux and ASCII-capable "voice" subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(true, false, createDummySubtype("voice", false, true))))
+ .isFalse();
+
+ // System IME w/ non-Aux and non-ASCII-capable subtype + Non-Aux and ASCII-capable subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(true, false,
+ createDummySubtype("keyboard", false, true),
+ createDummySubtype("keyboard", false, false))))
+ .isTrue();
+
+ // Non-system IME w/ non-Aux and ASCII-capable "keyboard" subtype
+ assertThat(InputMethodAndSubtypeUtilCompat.isValidSystemNonAuxAsciiCapableIme(
+ createDummyIme(false, false, createDummySubtype("keyboard", false, true))))
+ .isFalse();
+ }
+
+ private static InputMethodInfo createDummyIme(boolean isSystem, boolean isAuxIme,
+ InputMethodSubtype... subtypes) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ServiceInfo si = new ServiceInfo();
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = "com.example.android.dummyime";
+ ai.enabled = true;
+ ai.flags |= (isSystem ? ApplicationInfo.FLAG_SYSTEM : 0);
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = "com.example.android.dummyime";
+ si.name = "Dummy IME";
+ si.exported = true;
+ si.nonLocalizedLabel = "Dummy IME";
+ ri.serviceInfo = si;
+ return new InputMethodInfo(ri, isAuxIme, "", Arrays.asList(subtypes), 1, false);
+ }
+
+ private static InputMethodSubtype createDummySubtype(
+ String mode, boolean isAuxiliary, boolean isAsciiCapable) {
+ return new InputMethodSubtypeBuilder()
+ .setSubtypeNameResId(0)
+ .setSubtypeIconResId(0)
+ .setSubtypeLocale("en_US")
+ .setLanguageTag("en-US")
+ .setSubtypeMode(mode)
+ .setIsAuxiliary(isAuxiliary)
+ .setIsAsciiCapable(isAsciiCapable)
+ .build();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java
new file mode 100644
index 000000000000..f981f365ec2b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/license/LicenseHtmlLoaderCompatTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.settingslib.license;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.ArrayList;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+public class LicenseHtmlLoaderCompatTest {
+ @Mock
+ private Context mContext;
+
+ LicenseHtmlLoaderCompat newLicenseHtmlLoader(ArrayList<File> xmlFiles,
+ File cachedHtmlFile, boolean isCachedHtmlFileOutdated,
+ boolean generateHtmlFileSucceeded) {
+ LicenseHtmlLoaderCompat loader = spy(new LicenseHtmlLoaderCompat(mContext));
+ doReturn(xmlFiles).when(loader).getVaildXmlFiles();
+ doReturn(cachedHtmlFile).when(loader).getCachedHtmlFile();
+ doReturn(isCachedHtmlFileOutdated).when(loader).isCachedHtmlFileOutdated(any(), any());
+ doReturn(generateHtmlFileSucceeded).when(loader).generateHtmlFile(any(), any());
+ return loader;
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testLoadInBackground() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
+
+ assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
+ verify(loader).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithNoVaildXmlFiles() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true, true);
+
+ assertThat(loader.loadInBackground()).isNull();
+ verify(loader, never()).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithNonOutdatedCachedHtmlFile() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, false,
+ true);
+
+ assertThat(loader.loadInBackground()).isEqualTo(cachedHtmlFile);
+ verify(loader, never()).generateHtmlFile(any(), any());
+ }
+
+ @Test
+ public void testLoadInBackgroundWithGenerateHtmlFileFailed() {
+ ArrayList<File> xmlFiles = new ArrayList();
+ xmlFiles.add(new File("test.xml"));
+ File cachedHtmlFile = new File("test.html");
+
+ LicenseHtmlLoaderCompat loader = newLicenseHtmlLoader(xmlFiles, cachedHtmlFile, true,
+ false);
+
+ assertThat(loader.loadInBackground()).isNull();
+ verify(loader).generateHtmlFile(any(), any());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java
new file mode 100644
index 000000000000..1ee3afa9c1ce
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinCompatTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.suggestions;
+
+import static androidx.lifecycle.Lifecycle.Event.ON_START;
+import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+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.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.loader.app.LoaderManager;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(shadows = ShadowSuggestionController.class)
+public class SuggestionControllerMixinCompatTest {
+
+ @Mock
+ private SuggestionControllerMixinCompat.SuggestionControllerHost mHost;
+
+ private Context mContext;
+ private LifecycleOwner mLifecycleOwner;
+ private Lifecycle mLifecycle;
+ private SuggestionControllerMixinCompat mMixin;
+ private ComponentName mComponentName;
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
+ mComponentName = new ComponentName(
+ "com.android.settings.intelligence",
+ "com.android.settings.intelligence.suggestions.SuggestionService");
+ }
+
+ @After
+ public void tearDown() {
+ ShadowSuggestionController.reset();
+ }
+
+ @Test
+ public void goThroughLifecycle_onStartStop_shouldStartStopController() {
+ mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
+
+ mLifecycle.handleLifecycleEvent(ON_START);
+ assertThat(ShadowSuggestionController.sStartCalled).isTrue();
+
+ mLifecycle.handleLifecycleEvent(ON_STOP);
+ assertThat(ShadowSuggestionController.sStopCalled).isTrue();
+ }
+
+ @Test
+ public void onServiceConnected_shouldGetSuggestion() {
+ final LoaderManager loaderManager = mock(LoaderManager.class);
+ when(mHost.getLoaderManager()).thenReturn(loaderManager);
+
+ mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
+ mMixin.onServiceConnected();
+
+ verify(loaderManager).restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+ null /* args */, mMixin /* callback */);
+ }
+
+ @Test
+ public void onServiceConnected_hostNotAttached_shouldDoNothing() {
+ when(mHost.getLoaderManager()).thenReturn(null);
+
+ mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
+ mMixin.onServiceConnected();
+
+ verify(mHost).getLoaderManager();
+ }
+
+ @Test
+ public void onServiceDisconnected_hostNotAttached_shouldDoNothing() {
+ when(mHost.getLoaderManager()).thenReturn(null);
+
+ mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
+ mMixin.onServiceDisconnected();
+
+ verify(mHost).getLoaderManager();
+ }
+
+ @Test
+ public void doneLoadingg_shouldSetSuggestionLoaded() {
+ mMixin = new SuggestionControllerMixinCompat(mContext, mHost, mLifecycle, mComponentName);
+
+ mMixin.onLoadFinished(mock(SuggestionLoaderCompat.class), null);
+
+ assertThat(mMixin.isSuggestionLoaded()).isTrue();
+
+ mMixin.onLoaderReset(mock(SuggestionLoaderCompat.class));
+
+ assertThat(mMixin.isSuggestionLoaded()).isFalse();
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java
new file mode 100644
index 000000000000..8ba8606b8e08
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/testutils/FragmentTestUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.testutils;
+
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
+import org.robolectric.Robolectric;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+/**
+ * Utilities for creating Fragments for testing.
+ * <p>
+ * TODO(b/111195449) - Duplicated from org.robolectric.shadows.support.v4.SupportFragmentTestUtil
+ */
+@Deprecated
+public class FragmentTestUtils {
+
+ public static void startFragment(Fragment fragment) {
+ buildFragmentManager(FragmentUtilActivity.class)
+ .beginTransaction().add(fragment, null).commit();
+ }
+
+ public static void startFragment(Fragment fragment,
+ Class<? extends FragmentActivity> activityClass) {
+ buildFragmentManager(activityClass)
+ .beginTransaction().add(fragment, null).commit();
+ }
+
+ public static void startVisibleFragment(Fragment fragment) {
+ buildFragmentManager(FragmentUtilActivity.class)
+ .beginTransaction().add(1, fragment, null).commit();
+ }
+
+ public static void startVisibleFragment(Fragment fragment,
+ Class<? extends FragmentActivity> activityClass, int containerViewId) {
+ buildFragmentManager(activityClass)
+ .beginTransaction().add(containerViewId, fragment, null).commit();
+ }
+
+ private static FragmentManager buildFragmentManager(
+ Class<? extends FragmentActivity> activityClass) {
+ FragmentActivity activity = Robolectric.setupActivity(activityClass);
+ return activity.getSupportFragmentManager();
+ }
+
+ private static class FragmentUtilActivity extends FragmentActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ LinearLayout view = new LinearLayout(this);
+ view.setId(1);
+
+ setContentView(view);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
new file mode 100644
index 000000000000..1abbaba441ee
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinCompatTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.settingslib.widget;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.shadows.ShadowApplication;
+
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+public class FooterPreferenceMixinCompatTest {
+
+ @Mock
+ private PreferenceFragmentCompat mFragment;
+ @Mock
+ private PreferenceScreen mScreen;
+
+ private LifecycleOwner mLifecycleOwner;
+ private Lifecycle mLifecycle;
+ private FooterPreferenceMixinCompat mMixin;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLifecycleOwner = () -> mLifecycle;
+ mLifecycle = new Lifecycle(mLifecycleOwner);
+ when(mFragment.getPreferenceManager()).thenReturn(mock(PreferenceManager.class));
+ when(mFragment.getPreferenceManager().getContext())
+ .thenReturn(ShadowApplication.getInstance().getApplicationContext());
+ mMixin = new FooterPreferenceMixinCompat(mFragment, mLifecycle);
+ }
+
+ @Test
+ public void createFooter_screenNotAvailable_noCrash() {
+ assertThat(mMixin.createFooterPreference()).isNotNull();
+ }
+
+ @Test
+ public void createFooter_screenAvailable_canAttachToScreen() {
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+
+ final FooterPreference preference = mMixin.createFooterPreference();
+
+ assertThat(preference).isNotNull();
+ verify(mScreen).addPreference(preference);
+ }
+
+ @Test
+ public void createFooter_screenAvailableDelayed_canAttachToScreen() {
+ final FooterPreference preference = mMixin.createFooterPreference();
+
+ mLifecycle.setPreferenceScreen(mScreen);
+
+ assertThat(preference).isNotNull();
+ verify(mScreen).addPreference(preference);
+ }
+
+ @Test
+ public void createFooterTwice_screenAvailable_replaceOldFooter() {
+ when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+
+ mMixin.createFooterPreference();
+ mMixin.createFooterPreference();
+
+ verify(mScreen).removePreference(any(FooterPreference.class));
+ verify(mScreen, times(2)).addPreference(any(FooterPreference.class));
+ }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
index 78b7616716f5..8604d186f2dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceMixinTest.java
@@ -23,11 +23,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
-
import com.android.settingslib.SettingsLibRobolectricTestRunner;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -38,6 +33,11 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.shadows.ShadowApplication;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.preference.PreferenceFragment;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
@RunWith(SettingsLibRobolectricTestRunner.class)
public class FooterPreferenceMixinTest {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 375fef8ac3f9..9592b63c6d1e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2935,7 +2935,7 @@ public class SettingsProvider extends ContentProvider {
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 169;
+ private static final int SETTINGS_VERSION = 170;
private final int mUserId;
@@ -3810,6 +3810,37 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 169;
}
+ if (currentVersion == 169) {
+ // Version 169: by default, add STREAM_VOICE_CALL to list of streams that can
+ // be muted.
+ final SettingsState systemSettings = getSystemSettingsLocked(userId);
+ final Setting currentSetting = systemSettings.getSettingLocked(
+ Settings.System.MUTE_STREAMS_AFFECTED);
+ if (!currentSetting.isNull()) {
+ try {
+ int currentSettingIntegerValue = Integer.parseInt(
+ currentSetting.getValue());
+ if ((currentSettingIntegerValue
+ & (1 << AudioManager.STREAM_VOICE_CALL)) == 0) {
+ systemSettings.insertSettingLocked(
+ Settings.System.MUTE_STREAMS_AFFECTED,
+ Integer.toString(
+ currentSettingIntegerValue
+ | (1 << AudioManager.STREAM_VOICE_CALL)),
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ } catch (NumberFormatException e) {
+ // remove the setting in case it is not a valid integer
+ Slog.w("Failed to parse integer value of MUTE_STREAMS_AFFECTED"
+ + "setting, removing setting", e);
+ systemSettings.deleteSettingLocked(
+ Settings.System.MUTE_STREAMS_AFFECTED);
+ }
+
+ }
+ currentVersion = 170;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/res/color/notification_guts_buttons.xml b/packages/SystemUI/res/color/notification_guts_buttons.xml
index 3b8d59bae744..412e0be65cf7 100644
--- a/packages/SystemUI/res/color/notification_guts_buttons.xml
+++ b/packages/SystemUI/res/color/notification_guts_buttons.xml
@@ -2,6 +2,6 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true"
android:color="?android:attr/colorAccent" />
- <item android:color="@android:color/black"
+ <item android:color="@color/notification_primary_text_color"
android:alpha=".54" />
</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml
index ea6ef4cc0012..55e7a8526e1a 100644
--- a/packages/SystemUI/res/layout/notification_snooze.xml
+++ b/packages/SystemUI/res/layout/notification_snooze.xml
@@ -36,7 +36,7 @@
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:paddingStart="@*android:dimen/notification_content_margin_start"
- android:textColor="#DD000000"
+ android:textColor="@color/notification_primary_text_color"
android:paddingEnd="4dp"/>
<ImageView
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
new file mode 100644
index 000000000000..45d218548d1a
--- /dev/null
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -0,0 +1,44 @@
+<?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
+
+ NOTE: You might also want to edit: core/res/res/values-night/*.xml
+ -->
+<resources>
+ <!-- The color of the material notification background -->
+ <color name="notification_material_background_color">@*android:color/notification_material_background_color</color>
+
+ <!-- The color of the legacy notifications with customs backgrounds (gingerbread and lollipop.)
+ It's fine to override this color since at that point the shade was dark. -->
+ <color name="notification_legacy_background_color">@*android:color/notification_material_background_color</color>
+
+ <!-- The color of the material notification background when dimmed -->
+ <color name="notification_material_background_dimmed_color">#aa212121</color>
+
+ <!-- The color of the dividing line between grouped notifications while . -->
+ <color name="notification_divider_color">#000</color>
+
+ <!-- The background color of the notification shade -->
+ <color name="notification_shade_background_color">#181818</color>
+
+ <!-- The color of the ripples on the untinted notifications -->
+ <color name="notification_ripple_untinted_color">#30ffffff</color>
+
+ <!-- The "inside" of a notification, reached via longpress -->
+ <color name="notification_guts_bg_color">@*android:color/notification_material_background_color</color>
+
+ <!-- The color of the text inside a notification -->
+ <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_dark</color>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml
new file mode 100644
index 000000000000..481483991de9
--- /dev/null
+++ b/packages/SystemUI/res/values-night/dimens.xml
@@ -0,0 +1,21 @@
+<?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
+ -->
+
+<resources>
+ <!-- The height of the divider between the individual notifications. -->
+ <dimen name="notification_divider_height">1dp</dimen>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 07f1ee084183..4920fb2ea407 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -94,6 +94,9 @@
<!-- The color of the gear shown behind a notification -->
<color name="notification_gear_color">#ff757575</color>
+ <!-- The color of the text inside a notification -->
+ <color name="notification_primary_text_color">@*android:color/notification_primary_text_color_light</color>
+
<!-- The "inside" of a notification, reached via longpress -->
<color name="notification_guts_bg_color">#f8f9fa</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f62249a74cdb..a9d995c889c7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -252,6 +252,9 @@
-->
<dimen name="qs_header_system_icons_area_height">48dp</dimen>
+ <!-- How far the quick-quick settings panel extends below the status bar -->
+ <dimen name="qs_quick_header_panel_height">128dp</dimen>
+
<!-- The height of the container that holds the system icons in the quick settings header in the
car setting. -->
<dimen name="car_qs_header_system_icons_area_height">54dp</dimen>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index f8670485caf7..5a03c4a37cad 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -452,23 +452,20 @@
<style name="TextAppearance.NotificationInfo">
<item name="android:fontFamily">sans-serif</item>
- <item name="android:textColor">@android:color/black</item>
+ <item name="android:textColor">@color/notification_primary_text_color</item>
</style>
<style name="TextAppearance.NotificationInfo.Primary">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">16sp</item>
<item name="android:alpha">0.87</item>
</style>
<style name="TextAppearance.NotificationInfo.Confirmation">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:alpha">0.87</item>
</style>
<style name="TextAppearance.NotificationInfo.Secondary">
- <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">14sp</item>
<item name="android:alpha">0.54</item>
</style>
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 70ca055677e7..d21465c53bdf 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -30,6 +30,7 @@ import android.os.Looper;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.view.SurfaceControl;
@@ -391,14 +392,22 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(TAG_OPS + " state:");
- pw.print(" mConnectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
+ pw.print(" recentsComponentName="); pw.println(mRecentsComponentName);
+ pw.print(" isConnected="); pw.println(mOverviewProxy != null);
pw.print(" isCurrentUserSetup="); pw.println(mDeviceProvisionedController
.isCurrentUserSetup());
- pw.print(" isConnected="); pw.println(mOverviewProxy != null);
- pw.print(" mRecentsComponentName="); pw.println(mRecentsComponentName);
- pw.print(" mIsEnabled="); pw.println(isEnabled());
- pw.print(" mInteractionFlags="); pw.println(mInteractionFlags);
- pw.print(" mQuickStepIntent="); pw.println(mQuickStepIntent);
+ pw.print(" connectionBackoffAttempts="); pw.println(mConnectionBackoffAttempts);
+ pw.print(" interactionFlags="); pw.println(mInteractionFlags);
+
+ pw.print(" quickStepIntent="); pw.println(mQuickStepIntent);
+ pw.print(" quickStepIntentResolved="); pw.println(isEnabled());
+
+ final int swipeUpDefaultValue = mContext.getResources()
+ .getBoolean(com.android.internal.R.bool.config_swipe_up_gesture_default) ? 1 : 0;
+ final int swipeUpEnabled = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SWIPE_UP_TO_SWITCH_APPS_ENABLED, swipeUpDefaultValue);
+ pw.print(" swipeUpSetting="); pw.println(swipeUpEnabled != 0);
+ pw.print(" swipeUpSettingDefault="); pw.println(swipeUpDefaultValue != 0);
}
public interface OverviewProxyListener {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 6801e6917d3d..9a648d17c4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -77,6 +77,7 @@ public class PowerUI extends SystemUI {
private int mPlugType = 0;
private int mInvalidCharger = 0;
private EnhancedEstimates mEnhancedEstimates;
+ private Estimate mLastEstimate;
private boolean mLowWarningShownThisChargeCycle;
private boolean mSevereWarningShownThisChargeCycle;
@@ -247,7 +248,8 @@ public class PowerUI extends SystemUI {
// Show the correct version of low battery warning if needed
ThreadUtils.postOnBackgroundThread(() -> {
- maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
+ maybeShowBatteryWarning(
+ oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
});
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
@@ -262,14 +264,18 @@ public class PowerUI extends SystemUI {
}
}
- protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
- int bucket) {
+ protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
+ int oldBucket, int bucket) {
boolean isPowerSaver = mPowerManager.isPowerSaveMode();
// only play SFX when the dialog comes up or the bucket changes
final boolean playSound = bucket != oldBucket || oldPlugged;
final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
if (hybridEnabled) {
- final Estimate estimate = mEnhancedEstimates.getEstimate();
+ Estimate estimate = mLastEstimate;
+ if (estimate == null || mBatteryLevel != oldBatteryLevel) {
+ estimate = mEnhancedEstimates.getEstimate();
+ mLastEstimate = estimate;
+ }
// Turbo is not always booted once SysUI is running so we have ot make sure we actually
// get data back
if (estimate != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index a21886891e3d..35d2f90748c3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -33,6 +33,7 @@ import android.media.AudioManager;
import android.os.Handler;
import android.provider.AlarmClock;
import android.service.notification.ZenModeConfig;
+import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
import android.text.format.DateUtils;
import android.util.AttributeSet;
@@ -270,8 +271,22 @@ public class QuickStatusBarHeader extends RelativeLayout implements
updateResources();
}
+ /**
+ * The height of QQS should always be the status bar height + 128dp. This is normally easy, but
+ * when there is a notch involved the status bar can remain a fixed pixel size.
+ */
+ private void updateMinimumHeight() {
+ int sbHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ int qqsHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.qs_quick_header_panel_height);
+
+ setMinimumHeight(sbHeight + qqsHeight);
+ }
+
private void updateResources() {
Resources resources = mContext.getResources();
+ updateMinimumHeight();
// Update height for a few views, especially due to landscape mode restricting space.
mHeaderTextContainerView.getLayoutParams().height =
@@ -282,10 +297,17 @@ public class QuickStatusBarHeader extends RelativeLayout implements
com.android.internal.R.dimen.quick_qs_offset_height);
mSystemIconsView.setLayoutParams(mSystemIconsView.getLayoutParams());
- getLayoutParams().height = resources.getDimensionPixelSize(mQsDisabled
- ? com.android.internal.R.dimen.quick_qs_offset_height
- : com.android.internal.R.dimen.quick_qs_total_height);
- setLayoutParams(getLayoutParams());
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+ if (mQsDisabled) {
+ lp.height = resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.quick_qs_offset_height);
+ } else {
+ lp.height = Math.max(getMinimumHeight(),
+ resources.getDimensionPixelSize(
+ com.android.internal.R.dimen.quick_qs_offset_height));
+ }
+
+ setLayoutParams(lp);
updateStatusIconAlphaAnimator();
updateHeaderTextContainerAlphaAnimator();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 0ea941ff4f3e..5eaee5452ad5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -301,11 +301,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
// Create a share action for the notification
- PendingIntent shareAction = PendingIntent.getBroadcast(context, 0,
+ PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, 0,
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
.putExtra(EXTRA_ACTION_INTENT, sharingChooserIntent)
.putExtra(EXTRA_DISALLOW_ENTER_PIP, true),
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
R.drawable.ic_screenshot_share,
r.getString(com.android.internal.R.string.share), shareAction);
@@ -324,11 +324,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Create a edit action
- PendingIntent editAction = PendingIntent.getBroadcast(context, 1,
+ PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, 1,
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
.putExtra(EXTRA_ACTION_INTENT, editIntent)
.putExtra(EXTRA_CANCEL_NOTIFICATION, editIntent.getComponent() != null),
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
R.drawable.ic_screenshot_edit,
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
@@ -910,7 +910,7 @@ class GlobalScreenshot {
ActivityOptions opts = ActivityOptions.makeBasic();
opts.setDisallowEnterPictureInPictureWhileLaunching(
intent.getBooleanExtra(EXTRA_DISALLOW_ENTER_PIP, false));
- context.startActivityAsUser(actionIntent, opts.toBundle(),UserHandle.CURRENT);
+ context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
};
StatusBar statusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
statusBar.executeRunnableDismissingKeyguard(startActivityRunnable, null,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 6a387971500e..acdfa293ee6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -98,8 +98,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
= new PathInterpolator(0.6f, 0, 0.5f, 1);
private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
= new PathInterpolator(0, 0, 0.5f, 1);
- private final int mTintedRippleColor;
- protected final int mNormalRippleColor;
+ private int mTintedRippleColor;
+ protected int mNormalRippleColor;
private final AccessibilityManager mAccessibilityManager;
private final DoubleTapHelper mDoubleTapHelper;
@@ -132,7 +132,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private ValueAnimator mBackgroundColorAnimator;
private float mAppearAnimationFraction = -1.0f;
private float mAppearAnimationTranslation;
- private final int mNormalColor;
+ private int mNormalColor;
private boolean mIsBelowSpeedBump;
private FalsingManager mFalsingManager;
@@ -188,11 +188,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mSlowOutLinearInInterpolator = new PathInterpolator(0.8f, 0.0f, 1.0f, 1.0f);
setClipChildren(false);
setClipToPadding(false);
- mNormalColor = context.getColor(R.color.notification_material_background_color);
- mTintedRippleColor = context.getColor(
- R.color.notification_ripple_tinted_color);
- mNormalRippleColor = context.getColor(
- R.color.notification_ripple_untinted_color);
+ updateColors();
mFalsingManager = FalsingManager.getInstance(context);
mAccessibilityManager = AccessibilityManager.getInstance(mContext);
@@ -206,6 +202,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
initDimens();
}
+ private void updateColors() {
+ mNormalColor = mContext.getColor(R.color.notification_material_background_color);
+ mTintedRippleColor = mContext.getColor(
+ R.color.notification_ripple_tinted_color);
+ mNormalRippleColor = mContext.getColor(
+ R.color.notification_ripple_untinted_color);
+ mDimmedAlpha = Color.alpha(mContext.getColor(
+ R.color.notification_material_background_dimmed_color));
+ }
+
private void initDimens() {
mHeadsUpAddStartLocation = getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_content_margin_start);
@@ -217,6 +223,12 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
initDimens();
}
+ public void onUiModeChanged() {
+ updateColors();
+ initBackground();
+ updateBackgroundTint();
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -224,8 +236,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mFakeShadow = findViewById(R.id.fake_shadow);
mShadowHidden = mFakeShadow.getVisibility() != VISIBLE;
mBackgroundDimmed = findViewById(R.id.backgroundDimmed);
- mDimmedAlpha = Color.alpha(mContext.getColor(
- R.color.notification_material_background_dimmed_color));
initBackground();
updateBackground();
updateBackgroundTint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 9393d5b9be68..1d3f40894399 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
+import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -1053,6 +1054,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
super.onDensityOrFontScaleChanged();
initDimens();
initBackground();
+ reInflateViews();
+ }
+
+ private void reInflateViews() {
// Let's update our childrencontainer. This is intentionally not guarded with
// mIsSummaryWithChildren since we might have had children but not anymore.
if (mChildrenContainer != null) {
@@ -1079,7 +1084,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.initView();
l.reInflateViews();
}
- mNotificationInflater.onDensityOrFontScaleChanged();
+ mNotificationInflater.clearCachesAndReInflate();
onNotificationUpdated();
}
@@ -1090,6 +1095,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ @Override
+ public void onUiModeChanged() {
+ super.onUiModeChanged();
+ reInflateViews();
+ if (mChildrenContainer != null) {
+ for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
+ child.onUiModeChanged();
+ }
+ }
+ }
+
public void setContentBackground(int customBackgroundColor, boolean animate,
NotificationContentView notificationContentView) {
if (getShowingLayout() == notificationContentView) {
@@ -1448,6 +1464,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationInflater.setUsesIncreasedHeight(use);
}
+ public void setSmartActions(List<Notification.Action> smartActions) {
+ mNotificationInflater.setSmartActions(smartActions);
+ }
+
public void setUseIncreasedHeadsUpHeight(boolean use) {
mUseIncreasedHeadsUpHeight = use;
mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index a58752c20973..93433da9574b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -28,6 +28,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.AppGlobals;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -51,6 +52,8 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.RemoteViews;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ArrayUtils;
@@ -105,6 +108,8 @@ public class NotificationData {
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
+ @NonNull
+ public List<Notification.Action> smartActions = Collections.emptyList();
private int mCachedContrastColor = COLOR_INVALID;
private int mCachedContrastColorIsFor = COLOR_INVALID;
@@ -131,8 +136,23 @@ public class NotificationData {
private boolean hasSentReply;
public Entry(StatusBarNotification n) {
+ this(n, null);
+ }
+
+ public Entry(StatusBarNotification n, @Nullable Ranking ranking) {
this.key = n.getKey();
this.notification = n;
+ if (ranking != null) {
+ populateFromRanking(ranking);
+ }
+ }
+
+ public void populateFromRanking(@NonNull Ranking ranking) {
+ channel = ranking.getChannel();
+ snoozeCriteria = ranking.getSnoozeCriteria();
+ userSentiment = ranking.getUserSentiment();
+ smartActions = ranking.getSmartActions() == null
+ ? Collections.emptyList() : ranking.getSmartActions();
}
public void setInterruption() {
@@ -232,6 +252,7 @@ public class NotificationData {
/**
* Update the notification icons.
+ *
* @param context the context to create the icons with.
* @param sbn the notification to read the icon from.
* @throws InflationException
@@ -291,7 +312,7 @@ public class NotificationData {
}
public void onInflationTaskFinished() {
- mRunningTask = null;
+ mRunningTask = null;
}
@VisibleForTesting
@@ -607,7 +628,7 @@ public class NotificationData {
getRanking(key, mTmpRanking);
return mTmpRanking.getOverrideGroupKey();
}
- return null;
+ return null;
}
public List<SnoozeCriterion> getSnoozeCriteria(String key) {
@@ -658,9 +679,7 @@ public class NotificationData {
entry.notification.setOverrideGroupKey(overrideGroupKey);
mGroupManager.onEntryUpdated(entry, oldSbn);
}
- entry.channel = getChannel(entry.key);
- entry.snoozeCriteria = getSnoozeCriteria(entry.key);
- entry.userSentiment = mTmpRanking.getUserSentiment();
+ entry.populateFromRanking(mTmpRanking);
}
}
}
@@ -833,6 +852,7 @@ public class NotificationData {
public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
public String getCurrentMediaNotificationKey();
public NotificationGroupManager getGroupManager();
+
/**
* @return true iff the device is dozing
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
index 06f26c9cbc7c..bf0792923a22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
@@ -19,6 +19,7 @@ import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENAB
import static com.android.systemui.statusbar.NotificationRemoteInputManager
.FORCE_REMOTE_INPUT_HISTORY;
+import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -37,6 +38,7 @@ import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.EventLog;
import android.util.Log;
@@ -726,10 +728,10 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+ row.setSmartActions(entry.smartActions);
row.updateNotification(entry);
}
-
protected void addNotificationViews(NotificationData.Entry entry) {
if (entry == null) {
return;
@@ -740,12 +742,13 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
updateNotifications();
}
- protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
+ protected NotificationData.Entry createNotificationViews(
+ StatusBarNotification sbn, NotificationListenerService.Ranking ranking)
throws InflationException {
if (DEBUG) {
- Log.d(TAG, "createNotificationViews(notification=" + sbn);
+ Log.d(TAG, "createNotificationViews(notification=" + sbn + " " + ranking);
}
- NotificationData.Entry entry = new NotificationData.Entry(sbn);
+ NotificationData.Entry entry = new NotificationData.Entry(sbn, ranking);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
// Construct the expanded view.
@@ -754,12 +757,14 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
}
private void addNotificationInternal(StatusBarNotification notification,
- NotificationListenerService.RankingMap ranking) throws InflationException {
+ NotificationListenerService.RankingMap rankingMap) throws InflationException {
String key = notification.getKey();
if (DEBUG) Log.d(TAG, "addNotification key=" + key);
- mNotificationData.updateRanking(ranking);
- NotificationData.Entry shadeEntry = createNotificationViews(notification);
+ mNotificationData.updateRanking(rankingMap);
+ NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
+ rankingMap.getRanking(key, ranking);
+ NotificationData.Entry shadeEntry = createNotificationViews(notification, ranking);
boolean isHeadsUped = shouldPeek(shadeEntry);
if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(shadeEntry)) {
@@ -905,11 +910,57 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
mPresenter.updateNotificationViews();
}
- public void updateNotificationRanking(NotificationListenerService.RankingMap ranking) {
- mNotificationData.updateRanking(ranking);
+ public void updateNotificationRanking(NotificationListenerService.RankingMap rankingMap) {
+ List<NotificationData.Entry> entries = new ArrayList<>();
+ entries.addAll(mNotificationData.getActiveNotifications());
+ entries.addAll(mPendingNotifications.values());
+
+ // Has a copy of the current UI adjustments.
+ ArrayMap<String, NotificationUiAdjustment> oldAdjustments = new ArrayMap<>();
+ for (NotificationData.Entry entry : entries) {
+ NotificationUiAdjustment adjustment =
+ NotificationUiAdjustment.extractFromNotificationEntry(entry);
+ oldAdjustments.put(entry.key, adjustment);
+ }
+
+ // Populate notification entries from the new rankings.
+ mNotificationData.updateRanking(rankingMap);
+ updateRankingOfPendingNotifications(rankingMap);
+
+ // By comparing the old and new UI adjustments, reinflate the view accordingly.
+ for (NotificationData.Entry entry : entries) {
+ NotificationUiAdjustment newAdjustment =
+ NotificationUiAdjustment.extractFromNotificationEntry(entry);
+
+ if (NotificationUiAdjustment.needReinflate(
+ oldAdjustments.get(entry.key), newAdjustment)) {
+ if (entry.row != null) {
+ entry.reset();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ entry.notification.getUser().getIdentifier());
+ updateNotification(entry, pmUser, entry.notification, entry.row);
+ } else {
+ // Once the RowInflaterTask is done, it will pick up the updated entry, so
+ // no-op here.
+ }
+ }
+ }
+
updateNotifications();
}
+ private void updateRankingOfPendingNotifications(
+ @Nullable NotificationListenerService.RankingMap rankingMap) {
+ if (rankingMap == null) {
+ return;
+ }
+ NotificationListenerService.Ranking tmpRanking = new NotificationListenerService.Ranking();
+ for (NotificationData.Entry pendingNotification : mPendingNotifications.values()) {
+ rankingMap.getRanking(pendingNotification.key, tmpRanking);
+ pendingNotification.populateFromRanking(tmpRanking);
+ }
+ }
+
protected boolean shouldPeek(NotificationData.Entry entry) {
return shouldPeek(entry, entry.notification);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 886d6f17fedb..e013ae723c22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.content.res.Configuration;
import android.graphics.PorterDuff;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
@@ -25,6 +26,8 @@ import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.util.ContrastColorUtil;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -74,8 +77,11 @@ public class NotificationHeaderUtil {
imageView.getDrawable().mutate();
if (shouldApply) {
// lets gray it out
- int grey = view.getContext().getColor(
- com.android.internal.R.color.notification_default_color_light);
+ Configuration config = view.getContext().getResources().getConfiguration();
+ boolean inNightMode = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ int grey = ContrastColorUtil.resolveColor(view.getContext(),
+ Notification.COLOR_DEFAULT, inNightMode);
imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
} else {
// lets reset it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
new file mode 100644
index 000000000000..e6bdb2631912
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.RemoteInput;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * By diffing two entries, determines is view reinflation needed.
+ */
+public class NotificationUiAdjustment {
+
+ public final String key;
+ public final List<Notification.Action> smartActions;
+
+ @VisibleForTesting
+ NotificationUiAdjustment(String key, List<Notification.Action> smartActions) {
+ this.key = key;
+ this.smartActions = smartActions == null
+ ? Collections.emptyList()
+ : new ArrayList<>(smartActions);
+ }
+
+ public static NotificationUiAdjustment extractFromNotificationEntry(
+ NotificationData.Entry entry) {
+ return new NotificationUiAdjustment(entry.key, entry.smartActions);
+ }
+
+ public static boolean needReinflate(
+ @NonNull NotificationUiAdjustment oldAdjustment,
+ @NonNull NotificationUiAdjustment newAdjustment) {
+ if (oldAdjustment == newAdjustment) {
+ return false;
+ }
+ return areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions);
+ }
+
+ public static boolean areDifferent(
+ @NonNull List<Notification.Action> first, @NonNull List<Notification.Action> second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ if (first.size() != second.size()) {
+ return true;
+ }
+ for (int i = 0; i < first.size(); i++) {
+ Notification.Action firstAction = first.get(i);
+ Notification.Action secondAction = second.get(i);
+
+ if (!TextUtils.equals(firstAction.title, secondAction.title)) {
+ return true;
+ }
+
+ if (areDifferent(firstAction.getIcon(), secondAction.getIcon())) {
+ return true;
+ }
+
+ if (!Objects.equals(firstAction.actionIntent, secondAction.actionIntent)) {
+ return true;
+ }
+
+ if (areDifferent(firstAction.getRemoteInputs(), secondAction.getRemoteInputs())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean areDifferent(@Nullable Icon first, @Nullable Icon second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ return !first.sameAs(second);
+ }
+
+ private static boolean areDifferent(
+ @Nullable RemoteInput[] first, @Nullable RemoteInput[] second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ if (first.length != second.length) {
+ return true;
+ }
+ for (int i = 0; i < first.length; i++) {
+ RemoteInput firstRemoteInput = first[i];
+ RemoteInput secondRemoteInput = second[i];
+
+ if (!TextUtils.equals(firstRemoteInput.getLabel(), secondRemoteInput.getLabel())) {
+ return true;
+ }
+ if (areDifferent(firstRemoteInput.getChoices(), secondRemoteInput.getChoices())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean areDifferent(
+ @Nullable CharSequence[] first, @Nullable CharSequence[] second) {
+ if (first == second) {
+ return false;
+ }
+ if (first == null || second == null) {
+ return true;
+ }
+ if (first.length != second.length) {
+ return true;
+ }
+ for (int i = 0; i < first.length; i++) {
+ CharSequence firstCharSequence = first[i];
+ CharSequence secondCharSequence = second[i];
+ if (!TextUtils.equals(firstCharSequence, secondCharSequence)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c820e2b19366..cc5fbe53bba4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -661,8 +661,9 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
if (hsl[1] < 0.2f) {
contrastedColor = Notification.COLOR_DEFAULT;
}
+ boolean isDark = !ContrastColorUtil.isColorLight(mCachedContrastBackgroundColor);
contrastedColor = ContrastColorUtil.resolveContrastColor(mContext,
- contrastedColor, mCachedContrastBackgroundColor);
+ contrastedColor, mCachedContrastBackgroundColor, isDark);
}
mContrastedDrawableColor = contrastedColor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
index 130305715409..b9740d370782 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
@@ -27,15 +27,17 @@ import android.view.View;
import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationContentView;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.util.Assert;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
@@ -67,6 +69,7 @@ public class NotificationInflater {
private boolean mIsChildInGroup;
private InflationCallback mCallback;
private boolean mRedactAmbient;
+ private List<Notification.Action> mSmartActions;
public NotificationInflater(ExpandableNotificationRow row) {
mRow = row;
@@ -95,6 +98,10 @@ public class NotificationInflater {
mUsesIncreasedHeight = usesIncreasedHeight;
}
+ public void setSmartActions(List<Notification.Action> smartActions) {
+ mSmartActions = smartActions;
+ }
+
public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
}
@@ -140,7 +147,7 @@ public class NotificationInflater {
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow,
mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
- mCallback, mRemoteViewClickHandler);
+ mCallback, mRemoteViewClickHandler, mSmartActions);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
@@ -554,7 +561,7 @@ public class NotificationInflater {
}
}
- public void onDensityOrFontScaleChanged() {
+ public void clearCachesAndReInflate() {
NotificationData.Entry entry = mRow.getEntry();
entry.cachedAmbientContentView = null;
entry.cachedBigContentView = null;
@@ -586,13 +593,15 @@ public class NotificationInflater {
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
+ private List<Notification.Action> mSmartActions;
private AsyncInflationTask(StatusBarNotification notification,
int reInflateFlags, ExpandableNotificationRow row, boolean isLowPriority,
boolean isChildInGroup, boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
InflationCallback callback,
- RemoteViews.OnClickHandler remoteViewClickHandler) {
+ RemoteViews.OnClickHandler remoteViewClickHandler,
+ List<Notification.Action> smartActions) {
mRow = row;
mSbn = notification;
mReInflateFlags = reInflateFlags;
@@ -604,6 +613,9 @@ public class NotificationInflater {
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
+ mSmartActions = smartActions == null
+ ? Collections.emptyList()
+ : new ArrayList<>(smartActions);
NotificationData.Entry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -619,6 +631,9 @@ public class NotificationInflater {
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
+
+ applyChanges(recoveredBuilder);
+
Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
if (notification.isMediaNotification()) {
@@ -646,6 +661,18 @@ public class NotificationInflater {
}
}
+ /**
+ * Apply changes to the given notification builder, like adding smart actions suggested by
+ * a {@link android.service.notification.NotificationAssistantService}.
+ */
+ private void applyChanges(Notification.Builder builder) {
+ if (mSmartActions != null) {
+ for (Notification.Action smartAction : mSmartActions) {
+ builder.addAction(smartAction);
+ }
+ }
+ }
+
private void handleError(Exception e) {
mRow.getEntry().onInflationTaskFinished();
StatusBarNotification sbn = mRow.getStatusBarNotification();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index ea70ebbe7e4b..a781be69c93e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -189,6 +189,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
state |= DISABLE_SYSTEM_INFO;
state |= DISABLE_CLOCK;
}
+
+ // In landscape, the heads up show but shouldHideNotificationIcons() return false
+ // because the visual icon is in notification icon area rather than heads up's space.
+ // whether the notification icon show or not, clock should hide when heads up show.
+ if (mStatusBarComponent.isHeadsUpShouldBeVisible()) {
+ state |= DISABLE_CLOCK;
+ }
+
if (mNetworkController != null && EncryptionHelper.IS_DATA_ENCRYPTED) {
if (mNetworkController.hasEmergencyCryptKeeperText()) {
state |= DISABLE_NOTIFICATION_ICONS;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 5a07dbd1eda5..240d467936b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2533,7 +2533,8 @@ public class NotificationPanelView extends PanelView implements
}
private void updateStatusBarIcons() {
- boolean showIconsWhenExpanded = isFullWidth() && getExpandedHeight() < getOpeningHeight();
+ boolean showIconsWhenExpanded = (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+ && getExpandedHeight() < getOpeningHeight();
if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
showIconsWhenExpanded = false;
}
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 f5736423c6d2..5b42d5e31c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -113,6 +113,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DozeParameters mDozeParameters;
private final AlarmTimeout mTimeTicker;
+ private final KeyguardVisibilityCallback mKeyguardVisibilityCallback;
private final SysuiColorExtractor mColorExtractor;
private GradientColors mLockColors;
@@ -171,6 +172,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
mTimeTicker = new AlarmTimeout(alarmManager, this::onHideWallpaperTimeout,
"hide_aod_wallpaper", new Handler());
@@ -892,6 +895,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
for (ScrimState state : ScrimState.values()) {
state.setHasBackdrop(hasBackdrop);
}
+
+ // Backdrop event may arrive after state was already applied,
+ // in this case, back-scrim needs to be re-evaluated
+ if (mState == ScrimState.AOD || mState == ScrimState.PULSING) {
+ float newBehindAlpha = mState.getBehindAlpha(mNotificationDensity);
+ if (mCurrentBehindAlpha != newBehindAlpha) {
+ mCurrentBehindAlpha = newBehindAlpha;
+ updateScrims();
+ }
+ }
}
public void setLaunchingAffordanceWithPreview(boolean launchingAffordanceWithPreview) {
@@ -910,4 +923,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo
default void onCancelled() {
}
}
+
+ /**
+ * Simple keyguard callback that updates scrims when keyguard visibility changes.
+ */
+ private class KeyguardVisibilityCallback extends KeyguardUpdateMonitorCallback {
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ mNeedsDrawableColorUpdate = true;
+ scheduleUpdate();
+ }
+ }
}
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 19015fcdacf7..081ebfac5f1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -105,7 +105,6 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
mBlankScreen = mDisplayRequiresBlanking;
- mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
mCurrentInFrontTint = Color.BLACK;
mCurrentBehindTint = Color.BLACK;
@@ -116,6 +115,11 @@ public enum ScrimState {
}
@Override
+ public float getBehindAlpha(float busyness) {
+ return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
+ }
+
+ @Override
public boolean isLowPowerState() {
return true;
}
@@ -129,10 +133,14 @@ public enum ScrimState {
public void prepare(ScrimState previousState) {
mCurrentInFrontAlpha = 0;
mCurrentInFrontTint = Color.BLACK;
- mCurrentBehindAlpha = mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
mCurrentBehindTint = Color.BLACK;
mBlankScreen = mDisplayRequiresBlanking;
}
+
+ @Override
+ public float getBehindAlpha(float busyness) {
+ return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f;
+ }
},
/**
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 7cf2c3376e89..8d077f3afcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -1198,6 +1198,9 @@ public class StatusBar extends SystemUI implements DemoMode,
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.onUiModeChanged();
}
+ if (mStackScroller != null) {
+ mStackScroller.onUiModeChanged();
+ }
}
private void inflateEmptyShadeView() {
@@ -2155,6 +2158,10 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
+ public boolean isHeadsUpShouldBeVisible() {
+ return mHeadsUpAppearanceController.shouldBeVisible();
+ }
+
/**
* All changes to the status bar and notifications funnel through here and are batched.
*/
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 25261c0ad2ca..f729120f98bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -976,7 +976,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
- null, 0, 0, "");
+ null, null, null, "");
MobileSignalController controller = new MobileSignalController(mContext,
mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
mSubDefaults, mReceiverHandler.getLooper());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 6fc491119aa6..eb3289b8e8e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -514,6 +514,20 @@ public class NotificationStackScrollLayout extends ViewGroup
mSwipeHelper.onMenuShown(row);
}
+ public void onUiModeChanged() {
+ mBgColor = mContext.getColor(R.color.notification_shade_background_color);
+ updateBackgroundDimming();
+
+ // Re-inflate all notification views
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ if (child instanceof ActivatableNotificationView) {
+ ((ActivatableNotificationView) child).onUiModeChanged();
+ }
+ }
+ }
+
protected void onDraw(Canvas canvas) {
if (mShouldDrawNotificationBackground
&& (mCurrentBounds.top < mCurrentBounds.bottom || mAmbientState.isDark())) {
@@ -588,8 +602,8 @@ public class NotificationStackScrollLayout extends ViewGroup
// Interpolate between semi-transparent notification panel background color
// and white AOD separator.
- float colorInterpolation = Interpolators.DECELERATE_QUINT.getInterpolation(
- mInterpolatedDarkAmount);
+ float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
+ mLinearDarkAmount);
int color = ColorUtils.blendARGB(awakeColor, Color.WHITE, colorInterpolation);
if (mCachedBackgroundColor != color) {
@@ -1425,7 +1439,8 @@ public class NotificationStackScrollLayout extends ViewGroup
*/
private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
return positionInLinearLayout + v.getIntrinsicHeight() +
- getImeInset() - getHeight() + getTopPadding();
+ getImeInset() - getHeight()
+ + ((!isExpanded() && isPinnedHeadsUp(v)) ? mHeadsUpInset : getTopPadding());
}
@Override
@@ -2056,9 +2071,15 @@ public class NotificationStackScrollLayout extends ViewGroup
}
private int getScrollRange() {
- int scrollRange = Math.max(0, mContentHeight - mMaxLayoutHeight);
+ // In current design, it only use the top HUN to treat all of HUNs
+ // although there are more than one HUNs
+ int contentHeight = mContentHeight;
+ if (!isExpanded() && mHeadsUpManager.hasPinnedHeadsUp()) {
+ contentHeight = mHeadsUpInset + getTopHeadsUpPinnedHeight();
+ }
+ int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
int imeInset = getImeInset();
- scrollRange += Math.min(imeInset, Math.max(0, mContentHeight - (getHeight() - imeInset)));
+ scrollRange += Math.min(imeInset, Math.max(0, contentHeight - (getHeight() - imeInset)));
return scrollRange;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index ee006d3e267d..886bd5e3dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -473,6 +473,15 @@ public class StackScrollAlgorithm {
childState.yTranslation = topState.yTranslation + topState.height
- childState.height;
}
+
+ // heads up notification show and this row is the top entry of heads up
+ // notifications. i.e. this row should be the only one row that has input field
+ // To check if the row need to do translation according to scroll Y
+ // heads up show full of row's content and any scroll y indicate that the
+ // translationY need to move up the HUN.
+ if (!mIsExpanded && isTopEntry && ambientState.getScrollY() > 0) {
+ childState.yTranslation -= ambientState.getScrollY();
+ }
}
if (row.isHeadsUpAnimatingAway()) {
childState.hidden = false;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 4a9856b1c840..2cbb78a4a8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -351,11 +351,11 @@ public class CarVolumeDialogImpl implements VolumeDialog {
listItem.setOnSeekBarChangeListener(
new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
Drawable primaryIcon = mContext.getResources().getDrawable(volumeItem.icon);
- primaryIcon.setTint(color);
+ primaryIcon.mutate().setTint(color);
listItem.setPrimaryActionIcon(primaryIcon);
if (supplementalIconId != 0) {
Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
- supplementalIcon.setTint(color);
+ supplementalIcon.mutate().setTint(color);
listItem.setSupplementalIcon(supplementalIcon, true,
supplementalIconOnClickListener);
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index d19715d5c5cc..5ecf0c04aeb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -24,6 +24,7 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -66,6 +67,8 @@ public class PowerUITest extends SysuiTestCase {
public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
private static final long ABOVE_CHARGE_CYCLE_THRESHOLD = Duration.ofHours(8).toMillis();
+ private static final int OLD_BATTERY_LEVEL_NINE = 9;
+ private static final int OLD_BATTERY_LEVEL_10 = 10;
private HardwarePropertiesManager mHardProps;
private WarningsUI mMockWarnings;
private PowerUI mPowerUI;
@@ -307,8 +310,8 @@ public class PowerUITest extends SysuiTestCase {
.thenReturn(new Estimate(BELOW_HYBRID_THRESHOLD, true));
mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
- mPowerUI.maybeShowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
- ABOVE_WARNING_BUCKET);
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
// reduce battery level to handle time based trigger -> level trigger interactions
mPowerUI.mBatteryLevel = 10;
@@ -449,6 +452,33 @@ public class PowerUITest extends SysuiTestCase {
verify(mMockWarnings, never()).dismissLowBatteryWarning();
}
+ @Test
+ public void testMaybeShowBatteryWarning_onlyQueriesEstimateOnBatteryLevelChangeOrNull() {
+ mPowerUI.start();
+ Estimate estimate = new Estimate(BELOW_HYBRID_THRESHOLD, true);
+ when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+ when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+ when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+ when(mEnhancedEstimates.getEstimate()).thenReturn(estimate);
+ mPowerUI.mBatteryStatus = BatteryManager.BATTERY_HEALTH_GOOD;
+
+ // we expect that the first time it will query even if the level is the same
+ mPowerUI.mBatteryLevel = 9;
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+ verify(mEnhancedEstimates, times(1)).getEstimate();
+
+ // We should NOT query again if the battery level hasn't changed
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_NINE, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+ verify(mEnhancedEstimates, times(1)).getEstimate();
+
+ // Battery level has changed, so we should query again
+ mPowerUI.maybeShowBatteryWarning(OLD_BATTERY_LEVEL_10, UNPLUGGED, UNPLUGGED,
+ ABOVE_WARNING_BUCKET, ABOVE_WARNING_BUCKET);
+ verify(mEnhancedEstimates, times(2)).getEstimate();
+ }
+
private void setCurrentTemp(float temp) {
when(mHardProps.getDeviceTemperatures(DEVICE_TEMPERATURE_SKIN, TEMPERATURE_CURRENT))
.thenReturn(new float[] { temp });
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
index 77522e44b183..f2f58938009b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationDataTest.java
@@ -38,11 +38,16 @@ import static org.mockito.Mockito.when;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
import android.media.session.MediaSession;
import android.os.Bundle;
import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.SmallTest;
@@ -72,6 +77,8 @@ public class NotificationDataTest extends SysuiTestCase {
private static final int UID_ALLOW_DURING_SETUP = 456;
private static final String TEST_HIDDEN_NOTIFICATION_KEY = "testHiddenNotificationKey";
private static final String TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY = "exempt";
+ private static final NotificationChannel NOTIFICATION_CHANNEL =
+ new NotificationChannel("id", "name", NotificationChannel.USER_LOCKED_IMPORTANCE);
private final StatusBarNotification mMockStatusBarNotification =
mock(StatusBarNotification.class);
@@ -145,11 +152,9 @@ public class NotificationDataTest extends SysuiTestCase {
@Test
public void testChannelSetWhenAdded() {
mNotificationData.add(mRow.getEntry());
- Assert.assertTrue(mRow.getEntry().channel != null);
+ assertEquals(NOTIFICATION_CHANNEL, mRow.getEntry().channel);
}
-
-
@Test
public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
mNotificationData.add(mRow.getEntry());
@@ -373,6 +378,32 @@ public class NotificationDataTest extends SysuiTestCase {
assertFalse(mNotificationData.isExemptFromDndVisualSuppression(entry));
}
+ @Test
+ public void testCreateNotificationDataEntry_RankingUpdate() {
+ Ranking ranking = mock(Ranking.class);
+
+ ArrayList<Notification.Action> smartActions = new ArrayList<>();
+ smartActions.add(createAction());
+ when(ranking.getSmartActions()).thenReturn(smartActions);
+
+ when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
+
+ when(ranking.getUserSentiment()).thenReturn(Ranking.USER_SENTIMENT_NEGATIVE);
+
+ SnoozeCriterion snoozeCriterion = new SnoozeCriterion("id", "explanation", "confirmation");
+ ArrayList<SnoozeCriterion> snoozeCriterions = new ArrayList<>();
+ snoozeCriterions.add(snoozeCriterion);
+ when(ranking.getSnoozeCriteria()).thenReturn(snoozeCriterions);
+
+ NotificationData.Entry entry =
+ new NotificationData.Entry(mMockStatusBarNotification, ranking);
+
+ assertEquals(smartActions, entry.smartActions);
+ assertEquals(NOTIFICATION_CHANNEL, entry.channel);
+ assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
+ assertEquals(snoozeCriterions, entry.snoozeCriteria);
+ }
+
private void initStatusBarNotification(boolean allowDuringSetup) {
Bundle bundle = new Bundle();
bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup);
@@ -388,12 +419,7 @@ public class NotificationDataTest extends SysuiTestCase {
}
@Override
- public NotificationChannel getChannel(String key) {
- return new NotificationChannel(null, null, 0);
- }
-
- @Override
- protected boolean getRanking(String key, NotificationListenerService.Ranking outRanking) {
+ protected boolean getRanking(String key, Ranking outRanking) {
super.getRanking(key, outRanking);
if (key.equals(TEST_HIDDEN_NOTIFICATION_KEY)) {
outRanking.populate(key, outRanking.getRank(),
@@ -401,23 +427,31 @@ public class NotificationDataTest extends SysuiTestCase {
outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true,
+ null);
} else if (key.equals(TEST_EXEMPT_DND_VISUAL_SUPPRESSION_KEY)) {
outRanking.populate(key, outRanking.getRank(),
outRanking.matchesInterruptionFilter(),
outRanking.getVisibilityOverride(), 255,
outRanking.getImportance(), outRanking.getImportanceExplanation(),
outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), true);
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), true, null);
} else {
outRanking.populate(key, outRanking.getRank(),
outRanking.matchesInterruptionFilter(),
outRanking.getVisibilityOverride(), outRanking.getSuppressedVisualEffects(),
outRanking.getImportance(), outRanking.getImportanceExplanation(),
- outRanking.getOverrideGroupKey(), outRanking.getChannel(), null, null,
- outRanking.canShowBadge(), outRanking.getUserSentiment(), false);
+ outRanking.getOverrideGroupKey(), NOTIFICATION_CHANNEL, null, null,
+ outRanking.canShowBadge(), outRanking.getUserSentiment(), false, null);
}
return true;
}
}
+
+ private Notification.Action createAction() {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ "action",
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
index afe16cf13b76..e75e5786b463 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationEntryManagerTest.java
@@ -37,7 +37,10 @@ import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -54,6 +57,8 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationInflater;
+import com.android.systemui.statusbar.notification.RowInflaterTask;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -68,6 +73,9 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -99,6 +107,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
@Mock private VisualStabilityManager mVisualStabilityManager;
@Mock private MetricsLogger mMetricsLogger;
@Mock private SmartReplyController mSmartReplyController;
+ @Mock private RowInflaterTask mAsyncInflationTask;
private NotificationData.Entry mEntry;
private StatusBarNotification mSbn;
@@ -139,7 +148,26 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
0,
NotificationManager.IMPORTANCE_DEFAULT,
null, null,
- null, null, null, true, sentiment, false);
+ null, null, null, true, sentiment, false, null);
+ return true;
+ }).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
+ }
+
+ private void setSmartActions(String key, ArrayList<Notification.Action> smartActions) {
+ doAnswer(invocationOnMock -> {
+ NotificationListenerService.Ranking ranking = (NotificationListenerService.Ranking)
+ invocationOnMock.getArguments()[1];
+ ranking.populate(
+ key,
+ 0,
+ false,
+ 0,
+ 0,
+ NotificationManager.IMPORTANCE_DEFAULT,
+ null, null,
+ null, null, null, true,
+ NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL, false,
+ smartActions);
return true;
}).when(mRankingMap).getRanking(eq(key), any(NotificationListenerService.Ranking.class));
}
@@ -427,4 +455,71 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
Assert.assertTrue(newSbn.getNotification().extras
.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false));
}
+
+ @Test
+ public void testUpdateNotificationRanking() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = mRow;
+ mEntry.setInflationTask(mAsyncInflationTask);
+ mEntryManager.getNotificationData().add(mEntry);
+ setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow).updateNotification(eq(mEntry));
+ assertEquals(1, mEntry.smartActions.size());
+ assertEquals("action", mEntry.smartActions.get(0).title);
+ }
+
+ @Test
+ public void testUpdateNotificationRanking_noChange() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = mRow;
+ mEntryManager.getNotificationData().add(mEntry);
+ setSmartActions(mEntry.key, null);
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow, never()).updateNotification(eq(mEntry));
+ assertEquals(0, mEntry.smartActions.size());
+ }
+
+ @Test
+ public void testUpdateNotificationRanking_rowNotInflatedYet() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = null;
+ mEntryManager.getNotificationData().add(mEntry);
+ setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow, never()).updateNotification(eq(mEntry));
+ assertEquals(1, mEntry.smartActions.size());
+ assertEquals("action", mEntry.smartActions.get(0).title);
+ }
+
+ @Test
+ public void testUpdateNotificationRanking_pendingNotification() {
+ when(mPresenter.isDeviceProvisioned()).thenReturn(true);
+ when(mPresenter.isNotificationForCurrentProfiles(any())).thenReturn(true);
+
+ mEntry.row = null;
+ mEntryManager.mPendingNotifications.put(mEntry.key, mEntry);
+ setSmartActions(mEntry.key, new ArrayList<>(Arrays.asList(createAction())));
+
+ mEntryManager.updateNotificationRanking(mRankingMap);
+ verify(mRow, never()).updateNotification(eq(mEntry));
+ assertEquals(1, mEntry.smartActions.size());
+ assertEquals("action", mEntry.smartActions.get(0).title);
+ }
+
+ private Notification.Action createAction() {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ "action",
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
new file mode 100644
index 000000000000..ce47e60f3e1c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.support.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+@SmallTest
+public class NotificationUiAdjustmentTest extends SysuiTestCase {
+
+ @Test
+ public void needReinflate_differentLength() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Notification.Action action =
+ createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.emptyList()),
+ new NotificationUiAdjustment("second", Collections.singletonList(action))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentLabels() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Notification.Action firstAction =
+ createActionBuilder("first", R.drawable.ic_corp_icon, pendingIntent).build();
+ Notification.Action secondAction =
+ createActionBuilder("second", R.drawable.ic_corp_icon, pendingIntent).build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentIcons() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent).build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_account_circle, pendingIntent)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentPendingIntent() {
+ PendingIntent firstPendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW), 0);
+ PendingIntent secondPendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_PROCESS_TEXT), 0);
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, firstPendingIntent)
+ .build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, secondPendingIntent)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentChoices() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+
+ RemoteInput firstRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"first"});
+ RemoteInput secondRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"second"});
+
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(firstRemoteInput)
+ .build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(secondRemoteInput)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_differentRemoteInputLabel() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+
+ RemoteInput firstRemoteInput =
+ createRemoteInput("same", "first", new CharSequence[] {"same"});
+ RemoteInput secondRemoteInput =
+ createRemoteInput("same", "second", new CharSequence[] {"same"});
+
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(firstRemoteInput)
+ .build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(secondRemoteInput)
+ .build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isTrue();
+ }
+
+ @Test
+ public void needReinflate_negative() {
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ RemoteInput firstRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"same"});
+ RemoteInput secondRemoteInput =
+ createRemoteInput("same", "same", new CharSequence[] {"same"});
+
+ Notification.Action firstAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(firstRemoteInput).build();
+ Notification.Action secondAction =
+ createActionBuilder("same", R.drawable.ic_corp_icon, pendingIntent)
+ .addRemoteInput(secondRemoteInput).build();
+
+ assertThat(NotificationUiAdjustment.needReinflate(
+ new NotificationUiAdjustment("first", Collections.singletonList(firstAction)),
+ new NotificationUiAdjustment("second", Collections.singletonList(secondAction))))
+ .isFalse();
+ }
+
+ private Notification.Action.Builder createActionBuilder(
+ String title, int drawableRes, PendingIntent pendingIntent) {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(mContext, drawableRes), title, pendingIntent);
+ }
+
+ private RemoteInput createRemoteInput(String resultKey, String label, CharSequence[] choices) {
+ return new RemoteInput.Builder(resultKey).setLabel(label).setChoices(choices).build();
+ }
+}
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 89d562a2099c..9c558748697a 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
@@ -152,6 +152,20 @@ public class ScrimControllerTest extends SysuiTestCase {
}
@Test
+ public void setHasBackdrop_withAodWallpaperAndAlbumArt() {
+ mScrimController.setWallpaperSupportsAmbientMode(true);
+ mScrimController.transitionTo(ScrimState.AOD);
+ mScrimController.finishAnimationsImmediately();
+ mScrimController.setHasBackdrop(true);
+ mScrimController.finishAnimationsImmediately();
+ // Front scrim should be transparent
+ // Back scrim should be visible with tint
+ assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
+ assertScrimTint(mScrimBehind, true /* tinted */);
+ assertScrimTint(mScrimInFront, true /* tinted */);
+ }
+
+ @Test
public void transitionToAod_withFrontAlphaUpdates() {
// Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state.
mScrimController.transitionTo(ScrimState.KEYGUARD);
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index dc6d16be6e30..b6a5a9c562eb 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -6160,6 +6160,14 @@ message MetricsEvent {
// CATEGORY: SETTINGS
// OS: Q
FACE_ENROLL_FINISHED = 1508;
+
+ // OPEN: Face Enroll sidecar
+ // CATEGORY: SETTINGS
+ // OS: Q
+ FACE_ENROLL_SIDECAR = 1509;
+
+ // OPEN: Settings > Add face > Error dialog
+ DIALOG_FACE_ERROR = 5510;
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
diff --git a/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java b/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java
deleted file mode 100644
index 892c49052533..000000000000
--- a/sax/tests/saxtests/src/android/sax/ExpatPerformanceTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.sax;
-
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.util.Xml;
-import org.kxml2.io.KXmlParser;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-import com.android.frameworks.saxtests.R;
-
-public class ExpatPerformanceTest extends AndroidTestCase {
-
- private static final String TAG = ExpatPerformanceTest.class.getSimpleName();
-
- private byte[] mXmlBytes;
-
- @Override
- public void setUp() throws Exception {
- super.setUp();
- InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int length;
- while ((length = in.read(buffer)) != -1) {
- out.write(buffer, 0, length);
- }
- mXmlBytes = out.toByteArray();
-
- Log.i("***", "File size: " + (mXmlBytes.length / 1024) + "k");
- }
-
- @LargeTest
- public void testPerformance() throws Exception {
-// try {
-// Debug.startMethodTracing("expat3");
-// for (int i = 0; i < 1; i++) {
- runJavaPullParser();
- runSax();
- runExpatPullParser();
-// }
-// } finally {
-// Debug.stopMethodTracing();
-// }
- }
-
- private InputStream newInputStream() {
- return new ByteArrayInputStream(mXmlBytes);
- }
-
- private void runSax() throws IOException, SAXException {
- long start = System.currentTimeMillis();
- Xml.parse(newInputStream(), Xml.Encoding.UTF_8, new DefaultHandler());
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "expat SAX: " + elapsed + "ms");
- }
-
- private void runExpatPullParser() throws XmlPullParserException, IOException {
- long start = System.currentTimeMillis();
- XmlPullParser pullParser = Xml.newPullParser();
- pullParser.setInput(newInputStream(), "UTF-8");
- withPullParser(pullParser);
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "expat pull: " + elapsed + "ms");
- }
-
- private void runJavaPullParser() throws XmlPullParserException, IOException {
- XmlPullParser pullParser;
- long start = System.currentTimeMillis();
- pullParser = new KXmlParser();
- pullParser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
- pullParser.setInput(newInputStream(), "UTF-8");
- withPullParser(pullParser);
- long elapsed = System.currentTimeMillis() - start;
- Log.i(TAG, "java pull parser: " + elapsed + "ms");
- }
-
- private static void withPullParser(XmlPullParser pullParser)
- throws IOException, XmlPullParserException {
- int eventType = pullParser.next();
- while (eventType != XmlPullParser.END_DOCUMENT) {
- switch (eventType) {
- case XmlPullParser.START_TAG:
- pullParser.getName();
-// int nattrs = pullParser.getAttributeCount();
-// for (int i = 0; i < nattrs; ++i) {
-// pullParser.getAttributeName(i);
-// pullParser.getAttributeValue(i);
-// }
- break;
- case XmlPullParser.END_TAG:
- pullParser.getName();
- break;
- case XmlPullParser.TEXT:
- pullParser.getText();
- break;
- }
- eventType = pullParser.next();
- }
- }
-}
diff --git a/services/Android.bp b/services/Android.bp
index d125adc5fa7d..bea51be321c9 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -2,6 +2,7 @@
// ============================================================
java_library {
name: "services",
+ installable: true,
dex_preopt: {
app_image: true,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 41e9d2bf49de..021fdcd84971 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -46,6 +46,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Parcelable;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -622,6 +623,38 @@ public final class AutofillManagerService extends SystemService {
return getWhitelistedCompatModePackages(getWhitelistedCompatModePackagesFromSettings());
}
+ private void send(@NonNull IResultReceiver receiver, int value) {
+ try {
+ receiver.send(value, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @NonNull Bundle value) {
+ try {
+ receiver.send(0, value);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Error async reporting result to client: " + e);
+ }
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable String value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable String[] value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, @Nullable Parcelable value) {
+ send(receiver, AutofillManager.SyncResultReceiver.bundleFor(value));
+ }
+
+ private void send(@NonNull IResultReceiver receiver, boolean value) {
+ send(receiver, value ? 1 : 0);
+ }
+
@Nullable
@VisibleForTesting
static Map<String, String[]> getWhitelistedCompatModePackages(String setting) {
@@ -827,9 +860,10 @@ public final class AutofillManagerService extends SystemService {
final class AutoFillManagerServiceStub extends IAutoFillManager.Stub {
@Override
- public int addClient(IAutoFillManagerClient client, int userId) {
+ public void addClient(IAutoFillManagerClient client, int userId,
+ @NonNull IResultReceiver receiver) {
+ int flags = 0;
synchronized (mLock) {
- int flags = 0;
if (getServiceForUserLocked(userId).addClientLocked(client)) {
flags |= AutofillManager.FLAG_ADD_CLIENT_ENABLED;
}
@@ -839,8 +873,8 @@ public final class AutofillManagerService extends SystemService {
if (sVerbose) {
flags |= AutofillManager.FLAG_ADD_CLIENT_VERBOSE;
}
- return flags;
}
+ send(receiver, flags);
}
@Override
@@ -874,9 +908,9 @@ public final class AutofillManagerService extends SystemService {
}
@Override
- public int startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
+ public void startSession(IBinder activityToken, IBinder appCallback, AutofillId autofillId,
Rect bounds, AutofillValue value, int userId, boolean hasCallback, int flags,
- ComponentName componentName, boolean compatMode) {
+ ComponentName componentName, boolean compatMode, IResultReceiver receiver) {
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
@@ -892,61 +926,63 @@ public final class AutofillManagerService extends SystemService {
throw new IllegalArgumentException(packageName + " is not a valid package", e);
}
+ final int sessionId;
synchronized (mLock) {
final AutofillManagerServiceImpl service = getServiceForUserLocked(userId);
- return service.startSessionLocked(activityToken, getCallingUid(), appCallback,
+ sessionId = service.startSessionLocked(activityToken, getCallingUid(), appCallback,
autofillId, bounds, value, hasCallback, componentName, compatMode,
mAllowInstantService, flags);
}
+ send(receiver, sessionId);
}
@Override
- public FillEventHistory getFillEventHistory() throws RemoteException {
+ public void getFillEventHistory(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ FillEventHistory fillEventHistory = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getFillEventHistory(getCallingUid());
+ fillEventHistory = service.getFillEventHistory(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getFillEventHistory(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, fillEventHistory);
}
@Override
- public UserData getUserData() throws RemoteException {
+ public void getUserData(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ UserData userData = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getUserData(getCallingUid());
+ userData = service.getUserData(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getUserData(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, userData);
}
@Override
- public String getUserDataId() throws RemoteException {
+ public void getUserDataId(@NonNull IResultReceiver receiver) throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ UserData userData = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- final UserData userData = service.getUserData(getCallingUid());
- return userData == null ? null : userData.getId();
+ userData = service.getUserData(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "getUserDataId(): no service for " + userId);
}
}
-
- return null;
+ final String userDataId = userData == null ? null : userData.getId();
+ send(receiver, userDataId);
}
@Override
@@ -964,89 +1000,96 @@ public final class AutofillManagerService extends SystemService {
}
@Override
- public boolean isFieldClassificationEnabled() throws RemoteException {
+ public void isFieldClassificationEnabled(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ boolean enabled = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.isFieldClassificationEnabled(getCallingUid());
+ enabled = service.isFieldClassificationEnabled(getCallingUid());
} else if (sVerbose) {
Slog.v(TAG, "isFieldClassificationEnabled(): no service for " + userId);
}
}
-
- return false;
+ send(receiver, enabled);
}
@Override
- public String getDefaultFieldClassificationAlgorithm() throws RemoteException {
+ public void getDefaultFieldClassificationAlgorithm(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ String algorithm = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getDefaultFieldClassificationAlgorithm(getCallingUid());
+ algorithm = service.getDefaultFieldClassificationAlgorithm(getCallingUid());
} else {
if (sVerbose) {
Slog.v(TAG, "getDefaultFcAlgorithm(): no service for " + userId);
}
- return null;
}
}
+ send(receiver, algorithm);
}
@Override
- public String[] getAvailableFieldClassificationAlgorithms() throws RemoteException {
+ public void getAvailableFieldClassificationAlgorithms(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ String[] algorithms = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getAvailableFieldClassificationAlgorithms(getCallingUid());
+ algorithms = service.getAvailableFieldClassificationAlgorithms(getCallingUid());
} else {
if (sVerbose) {
Slog.v(TAG, "getAvailableFcAlgorithms(): no service for " + userId);
}
- return null;
}
}
+ send(receiver, algorithms);
}
@Override
- public ComponentName getAutofillServiceComponentName() throws RemoteException {
+ public void getAutofillServiceComponentName(@NonNull IResultReceiver receiver)
+ throws RemoteException {
final int userId = UserHandle.getCallingUserId();
+ ComponentName componentName = null;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return service.getServiceComponentName();
+ componentName = service.getServiceComponentName();
} else if (sVerbose) {
Slog.v(TAG, "getAutofillServiceComponentName(): no service for " + userId);
}
}
-
- return null;
+ send(receiver, componentName);
}
@Override
- public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
+ public void restoreSession(int sessionId, @NonNull IBinder activityToken,
+ @NonNull IBinder appCallback, @NonNull IResultReceiver receiver)
throws RemoteException {
final int userId = UserHandle.getCallingUserId();
activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
appCallback = Preconditions.checkNotNull(appCallback, "appCallback");
+ boolean restored = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = mServicesCache.get(userId);
if (service != null) {
- return service.restoreSession(sessionId, getCallingUid(), activityToken,
+ restored = service.restoreSession(sessionId, getCallingUid(), activityToken,
appCallback);
} else if (sVerbose) {
Slog.v(TAG, "restoreSession(): no service for " + userId);
}
}
-
- return false;
+ send(receiver, restored);
}
@Override
@@ -1112,23 +1155,27 @@ public final class AutofillManagerService extends SystemService {
}
@Override
- public boolean isServiceSupported(int userId) {
+ public void isServiceSupported(int userId, @NonNull IResultReceiver receiver) {
+ boolean supported = false;
synchronized (mLock) {
- return !mDisabledUsers.get(userId);
+ supported = !mDisabledUsers.get(userId);
}
+ send(receiver, supported);
}
@Override
- public boolean isServiceEnabled(int userId, String packageName) {
+ public void isServiceEnabled(int userId, @NonNull String packageName,
+ @NonNull IResultReceiver receiver) {
+ boolean enabled = false;
synchronized (mLock) {
final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
if (service != null) {
- return Objects.equals(packageName, service.getServicePackageName());
+ enabled = Objects.equals(packageName, service.getServicePackageName());
} else if (sVerbose) {
Slog.v(TAG, "isServiceEnabled(): no service for " + userId);
}
- return false;
}
+ send(receiver, enabled);
}
@Override
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index de02e81a00c4..776cf47a1e56 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1549,6 +1549,22 @@ public class LocationManagerService extends ILocationManager.Stub {
return -1;
}
+ private static String resolutionLevelToOpStr(int allowedResolutionLevel) {
+ switch(allowedResolutionLevel) {
+ case RESOLUTION_LEVEL_COARSE:
+ return AppOpsManager.OPSTR_COARSE_LOCATION;
+ case RESOLUTION_LEVEL_FINE:
+ return AppOpsManager.OPSTR_FINE_LOCATION;
+ case RESOLUTION_LEVEL_NONE:
+ // The client is not allowed to get any location, so both FINE and COARSE ops will
+ // be denied. Pick the most restrictive one to be safe.
+ return AppOpsManager.OPSTR_FINE_LOCATION;
+ default:
+ // Use the most restrictive ops if not sure.
+ return AppOpsManager.OPSTR_FINE_LOCATION;
+ }
+ }
+
boolean reportLocationAccessNoThrow(
int pid, int uid, String packageName, int allowedResolutionLevel) {
int op = resolutionLevelToOp(allowedResolutionLevel);
@@ -2295,7 +2311,7 @@ public class LocationManagerService extends ILocationManager.Stub {
}
// Don't return stale location to apps with foreground-only location permission.
- String op = getResolutionPermission(allowedResolutionLevel);
+ String op = resolutionLevelToOpStr(allowedResolutionLevel);
long locationAgeMs = SystemClock.elapsedRealtime() -
location.getElapsedRealtimeNanos() / NANOS_PER_MILLI;
if ((locationAgeMs > mLastLocationMaxAgeMs)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 5028fd59124f..14eeb7853263 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -86,7 +86,6 @@ import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
import static android.os.Process.startWebView;
import static android.os.Process.zygoteProcess;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
import static android.provider.Settings.Global.ALWAYS_FINISH_ACTIVITIES;
import static android.provider.Settings.Global.DEBUG_APP;
import static android.provider.Settings.Global.NETWORK_ACCESS_TIMEOUT_MS;
@@ -164,7 +163,6 @@ import android.app.ActivityManagerInternal;
import android.app.ActivityManagerProto;
import android.app.ActivityOptions;
import android.app.ActivityThread;
-import android.app.AlertDialog;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManagerInternal.CheckOpsDelegate;
@@ -206,7 +204,6 @@ import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.IContentProvider;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
@@ -262,7 +259,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
import android.os.PowerManager.ServiceType;
import android.os.PowerManagerInternal;
import android.os.Process;
@@ -285,7 +281,6 @@ import android.os.storage.StorageManager;
import android.os.storage.StorageManagerInternal;
import android.provider.Downloads;
import android.provider.Settings;
-import android.service.voice.IVoiceInteractionSession;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.SuggestionSpan;
@@ -323,10 +318,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.DumpHeapActivity;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
-import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.ProcessMap;
import com.android.internal.app.SystemUserHomeActivity;
-import com.android.internal.app.procstats.AssociationState;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -364,21 +357,9 @@ import com.android.server.SystemService;
import com.android.server.SystemServiceManager;
import com.android.server.ThreadPriorityBooster;
import com.android.server.Watchdog;
-import com.android.server.am.ActivityManagerServiceDumpActivitiesProto;
-import com.android.server.am.ActivityManagerServiceDumpBroadcastsProto;
-import com.android.server.am.ActivityManagerServiceDumpProcessesProto;
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
-import com.android.server.am.ActivityManagerServiceDumpServicesProto;
-import com.android.server.am.ActivityManagerServiceProto;
import com.android.server.am.ActivityStack.ActivityState;
-import com.android.server.am.GrantUriProto;
-import com.android.server.am.ImportanceTokenProto;
-import com.android.server.am.MemInfoDumpProto;
import com.android.server.am.MemoryStatUtil.MemoryStat;
-import com.android.server.am.NeededUriGrantsProto;
-import com.android.server.am.ProcessOomProto;
-import com.android.server.am.ProcessToGcProto;
-import com.android.server.am.StickyBroadcastProto;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
import com.android.server.pm.Installer;
@@ -387,7 +368,6 @@ import com.android.server.pm.dex.DexManager;
import com.android.server.utils.PriorityDump;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.ActivityTaskManagerInternal.SleepToken;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParser;
@@ -540,6 +520,9 @@ public class ActivityManagerService extends IActivityManager.Stub
// Used to indicate that an app transition should be animated.
static final boolean ANIMATE = true;
+ // If set, we will push process association information in to procstats.
+ static final boolean TRACK_PROCSTATS_ASSOCIATIONS = true;
+
/**
* Default value for {@link Settings.Global#NETWORK_ACCESS_TIMEOUT_MS}.
*/
@@ -583,6 +566,8 @@ public class ActivityManagerService extends IActivityManager.Stub
public final IntentFirewall mIntentFirewall;
+ public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
+
// Whether we should use SCHED_FIFO for UI and RenderThreads.
private boolean mUseFifoUiScheduling = false;
@@ -2648,6 +2633,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mOnBattery = DEBUG_POWER ? true
: mBatteryStatsService.getActiveStatistics().getIsOnBattery();
mBatteryStatsService.getActiveStatistics().setCallback(this);
+ mOomAdjProfiler.batteryPowerChanged(mOnBattery);
mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats"));
@@ -2936,12 +2922,14 @@ public class ActivityManagerService extends IActivityManager.Stub
synchronized(mPidsSelfLocked) {
mOnBattery = DEBUG_POWER ? true : onBattery;
}
+ mOomAdjProfiler.batteryPowerChanged(onBattery);
}
}
@Override
public void batteryStatsReset() {
BinderCallsStatsService.reset();
+ mOomAdjProfiler.reset();
}
@Override
@@ -9020,7 +9008,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
ContentProviderConnection incProviderCountLocked(ProcessRecord r,
- final ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
+ final ContentProviderRecord cpr, IBinder externalProcessToken, int callingUid,
+ String callingTag, boolean stable) {
if (r != null) {
for (int i=0; i<r.conProviders.size(); i++) {
ContentProviderConnection conn = r.conProviders.get(i);
@@ -9055,7 +9044,7 @@ public class ActivityManagerService extends IActivityManager.Stub
cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName);
return conn;
}
- cpr.addExternalProcessHandleLocked(externalProcessToken);
+ cpr.addExternalProcessHandleLocked(externalProcessToken, callingUid, callingTag);
return null;
}
@@ -9132,7 +9121,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
private ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
- String name, IBinder token, boolean stable, int userId) {
+ String name, IBinder token, int callingUid, String callingTag, boolean stable,
+ int userId) {
ContentProviderRecord cpr;
ContentProviderConnection conn = null;
ProviderInfo cpi = null;
@@ -9225,7 +9215,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// In this case the provider instance already exists, so we can
// return it right away.
- conn = incProviderCountLocked(r, cpr, token, stable);
+ conn = incProviderCountLocked(r, cpr, token, callingUid, callingTag, stable);
if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
// If this is a perceptible app accessing the provider,
@@ -9469,7 +9459,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mProviderMap.putProviderByName(name, cpr);
- conn = incProviderCountLocked(r, cpr, token, stable);
+ conn = incProviderCountLocked(r, cpr, token, callingUid, callingTag, stable);
if (conn != null) {
conn.waiting = true;
}
@@ -9580,21 +9570,23 @@ public class ActivityManagerService extends IActivityManager.Stub
}
// The incoming user check is now handled in checkContentProviderPermissionLocked() to deal
// with cross-user grant.
- return getContentProviderImpl(caller, name, null, stable, userId);
+ return getContentProviderImpl(caller, name, null, Binder.getCallingUid(), null, stable,
+ userId);
}
public ContentProviderHolder getContentProviderExternal(
- String name, int userId, IBinder token) {
+ String name, int userId, IBinder token, String tag) {
enforceCallingPermission(android.Manifest.permission.ACCESS_CONTENT_PROVIDERS_EXTERNALLY,
"Do not have permission in call getContentProviderExternal()");
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
userId, false, ALLOW_FULL_ONLY, "getContentProvider", null);
- return getContentProviderExternalUnchecked(name, token, userId);
+ return getContentProviderExternalUnchecked(name, token, Binder.getCallingUid(),
+ tag != null ? tag : "*external*", userId);
}
private ContentProviderHolder getContentProviderExternalUnchecked(String name,
- IBinder token, int userId) {
- return getContentProviderImpl(null, name, token, true, userId);
+ IBinder token, int callingUid, String callingTag, int userId) {
+ return getContentProviderImpl(null, name, token, callingUid, callingTag, true, userId);
}
/**
@@ -9986,7 +9978,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
ContentProviderHolder holder = null;
try {
- holder = getContentProviderExternalUnchecked(name, null, userId);
+ holder = getContentProviderExternalUnchecked(name, null, callingUid,
+ "*getmimetype*", userId);
if (holder != null) {
return holder.provider.getType(uri);
}
@@ -10201,7 +10194,8 @@ public class ActivityManagerService extends IActivityManager.Stub
final int userId = UserHandle.getCallingUserId();
final Uri uri = Uri.parse(uriString);
String name = uri.getAuthority();
- ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null, userId);
+ ContentProviderHolder cph = getContentProviderExternalUnchecked(name, null,
+ Binder.getCallingUid(), "*opencontent*", userId);
ParcelFileDescriptor pfd = null;
if (cph != null) {
// We record the binder invoker's uid in thread-local storage before
@@ -10257,6 +10251,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mServices.updateScreenStateLocked(isAwake);
reportCurWakefulnessUsageEventLocked();
mActivityTaskManager.onScreenAwakeChanged(isAwake);
+ mOomAdjProfiler.onWakefulnessChanged(wakefulness);
}
updateOomAdjLocked();
}
@@ -12127,7 +12122,7 @@ public class ActivityManagerService extends IActivityManager.Stub
return imp;
}
- private void fillInProcMemInfoLocked(ProcessRecord app,
+ private void fillInProcMemInfo(ProcessRecord app,
ActivityManager.RunningAppProcessInfo outInfo,
int clientTargetSdk) {
outInfo.pid = app.pid;
@@ -12147,8 +12142,6 @@ public class ActivityManagerService extends IActivityManager.Stub
outInfo.importance = procStateToImportance(procState, adj, outInfo, clientTargetSdk);
outInfo.importanceReasonCode = app.adjTypeCode;
outInfo.processState = app.curProcState;
- outInfo.isFocused = (app == getTopAppLocked());
- outInfo.lastActivityTime = app.lastActivityTime;
}
@Override
@@ -12179,7 +12172,7 @@ public class ActivityManagerService extends IActivityManager.Stub
ActivityManager.RunningAppProcessInfo currApp =
new ActivityManager.RunningAppProcessInfo(app.processName,
app.pid, app.getPackageList());
- fillInProcMemInfoLocked(app, currApp, clientTargetSdk);
+ fillInProcMemInfo(app, currApp, clientTargetSdk);
if (app.adjSource instanceof ProcessRecord) {
currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid;
currApp.importanceReasonImportance =
@@ -12248,7 +12241,7 @@ public class ActivityManagerService extends IActivityManager.Stub
proc = mPidsSelfLocked.get(Binder.getCallingPid());
}
if (proc != null) {
- fillInProcMemInfoLocked(proc, outState, clientTargetSdk);
+ fillInProcMemInfo(proc, outState, clientTargetSdk);
}
}
}
@@ -12746,6 +12739,11 @@ public class ActivityManagerService extends IActivityManager.Stub
pw.println("-------------------------------------------------------------------------------");
}
dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
+ pw.println();
+ if (dumpAll) {
+ pw.println("-------------------------------------------------------------------------------");
+ }
+ mOomAdjProfiler.dump(pw);
}
}
Binder.restoreCallingIdentity(origId);
@@ -16031,6 +16029,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
updateCpuStatsNow();
long[] memtrackTmp = new long[1];
+ long[] swaptrackTmp = new long[2];
final List<ProcessCpuTracker.Stats> stats;
// Get a list of Stats that have vsize > 0
synchronized (mProcessCpuTracker) {
@@ -16041,12 +16040,13 @@ public class ActivityManagerService extends IActivityManager.Stub
final int statsCount = stats.size();
for (int i = 0; i < statsCount; i++) {
ProcessCpuTracker.Stats st = stats.get(i);
- long pss = Debug.getPss(st.pid, null, memtrackTmp);
+ long pss = Debug.getPss(st.pid, swaptrackTmp, memtrackTmp);
if (pss > 0) {
if (infoMap.indexOfKey(st.pid) < 0) {
ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid,
ProcessList.NATIVE_ADJ, -1, "native", null);
mi.pss = pss;
+ mi.swapPss = swaptrackTmp[1];
mi.memtrack = memtrackTmp[0];
memInfos.add(mi);
}
@@ -16054,14 +16054,17 @@ public class ActivityManagerService extends IActivityManager.Stub
}
long totalPss = 0;
+ long totalSwapPss = 0;
long totalMemtrack = 0;
for (int i=0, N=memInfos.size(); i<N; i++) {
ProcessMemInfo mi = memInfos.get(i);
if (mi.pss == 0) {
- mi.pss = Debug.getPss(mi.pid, null, memtrackTmp);
+ mi.pss = Debug.getPss(mi.pid, swaptrackTmp, memtrackTmp);
+ mi.swapPss = swaptrackTmp[1];
mi.memtrack = memtrackTmp[0];
}
totalPss += mi.pss;
+ totalSwapPss += mi.swapPss;
totalMemtrack += mi.memtrack;
}
Collections.sort(memInfos, new Comparator<ProcessMemInfo>() {
@@ -16223,7 +16226,7 @@ public class ActivityManagerService extends IActivityManager.Stub
memInfoBuilder.append("\n");
memInfoBuilder.append(" Lost RAM: ");
memInfoBuilder.append(stringifyKBSize(memInfo.getTotalSizeKb()
- - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
+ - (totalPss - totalSwapPss) - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
- memInfo.getKernelUsedSizeKb() - memInfo.getZramTotalSizeKb()));
memInfoBuilder.append("\n");
Slog.i(TAG, "Low on memory:");
@@ -18889,6 +18892,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// The process is being computed, so there is a cycle. We cannot
// rely on this process's state.
app.containsCycle = true;
+
return false;
}
}
@@ -18913,6 +18917,7 @@ public class ActivityManagerService extends IActivityManager.Stub
final int logUid = mCurOomAdjUid;
int prevAppAdj = app.curAdj;
+ int prevProcState = app.curProcState;
if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
// The max adjustment doesn't allow this app to be anything
@@ -19281,19 +19286,25 @@ public class ActivityManagerService extends IActivityManager.Stub
// all connected clients.
ConnectionRecord cr = clist.get(i);
if (cr.binding.client == app) {
- // Binding to ourself is not interesting.
+ // Binding to oneself is not interesting.
continue;
}
+ boolean trackedProcState = false;
if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
ProcessRecord client = cr.binding.client;
computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
if (client.containsCycle) {
- // We've detected a cycle. We should ignore this connection and allow
- // this process to retry computeOomAdjLocked later in case a later-checked
- // connection from a client would raise its priority legitimately.
+ // We've detected a cycle. We should retry computeOomAdjLocked later in
+ // case a later-checked connection from a client would raise its
+ // priority legitimately.
app.containsCycle = true;
- continue;
+ // If the client has not been completely evaluated, skip using its
+ // priority. Else use the conservative value for now and look for a
+ // better state in the next iteration.
+ if (client.completedAdjSeq < mAdjSeq) {
+ continue;
+ }
}
int clientAdj = client.curRawAdj;
int clientProcState = client.curProcState;
@@ -19356,6 +19367,8 @@ public class ActivityManagerService extends IActivityManager.Stub
newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
procState = ActivityManager.PROCESS_STATE_PERSISTENT;
+ cr.trackProcState(procState, mAdjSeq, now);
+ trackedProcState = true;
}
} else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
&& clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
@@ -19441,6 +19454,9 @@ public class ActivityManagerService extends IActivityManager.Stub
ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
}
}
+ if (!trackedProcState) {
+ cr.trackProcState(clientProcState, mAdjSeq, now);
+ }
if (procState > clientProcState) {
procState = clientProcState;
if (adjType == null) {
@@ -19517,11 +19533,16 @@ public class ActivityManagerService extends IActivityManager.Stub
}
computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);
if (client.containsCycle) {
- // We've detected a cycle. We should ignore this connection and allow
- // this process to retry computeOomAdjLocked later in case a later-checked
- // connection from a client would raise its priority legitimately.
+ // We've detected a cycle. We should retry computeOomAdjLocked later in
+ // case a later-checked connection from a client would raise its
+ // priority legitimately.
app.containsCycle = true;
- continue;
+ // If the client has not been completely evaluated, skip using its
+ // priority. Else use the conservative value for now and look for a
+ // better state in the next iteration.
+ if (client.completedAdjSeq < mAdjSeq) {
+ continue;
+ }
}
int clientAdj = client.curRawAdj;
int clientProcState = client.curProcState;
@@ -19570,6 +19591,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
}
+ conn.trackProcState(clientProcState, mAdjSeq, now);
if (procState > clientProcState) {
procState = clientProcState;
}
@@ -19753,8 +19775,8 @@ public class ActivityManagerService extends IActivityManager.Stub
app.foregroundActivities = foregroundActivities;
app.completedAdjSeq = mAdjSeq;
- // if curAdj is less than prevAppAdj, then this process was promoted
- return app.curAdj < prevAppAdj;
+ // if curAdj or curProcState improved, then this process was promoted
+ return app.curAdj < prevAppAdj || app.curProcState < prevProcState;
}
/**
@@ -20668,23 +20690,22 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@GuardedBy("this")
- ProcessRecord getTopAppLocked() {
- final ActivityRecord TOP_ACT = resumedAppLocked();
- if (TOP_ACT != null && TOP_ACT.hasProcess()) {
- return (ProcessRecord) TOP_ACT.app.mOwner;
- } else {
- return null;
- }
- }
-
- @GuardedBy("this")
final void updateOomAdjLocked() {
- final ProcessRecord TOP_APP = getTopAppLocked();
+ mOomAdjProfiler.oomAdjStarted();
+ final ActivityRecord TOP_ACT = resumedAppLocked();
+ final ProcessRecord TOP_APP = TOP_ACT != null && TOP_ACT.hasProcess()
+ ? (ProcessRecord) TOP_ACT.app.mOwner : null;
final long now = SystemClock.uptimeMillis();
final long nowElapsed = SystemClock.elapsedRealtime();
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final int N = mLruProcesses.size();
+ if (false) {
+ RuntimeException e = new RuntimeException();
+ e.fillInStackTrace();
+ Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);
+ }
+
// Reset state in all uid records.
for (int i=mActiveUids.size()-1; i>=0; i--) {
final UidRecord uidRec = mActiveUids.valueAt(i);
@@ -20818,7 +20839,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// - Continue retrying until no process was promoted.
// - Iterate from least important to most important.
int cycleCount = 0;
- while (retryCycles) {
+ while (retryCycles && cycleCount < 10) {
cycleCount++;
retryCycles = false;
@@ -20833,12 +20854,14 @@ public class ActivityManagerService extends IActivityManager.Stub
for (int i=0; i<N; i++) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+
if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now)) {
retryCycles = true;
}
}
}
}
+
for (int i=N-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
@@ -20902,6 +20925,8 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
+
incrementProcStateSeqAndNotifyAppsLocked();
mNumServiceProcs = mNewNumServiceProcs;
@@ -21178,6 +21203,7 @@ public class ActivityManagerService extends IActivityManager.Stub
Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
}
}
+ mOomAdjProfiler.oomAdjEnded();
}
@Override
diff --git a/services/core/java/com/android/server/am/AppTaskImpl.java b/services/core/java/com/android/server/am/AppTaskImpl.java
index c22dedc4073f..a1f1ff9cc3ec 100644
--- a/services/core/java/com/android/server/am/AppTaskImpl.java
+++ b/services/core/java/com/android/server/am/AppTaskImpl.java
@@ -97,7 +97,7 @@ class AppTaskImpl extends IAppTask.Stub {
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
- synchronized (this) {
+ synchronized (mService.mGlobalLock) {
mService.mStackSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId,
null);
}
diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java
index c348aeee8b86..fa8e6c431770 100644
--- a/services/core/java/com/android/server/am/ConnectionRecord.java
+++ b/services/core/java/com/android/server/am/ConnectionRecord.java
@@ -106,9 +106,14 @@ final class ConnectionRecord {
}
public void startAssociationIfNeeded() {
- if (association == null) {
- ProcessStats.ProcessStateHolder holder = binding.service.app != null
- ? binding.service.app.pkgList.get(binding.service.name.getPackageName()) : null;
+ // If we don't already have an active association, create one... but only if this
+ // is an association between two different processes.
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+ && association == null && binding.service.app != null
+ && (binding.service.appInfo.uid != clientUid
+ || !binding.service.processName.equals(clientProcessName))) {
+ ProcessStats.ProcessStateHolder holder = binding.service.app.pkgList.get(
+ binding.service.name.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced service "
+ binding.service.name.toShortString() + ": proc=" + binding.service.app);
@@ -116,7 +121,7 @@ final class ConnectionRecord {
Slog.wtf(TAG_AM, "Inactive holder in referenced service "
+ binding.service.name.toShortString() + ": proc=" + binding.service.app);
} else {
- association = holder.pkg.getAssociationStateLocked(binding.service.processName,
+ association = holder.pkg.getAssociationStateLocked(holder.state,
binding.service.name.getClassName()).startSource(clientUid,
clientProcessName);
@@ -124,6 +129,12 @@ final class ConnectionRecord {
}
}
+ public void trackProcState(int procState, int seq, long now) {
+ if (association != null) {
+ association.trackProcState(procState, seq, now);
+ }
+ }
+
public void stopAssociation() {
if (association != null) {
association.stop();
diff --git a/services/core/java/com/android/server/am/ContentProviderConnection.java b/services/core/java/com/android/server/am/ContentProviderConnection.java
index 0320b10b9a7a..f2d4f739c8bf 100644
--- a/services/core/java/com/android/server/am/ContentProviderConnection.java
+++ b/services/core/java/com/android/server/am/ContentProviderConnection.java
@@ -53,9 +53,14 @@ public final class ContentProviderConnection extends Binder {
}
public void startAssociationIfNeeded() {
- if (association == null) {
- ProcessStats.ProcessStateHolder holder = provider.proc != null
- ? provider.proc.pkgList.get(provider.name.getPackageName()) : null;
+ // If we don't already have an active association, create one... but only if this
+ // is an association between two different processes.
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+ && association == null && provider.proc != null
+ && (provider.appInfo.uid != client.uid
+ || !provider.info.processName.equals(client.processName))) {
+ ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+ provider.name.getPackageName());
if (holder == null) {
Slog.wtf(TAG_AM, "No package in referenced provider "
+ provider.name.toShortString() + ": proc=" + provider.proc);
@@ -63,13 +68,19 @@ public final class ContentProviderConnection extends Binder {
Slog.wtf(TAG_AM, "Inactive holder in referenced provider "
+ provider.name.toShortString() + ": proc=" + provider.proc);
} else {
- association = holder.pkg.getAssociationStateLocked(provider.info.processName,
+ association = holder.pkg.getAssociationStateLocked(holder.state,
provider.name.getClassName()).startSource(client.uid, client.processName);
}
}
}
+ public void trackProcState(int procState, int seq, long now) {
+ if (association != null) {
+ association.trackProcState(procState, seq, now);
+ }
+ }
+
public void stopAssociation() {
if (association != null) {
association.stop();
diff --git a/services/core/java/com/android/server/am/ContentProviderRecord.java b/services/core/java/com/android/server/am/ContentProviderRecord.java
index 07ae1aedc686..c22966d631f6 100644
--- a/services/core/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/core/java/com/android/server/am/ContentProviderRecord.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+
import android.app.ContentProviderHolder;
import android.content.ComponentName;
import android.content.IContentProvider;
@@ -26,11 +28,14 @@ import android.os.IBinder.DeathRecipient;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.ArrayMap;
import android.util.Slog;
+import com.android.internal.app.procstats.AssociationState;
+import com.android.internal.app.procstats.ProcessStats;
+
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
final class ContentProviderRecord implements ComponentName.WithComponentName {
final ActivityManagerService service;
@@ -46,7 +51,7 @@ final class ContentProviderRecord implements ComponentName.WithComponentName {
= new ArrayList<ContentProviderConnection>();
//final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
// Handles for non-framework processes supported by this provider
- HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
+ ArrayMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
// Count for external process for which we have no handles.
int externalProcessNoHandleCount;
ProcessRecord proc; // if non-null, hosting process.
@@ -85,12 +90,24 @@ final class ContentProviderRecord implements ComponentName.WithComponentName {
public void setProcess(ProcessRecord proc) {
this.proc = proc;
- for (int iconn = connections.size() - 1; iconn >= 0; iconn--) {
- final ContentProviderConnection conn = connections.get(iconn);
- if (proc != null) {
- conn.startAssociationIfNeeded();
- } else {
- conn.stopAssociation();
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
+ for (int iconn = connections.size() - 1; iconn >= 0; iconn--) {
+ final ContentProviderConnection conn = connections.get(iconn);
+ if (proc != null) {
+ conn.startAssociationIfNeeded();
+ } else {
+ conn.stopAssociation();
+ }
+ }
+ if (externalProcessTokenToHandle != null) {
+ for (int iext = externalProcessTokenToHandle.size() - 1; iext >= 0; iext--) {
+ final ExternalProcessHandle handle = externalProcessTokenToHandle.valueAt(iext);
+ if (proc != null) {
+ handle.startAssociationIfNeeded(this);
+ } else {
+ handle.stopAssociation();
+ }
+ }
}
}
}
@@ -100,17 +117,18 @@ final class ContentProviderRecord implements ComponentName.WithComponentName {
&& uid == app.info.uid;
}
- public void addExternalProcessHandleLocked(IBinder token) {
+ public void addExternalProcessHandleLocked(IBinder token, int callingUid, String callingTag) {
if (token == null) {
externalProcessNoHandleCount++;
} else {
if (externalProcessTokenToHandle == null) {
- externalProcessTokenToHandle = new HashMap<IBinder, ExternalProcessHandle>();
+ externalProcessTokenToHandle = new ArrayMap<>();
}
ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
if (handle == null) {
- handle = new ExternalProcessHandle(token);
+ handle = new ExternalProcessHandle(token, callingUid, callingTag);
externalProcessTokenToHandle.put(token, handle);
+ handle.startAssociationIfNeeded(this);
}
handle.mAcquisitionCount++;
}
@@ -141,6 +159,7 @@ final class ContentProviderRecord implements ComponentName.WithComponentName {
private void removeExternalProcessHandleInternalLocked(IBinder token) {
ExternalProcessHandle handle = externalProcessTokenToHandle.get(token);
handle.unlinkFromOwnDeathLocked();
+ handle.stopAssociation();
externalProcessTokenToHandle.remove(token);
if (externalProcessTokenToHandle.size() == 0) {
externalProcessTokenToHandle = null;
@@ -246,11 +265,16 @@ final class ContentProviderRecord implements ComponentName.WithComponentName {
private class ExternalProcessHandle implements DeathRecipient {
private static final String LOG_TAG = "ExternalProcessHanldle";
- private final IBinder mToken;
- private int mAcquisitionCount;
+ final IBinder mToken;
+ final int mOwningUid;
+ final String mOwningProcessName;
+ int mAcquisitionCount;
+ AssociationState.SourceState mAssociation;
- public ExternalProcessHandle(IBinder token) {
+ public ExternalProcessHandle(IBinder token, int owningUid, String owningProcessName) {
mToken = token;
+ mOwningUid = owningUid;
+ mOwningProcessName = owningProcessName;
try {
token.linkToDeath(this, 0);
} catch (RemoteException re) {
@@ -262,6 +286,37 @@ final class ContentProviderRecord implements ComponentName.WithComponentName {
mToken.unlinkToDeath(this, 0);
}
+ public void startAssociationIfNeeded(ContentProviderRecord provider) {
+ // If we don't already have an active association, create one... but only if this
+ // is an association between two different processes.
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS
+ && mAssociation == null && provider.proc != null
+ && (provider.appInfo.uid != mOwningUid
+ || !provider.info.processName.equals(mOwningProcessName))) {
+ ProcessStats.ProcessStateHolder holder = provider.proc.pkgList.get(
+ provider.name.getPackageName());
+ if (holder == null) {
+ Slog.wtf(TAG_AM, "No package in referenced provider "
+ + provider.name.toShortString() + ": proc=" + provider.proc);
+ } else if (holder.pkg == null) {
+ Slog.wtf(TAG_AM, "Inactive holder in referenced provider "
+ + provider.name.toShortString() + ": proc=" + provider.proc);
+ } else {
+ mAssociation = holder.pkg.getAssociationStateLocked(holder.state,
+ provider.name.getClassName()).startSource(mOwningUid,
+ mOwningProcessName);
+
+ }
+ }
+ }
+
+ public void stopAssociation() {
+ if (mAssociation != null) {
+ mAssociation.stop();
+ mAssociation = null;
+ }
+ }
+
@Override
public void binderDied() {
synchronized (service) {
diff --git a/services/core/java/com/android/server/am/OomAdjProfiler.java b/services/core/java/com/android/server/am/OomAdjProfiler.java
new file mode 100644
index 000000000000..6230e0dfaa55
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjProfiler.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.PowerManagerInternal;
+import android.os.Process;
+import android.os.SystemClock;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.util.RingBuffer;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.io.PrintWriter;
+
+public class OomAdjProfiler {
+ @GuardedBy("this")
+ private boolean mOnBattery;
+ @GuardedBy("this")
+ private boolean mScreenOff;
+
+ @GuardedBy("this")
+ private long mOomAdjStartTimeMs;
+ @GuardedBy("this")
+ private boolean mOomAdjStarted;
+
+ @GuardedBy("this")
+ private CpuTimes mOomAdjRunTime = new CpuTimes();
+ @GuardedBy("this")
+ private CpuTimes mSystemServerCpuTime = new CpuTimes();
+
+ @GuardedBy("this")
+ private long mLastSystemServerCpuTimeMs;
+ @GuardedBy("this")
+ private boolean mSystemServerCpuTimeUpdateScheduled;
+ private final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker(false);
+
+ @GuardedBy("this")
+ final RingBuffer<CpuTimes> mOomAdjRunTimesHist = new RingBuffer<>(CpuTimes.class, 10);
+ @GuardedBy("this")
+ final RingBuffer<CpuTimes> mSystemServerCpuTimesHist = new RingBuffer<>(CpuTimes.class, 10);
+
+ void batteryPowerChanged(boolean onBattery) {
+ synchronized (this) {
+ scheduleSystemServerCpuTimeUpdate();
+ mOnBattery = onBattery;
+ }
+ }
+
+ void onWakefulnessChanged(int wakefulness) {
+ synchronized (this) {
+ scheduleSystemServerCpuTimeUpdate();
+ mScreenOff = wakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE;
+ }
+ }
+
+ void oomAdjStarted() {
+ synchronized (this) {
+ mOomAdjStartTimeMs = SystemClock.currentThreadTimeMillis();
+ mOomAdjStarted = true;
+ }
+ }
+
+ void oomAdjEnded() {
+ synchronized (this) {
+ if (!mOomAdjStarted) {
+ return;
+ }
+ mOomAdjRunTime.addCpuTimeMs(SystemClock.currentThreadTimeMillis() - mOomAdjStartTimeMs);
+ }
+ }
+
+ private void scheduleSystemServerCpuTimeUpdate() {
+ synchronized (this) {
+ if (mSystemServerCpuTimeUpdateScheduled) {
+ return;
+ }
+ mSystemServerCpuTimeUpdateScheduled = true;
+ BackgroundThread.getHandler().post(PooledLambda.obtainRunnable(
+ OomAdjProfiler::updateSystemServerCpuTime,
+ this, mOnBattery, mScreenOff).recycleOnUse());
+ }
+ }
+
+ private void updateSystemServerCpuTime(boolean onBattery, boolean screenOff) {
+ final long cpuTimeMs = mProcessCpuTracker.getCpuTimeForPid(Process.myPid());
+ synchronized (this) {
+ mSystemServerCpuTime.addCpuTimeMs(
+ cpuTimeMs - mLastSystemServerCpuTimeMs, onBattery, screenOff);
+ mLastSystemServerCpuTimeMs = cpuTimeMs;
+ mSystemServerCpuTimeUpdateScheduled = false;
+ notifyAll();
+ }
+ }
+
+ void reset() {
+ synchronized (this) {
+ if (mSystemServerCpuTime.isEmpty()) {
+ return;
+ }
+ mOomAdjRunTimesHist.append(mOomAdjRunTime);
+ mSystemServerCpuTimesHist.append(mSystemServerCpuTime);
+ mOomAdjRunTime = new CpuTimes();
+ mSystemServerCpuTime = new CpuTimes();
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ synchronized (this) {
+ if (mSystemServerCpuTimeUpdateScheduled) {
+ while (mSystemServerCpuTimeUpdateScheduled) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ } else {
+ updateSystemServerCpuTime(mOnBattery, mScreenOff);
+ }
+
+ pw.println("System server and oomAdj runtimes (ms) in recent battery sessions "
+ + "(most recent first):");
+ if (!mSystemServerCpuTime.isEmpty()) {
+ pw.print(" ");
+ pw.print("system_server=");
+ pw.print(mSystemServerCpuTime);
+ pw.print(" ");
+ pw.print("oom_adj=");
+ pw.println(mOomAdjRunTime);
+ }
+ final CpuTimes[] systemServerCpuTimes = mSystemServerCpuTimesHist.toArray();
+ final CpuTimes[] oomAdjRunTimes = mOomAdjRunTimesHist.toArray();
+ for (int i = oomAdjRunTimes.length - 1; i >= 0; --i) {
+ pw.print(" ");
+ pw.print("system_server=");
+ pw.print(systemServerCpuTimes[i]);
+ pw.print(" ");
+ pw.print("oom_adj=");
+ pw.println(oomAdjRunTimes[i]);
+ }
+ }
+ }
+
+ private class CpuTimes {
+ private long mOnBatteryTimeMs;
+ private long mOnBatteryScreenOffTimeMs;
+
+ public void addCpuTimeMs(long cpuTimeMs) {
+ addCpuTimeMs(cpuTimeMs, mOnBattery, mScreenOff);
+ }
+
+ public void addCpuTimeMs(long cpuTimeMs, boolean onBattery, boolean screenOff) {
+ if (onBattery) {
+ mOnBatteryTimeMs += cpuTimeMs;
+ if (screenOff) {
+ mOnBatteryScreenOffTimeMs += cpuTimeMs;
+ }
+ }
+ }
+
+ public boolean isEmpty() {
+ return mOnBatteryTimeMs == 0 && mOnBatteryScreenOffTimeMs == 0;
+ }
+
+ public String toString() {
+ return "[" + mOnBatteryTimeMs + "," + mOnBatteryScreenOffTimeMs + "]";
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/ProcessMemInfo.java b/services/core/java/com/android/server/am/ProcessMemInfo.java
index 83d29e2bba68..6c10a2af9fe6 100644
--- a/services/core/java/com/android/server/am/ProcessMemInfo.java
+++ b/services/core/java/com/android/server/am/ProcessMemInfo.java
@@ -24,6 +24,7 @@ public class ProcessMemInfo {
final String adjType;
final String adjReason;
long pss;
+ long swapPss;
long memtrack;
public ProcessMemInfo(String _name, int _pid, int _oomAdj, int _procState,
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index e1bc1bccdb14..f0bd8fa31478 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -32,7 +32,6 @@ import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.procstats.AssociationState;
import com.android.internal.app.procstats.DumpUtils;
import com.android.internal.app.procstats.IProcessStats;
import com.android.internal.app.procstats.ProcessState;
@@ -194,6 +193,11 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
@GuardedBy("mAm")
+ public void updateTrackingAssociationsLocked(int curSeq, long now) {
+ mProcessStats.updateTrackingAssociationsLocked(curSeq, now);
+ }
+
+ @GuardedBy("mAm")
public boolean shouldWriteNowLocked(long now) {
if (now > (mLastWriteTime+WRITE_PERIOD)) {
if (SystemClock.elapsedRealtime()
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index f72b8010429b..8f436206f480 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -502,14 +502,16 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
public void setProcess(ProcessRecord _proc) {
app = _proc;
- for (int conni=connections.size()-1; conni>=0; conni--) {
- ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
- for (int i=0; i<cr.size(); i++) {
- final ConnectionRecord conn = cr.get(i);
- if (_proc != null) {
- conn.startAssociationIfNeeded();
- } else {
- conn.stopAssociation();
+ if (ActivityManagerService.TRACK_PROCSTATS_ASSOCIATIONS) {
+ for (int conni = connections.size() - 1; conni >= 0; conni--) {
+ ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
+ for (int i = 0; i < cr.size(); i++) {
+ final ConnectionRecord conn = cr.get(i);
+ if (_proc != null) {
+ conn.startAssociationIfNeeded();
+ } else {
+ conn.stopAssociation();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
new file mode 100644
index 000000000000..9e11eb0fa603
--- /dev/null
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -0,0 +1,70 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsActivityManagerDeviceTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsActivityManagerDeviceSdk25TestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsAppTestCases",
+ "options": [
+ {
+ "include-filter": "android.app.cts.TaskDescriptionTest"
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.am."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsActivityManagerDeviceTestCases"
+ },
+ {
+ "name": "CtsActivityManagerDeviceSdk25TestCases"
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.am."
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 089632dbe01c..bfcc629541f8 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -16,12 +16,10 @@
package com.android.server.content;
+import android.annotation.Nullable;
import android.app.job.JobParameters;
import android.app.job.JobService;
-import android.content.Intent;
import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;
import android.util.Slog;
@@ -34,78 +32,86 @@ import com.android.internal.annotations.GuardedBy;
public class SyncJobService extends JobService {
private static final String TAG = "SyncManager";
- public static final String EXTRA_MESSENGER = "messenger";
+ private static final Object sLock = new Object();
- private Messenger mMessenger;
+ @GuardedBy("sLock")
+ private static SyncJobService sInstance;
- private final Object mLock = new Object();
+ @GuardedBy("sLock")
+ private static final SparseArray<JobParameters> sJobParamsMap = new SparseArray<>();
- @GuardedBy("mLock")
- private final SparseArray<JobParameters> mJobParamsMap = new SparseArray<>();
+ @GuardedBy("sLock")
+ private static final SparseBooleanArray sStartedSyncs = new SparseBooleanArray();
- @GuardedBy("mLock")
- private final SparseBooleanArray mStartedSyncs = new SparseBooleanArray();
+ @GuardedBy("sLock")
+ private static final SparseLongArray sJobStartUptimes = new SparseLongArray();
- @GuardedBy("mLock")
- private final SparseLongArray mJobStartUptimes = new SparseLongArray();
+ private static final SyncLogger sLogger = SyncLogger.getInstance();
- private final SyncLogger mLogger = SyncLogger.getInstance();
-
- /**
- * This service is started by the SyncManager which passes a messenger object to
- * communicate back with it. It never stops while the device is running.
- */
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- mMessenger = intent.getParcelableExtra(EXTRA_MESSENGER);
- Message m = Message.obtain();
- m.what = SyncManager.SyncHandler.MESSAGE_JOBSERVICE_OBJECT;
- m.obj = this;
- sendMessage(m);
-
- return START_NOT_STICKY;
+ private void updateInstance() {
+ synchronized (SyncJobService.class) {
+ sInstance = this;
+ }
}
- private void sendMessage(Message message) {
- if (mMessenger == null) {
- Slog.e(TAG, "Messenger not initialized.");
- return;
+ @Nullable
+ private static SyncJobService getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ Slog.wtf(TAG, "sInstance == null");
+ }
+ return sInstance;
}
- try {
- mMessenger.send(message);
- } catch (RemoteException e) {
- Slog.e(TAG, e.toString());
+ }
+
+ public static boolean isReady() {
+ synchronized (sLock) {
+ return sInstance != null;
}
}
@Override
public boolean onStartJob(JobParameters params) {
+ updateInstance();
+
+ sLogger.purgeOldLogs();
- mLogger.purgeOldLogs();
+ SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+
+ if (op == null) {
+ Slog.wtf(TAG, "Got invalid job " + params.getJobId());
+ return false;
+ }
+
+ final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
+
+ sLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op,
+ " readyToSync", readyToSync);
+
+ if (!readyToSync) {
+ // If the user isn't unlocked or the device has been provisioned yet, just stop the job
+ // at this point. If it's a non-periodic sync, ask the job scheduler to reschedule it.
+ // If it's a periodic sync, then just wait until the next cycle.
+ final boolean wantsReschedule = !op.isPeriodic;
+ jobFinished(params, wantsReschedule);
+ return true;
+ }
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
- synchronized (mLock) {
+ synchronized (sLock) {
final int jobId = params.getJobId();
- mJobParamsMap.put(jobId, params);
+ sJobParamsMap.put(jobId, params);
- mStartedSyncs.delete(jobId);
- mJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
+ sStartedSyncs.delete(jobId);
+ sJobStartUptimes.put(jobId, SystemClock.uptimeMillis());
}
Message m = Message.obtain();
m.what = SyncManager.SyncHandler.MESSAGE_START_SYNC;
- SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
-
- mLogger.log("onStartJob() jobid=", params.getJobId(), " op=", op);
-
- if (op == null) {
- Slog.e(TAG, "Got invalid job " + params.getJobId());
- return false;
- }
if (isLoggable) {
Slog.v(TAG, "Got start job message " + op.target);
}
m.obj = op;
- sendMessage(m);
+ SyncManager.sendMessage(m);
return true;
}
@@ -115,15 +121,22 @@ public class SyncJobService extends JobService {
Slog.v(TAG, "onStopJob called " + params.getJobId() + ", reason: "
+ params.getStopReason());
}
- final boolean readyToSync = SyncManager.readyToSync();
+ final SyncOperation op = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
+ if (op == null) {
+ Slog.wtf(TAG, "Got invalid job " + params.getJobId());
+ return false;
+ }
- mLogger.log("onStopJob() ", mLogger.jobParametersToString(params),
+ final boolean readyToSync = SyncManager.readyToSync(op.target.userId);
+
+ sLogger.log("onStopJob() ", sLogger.jobParametersToString(params),
" readyToSync=", readyToSync);
- synchronized (mLock) {
+
+ synchronized (sLock) {
final int jobId = params.getJobId();
- mJobParamsMap.remove(jobId);
+ sJobParamsMap.remove(jobId);
- final long startUptime = mJobStartUptimes.get(jobId);
+ final long startUptime = sJobStartUptimes.get(jobId);
final long nowUptime = SystemClock.uptimeMillis();
final long runtime = nowUptime - startUptime;
@@ -135,61 +148,57 @@ public class SyncJobService extends JobService {
// WTF if startSyncH() hasn't happened, *unless* onStopJob() was called too soon.
// (1 minute threshold.)
// Also don't wtf when it's not ready to sync.
- if (readyToSync && !mStartedSyncs.get(jobId)) {
+ if (readyToSync && !sStartedSyncs.get(jobId)) {
wtf("Job " + jobId + " didn't start: "
+ " startUptime=" + startUptime
+ " nowUptime=" + nowUptime
+ " params=" + jobParametersToString(params));
}
- } else if (runtime < 10 * 1000) {
- // This happens too in a normal case too, and it's rather too often.
- // Disable it for now.
-// // Job stopped too soon. WTF.
-// wtf("Job " + jobId + " stopped in " + runtime + " ms: "
-// + " startUptime=" + startUptime
-// + " nowUptime=" + nowUptime
-// + " params=" + jobParametersToString(params));
}
- mStartedSyncs.delete(jobId);
- mJobStartUptimes.delete(jobId);
+ sStartedSyncs.delete(jobId);
+ sJobStartUptimes.delete(jobId);
}
Message m = Message.obtain();
m.what = SyncManager.SyncHandler.MESSAGE_STOP_SYNC;
- m.obj = SyncOperation.maybeCreateFromJobExtras(params.getExtras());
- if (m.obj == null) {
- return false;
- }
+ m.obj = op;
// Reschedule if this job was NOT explicitly canceled.
m.arg1 = params.getStopReason() != JobParameters.REASON_CANCELED ? 1 : 0;
// Apply backoff only if stop is called due to timeout.
m.arg2 = params.getStopReason() == JobParameters.REASON_TIMEOUT ? 1 : 0;
- sendMessage(m);
+ SyncManager.sendMessage(m);
return false;
}
- public void callJobFinished(int jobId, boolean needsReschedule, String why) {
- synchronized (mLock) {
- JobParameters params = mJobParamsMap.get(jobId);
- mLogger.log("callJobFinished()",
+ public static void callJobFinished(int jobId, boolean needsReschedule, String why) {
+ final SyncJobService instance = getInstance();
+ if (instance != null) {
+ instance.callJobFinishedInner(jobId, needsReschedule, why);
+ }
+ }
+
+ public void callJobFinishedInner(int jobId, boolean needsReschedule, String why) {
+ synchronized (sLock) {
+ JobParameters params = sJobParamsMap.get(jobId);
+ sLogger.log("callJobFinished()",
" jobid=", jobId,
" needsReschedule=", needsReschedule,
- " ", mLogger.jobParametersToString(params),
+ " ", sLogger.jobParametersToString(params),
" why=", why);
if (params != null) {
jobFinished(params, needsReschedule);
- mJobParamsMap.remove(jobId);
+ sJobParamsMap.remove(jobId);
} else {
Slog.e(TAG, "Job params not found for " + String.valueOf(jobId));
}
}
}
- public void markSyncStarted(int jobId) {
- synchronized (mLock) {
- mStartedSyncs.put(jobId, true);
+ public static void markSyncStarted(int jobId) {
+ synchronized (sLock) {
+ sStartedSyncs.put(jobId, true);
}
}
@@ -203,8 +212,8 @@ public class SyncJobService extends JobService {
}
}
- private void wtf(String message) {
- mLogger.log(message);
+ private static void wtf(String message) {
+ sLogger.log(message);
Slog.wtf(TAG, message);
}
}
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 0a640b8a76c6..d06e78514a5c 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -21,6 +21,7 @@ import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
import android.accounts.AccountManagerInternal;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -72,7 +73,6 @@ import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteCallback;
@@ -89,6 +89,7 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
+import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -227,7 +228,6 @@ public class SyncManager {
// TODO: add better locking around mRunningAccounts
private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
- volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
volatile private PowerManager.WakeLock mSyncManagerWakeLock;
volatile private boolean mDataConnectionIsConnected = false;
volatile private boolean mStorageIsLow = false;
@@ -238,7 +238,6 @@ public class SyncManager {
private final IBatteryStats mBatteryStats;
private JobScheduler mJobScheduler;
private JobSchedulerInternal mJobSchedulerInternal;
- private SyncJobService mSyncJobService;
private SyncStorageEngine mSyncStorageEngine;
@@ -318,16 +317,6 @@ public class SyncManager {
}
};
- private final BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- mBootCompleted = true;
- // Called because it gets all pending jobs and stores them in mScheduledSyncs cache.
- verifyJobScheduler();
- mSyncHandler.onBootCompleted();
- }
- };
-
private final BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -371,14 +360,14 @@ public class SyncManager {
m.sendToTarget();
}
- private void doDatabaseCleanup() {
+ private void removeStaleAccounts() {
for (UserInfo user : mUserManager.getUsers(true)) {
// Skip any partially created/removed users
if (user.partial) continue;
Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(
user.id, mContext.getOpPackageName());
- mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
+ mSyncStorageEngine.removeStaleAccounts(accountsForUser, user.id);
}
}
@@ -464,8 +453,8 @@ public class SyncManager {
private final SyncHandler mSyncHandler;
private final SyncManagerConstants mConstants;
- private volatile boolean mBootCompleted = false;
- private volatile boolean mJobServiceReady = false;
+ @GuardedBy("mUnlockedUsers")
+ private final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray();
private ConnectivityManager getConnectivityManager() {
synchronized (this) {
@@ -641,12 +630,6 @@ public class SyncManager {
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
- if (!factoryTest) {
- intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
- intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- context.registerReceiver(mBootCompletedReceiver, intentFilter);
- }
-
intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
context.registerReceiver(mStorageIntentReceiver, intentFilter);
@@ -690,14 +673,6 @@ public class SyncManager {
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
BatteryStats.SERVICE_NAME));
- // This WakeLock is used to ensure that we stay awake between the time that we receive
- // a sync alarm notification and when we finish processing it. We need to do this
- // because we don't do the work in the alarm handler, rather we do it in a message
- // handler.
- mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
- HANDLE_SYNC_ALARM_WAKE_LOCK);
- mHandleAlarmWakeLock.setReferenceCounted(false);
-
// This WakeLock is used to ensure that we stay awake while running the sync loop
// message handler. Normally we will hold a sync adapter wake lock while it is being
// synced but during the execution of the sync loop it might finish a sync for
@@ -715,7 +690,6 @@ public class SyncManager {
public void onChange(boolean selfChange) {
mProvisioned |= isDeviceProvisioned();
if (mProvisioned) {
- mSyncHandler.onDeviceProvisioned();
resolver.unregisterContentObserver(this);
}
}
@@ -744,19 +718,6 @@ public class SyncManager {
null, null);
}
- // Set up the communication channel between the scheduled job and the sync manager.
- // This is posted to the *main* looper intentionally, to defer calling startService()
- // until after the lengthy primary boot sequence completes on that thread, to avoid
- // spurious ANR triggering.
- final Intent startServiceIntent = new Intent(mContext, SyncJobService.class);
- startServiceIntent.putExtra(SyncJobService.EXTRA_MESSENGER, new Messenger(mSyncHandler));
- new Handler(mContext.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- mContext.startService(startServiceIntent);
- }
- });
-
// Sync adapters were able to access the synced account without the accounts
// permission which circumvents our permission model. Therefore, we require
// sync adapters that don't have access to the account to get user consent.
@@ -768,16 +729,31 @@ public class SyncManager {
mLogger.log("Sync manager initialized: " + Build.FINGERPRINT);
}
- public void onStartUser(int userHandle) {
- mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userHandle));
+ public void onStartUser(int userId) {
+ // Log on the handler to avoid slowing down device boot.
+ mSyncHandler.post(() -> mLogger.log("onStartUser: user=", userId));
}
- public void onUnlockUser(int userHandle) {
- mSyncHandler.post(() -> mLogger.log("onUnlockUser: user=", userHandle));
+ public void onUnlockUser(int userId) {
+ synchronized (mUnlockedUsers) {
+ mUnlockedUsers.put(userId, true);
+ }
+ // Log on the handler to avoid slowing down device boot.
+ mSyncHandler.post(() -> mLogger.log("onUnlockUser: user=", userId));
+ }
+
+ public void onStopUser(int userId) {
+ synchronized (mUnlockedUsers) {
+ mUnlockedUsers.put(userId, false);
+ }
+ // Log on the handler to avoid slowing down user switch.
+ mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userId));
}
- public void onStopUser(int userHandle) {
- mSyncHandler.post(() -> mLogger.log("onStopUser: user=", userHandle));
+ private boolean isUserUnlocked(int userId) {
+ synchronized (mUnlockedUsers) {
+ return mUnlockedUsers.get(userId);
+ }
}
public void onBootPhase(int phase) {
@@ -1820,7 +1796,7 @@ public class SyncManager {
updateRunningAccounts(null /* Don't sync any target */);
// Clean up the storage engine database
- mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
+ mSyncStorageEngine.removeStaleAccounts(null, userId);
List<SyncOperation> ops = getAllPendingSyncs();
for (SyncOperation op: ops) {
if (op.target.userId == userId) {
@@ -2235,8 +2211,13 @@ public class SyncManager {
mSyncStorageEngine.resetTodayStats(/* force=*/ false);
for (AccountAndUser account : accounts) {
- pw.printf("Account %s u%d %s\n",
- account.account.name, account.userId, account.account.type);
+ final boolean unlocked;
+ synchronized (mUnlockedUsers) {
+ unlocked = mUnlockedUsers.get(account.userId);
+ }
+ pw.printf("Account %s u%d %s%s\n",
+ account.account.name, account.userId, account.account.type,
+ (unlocked ? "" : " (locked)"));
pw.println("=======================================================================");
final PrintTable table = new PrintTable(16);
@@ -2872,13 +2853,29 @@ public class SyncManager {
}
}
+ @Nullable
+ private static SyncManager getInstance() {
+ synchronized (SyncManager.class) {
+ if (sInstance == null) {
+ Slog.wtf(TAG, "sInstance == null"); // Maybe called too early?
+ }
+ return sInstance;
+ }
+ }
+
/**
- * @return whether the device is ready to run sync jobs.
+ * @return whether the device is ready to run sync jobs for a given user.
*/
- public static boolean readyToSync() {
- synchronized (SyncManager.class) {
- return sInstance != null && sInstance.mProvisioned && sInstance.mBootCompleted
- && sInstance.mJobServiceReady;
+ public static boolean readyToSync(int userId) {
+ final SyncManager instance = getInstance();
+ return (instance != null) && SyncJobService.isReady()
+ && instance.mProvisioned && instance.isUserUnlocked(userId);
+ }
+
+ public static void sendMessage(Message message) {
+ final SyncManager instance = getInstance();
+ if (instance != null) {
+ instance.mSyncHandler.sendMessage(message);
}
}
@@ -2889,11 +2886,9 @@ public class SyncManager {
class SyncHandler extends Handler {
// Messages that can be sent on mHandler.
private static final int MESSAGE_SYNC_FINISHED = 1;
- private static final int MESSAGE_RELEASE_MESSAGES_FROM_QUEUE = 2;
private static final int MESSAGE_SERVICE_CONNECTED = 4;
private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
private static final int MESSAGE_CANCEL = 6;
- static final int MESSAGE_JOBSERVICE_OBJECT = 7;
static final int MESSAGE_START_SYNC = 10;
static final int MESSAGE_STOP_SYNC = 11;
static final int MESSAGE_SCHEDULE_SYNC = 12;
@@ -2910,86 +2905,17 @@ public class SyncManager {
public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap();
- private List<Message> mUnreadyQueue = new ArrayList<Message>();
-
- void onBootCompleted() {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "Boot completed.");
- }
- checkIfDeviceReady();
- }
-
- void onDeviceProvisioned() {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "mProvisioned=" + mProvisioned);
- }
- checkIfDeviceReady();
- }
-
- void checkIfDeviceReady() {
- if (mProvisioned && mBootCompleted && mJobServiceReady) {
- synchronized(this) {
- mSyncStorageEngine.restoreAllPeriodicSyncs();
- // Dispatch any stashed messages.
- obtainMessage(MESSAGE_RELEASE_MESSAGES_FROM_QUEUE).sendToTarget();
- }
- }
- }
-
- /**
- * Stash any messages that come to the handler before boot is complete or before the device
- * is properly provisioned (i.e. out of set-up wizard).
- * {@link #onBootCompleted()} and {@link SyncHandler#onDeviceProvisioned} both
- * need to come in before we start syncing.
- * @param msg Message to dispatch at a later point.
- * @return true if a message was enqueued, false otherwise. This is to avoid losing the
- * message if we manage to acquire the lock but by the time we do boot has completed.
- */
- private boolean tryEnqueueMessageUntilReadyToRun(Message msg) {
- synchronized (this) {
- if (!mBootCompleted || !mProvisioned || !mJobServiceReady) {
- // Need to copy the message bc looper will recycle it.
- Message m = Message.obtain(msg);
- mUnreadyQueue.add(m);
- return true;
- } else {
- return false;
- }
- }
- }
-
public SyncHandler(Looper looper) {
super(looper);
}
public void handleMessage(Message msg) {
+ // TODO Do we really need this wake lock?? If we actually needed it, this is probably
+ // not the best place to acquire the lock -- it's probably too late, because the device
+ // could have gone to sleep before we reach here.
+ mSyncManagerWakeLock.acquire();
try {
- mSyncManagerWakeLock.acquire();
- // We only want to enqueue sync related messages until device is ready.
- // Other messages are handled without enqueuing.
- if (msg.what == MESSAGE_JOBSERVICE_OBJECT) {
- Slog.i(TAG, "Got SyncJobService instance.");
- mSyncJobService = (SyncJobService) msg.obj;
- mJobServiceReady = true;
- checkIfDeviceReady();
- } else if (msg.what == SyncHandler.MESSAGE_ACCOUNTS_UPDATED) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
- }
- EndPoint targets = (EndPoint) msg.obj;
- updateRunningAccountsH(targets);
- } else if (msg.what == MESSAGE_RELEASE_MESSAGES_FROM_QUEUE) {
- if (mUnreadyQueue != null) {
- for (Message m : mUnreadyQueue) {
- handleSyncMessage(m);
- }
- mUnreadyQueue = null;
- }
- } else if (tryEnqueueMessageUntilReadyToRun(msg)) {
- // No work to be done.
- } else {
- handleSyncMessage(msg);
- }
+ handleSyncMessage(msg);
} finally {
mSyncManagerWakeLock.release();
}
@@ -3001,6 +2927,13 @@ public class SyncManager {
try {
mDataConnectionIsConnected = readDataConnectionState();
switch (msg.what) {
+ case MESSAGE_ACCOUNTS_UPDATED:
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, "handleSyncHandlerMessage: MESSAGE_ACCOUNTS_UPDATED");
+ }
+ EndPoint targets = (EndPoint) msg.obj;
+ updateRunningAccountsH(targets);
+ break;
case MESSAGE_SCHEDULE_SYNC:
ScheduleSyncMessagePayload syncPayload =
(ScheduleSyncMessagePayload) msg.obj;
@@ -3069,7 +3002,7 @@ public class SyncManager {
if (isLoggable) {
Slog.v(TAG, "syncFinished" + payload.activeSyncContext.mSyncOperation);
}
- mSyncJobService.callJobFinished(
+ SyncJobService.callJobFinished(
payload.activeSyncContext.mSyncOperation.jobId, false,
"sync finished");
runSyncFinishedOrCanceledH(payload.syncResult,
@@ -3119,7 +3052,7 @@ public class SyncManager {
// which is a soft error.
SyncResult syncResult = new SyncResult();
syncResult.stats.numIoExceptions++;
- mSyncJobService.callJobFinished(
+ SyncJobService.callJobFinished(
currentSyncContext.mSyncOperation.jobId, false,
"service disconnected");
runSyncFinishedOrCanceledH(syncResult, currentSyncContext);
@@ -3138,7 +3071,7 @@ public class SyncManager {
Log.w(TAG, String.format(
"Detected sync making no progress for %s. cancelling.",
monitoredSyncContext));
- mSyncJobService.callJobFinished(
+ SyncJobService.callJobFinished(
monitoredSyncContext.mSyncOperation.jobId, false,
"no network activity");
runSyncFinishedOrCanceledH(
@@ -3175,7 +3108,7 @@ public class SyncManager {
private void deferSyncH(SyncOperation op, long delay, String why) {
mLogger.log("deferSyncH() ", (op.isPeriodic ? "periodic " : ""),
"sync. op=", op, " delay=", delay, " why=", why);
- mSyncJobService.callJobFinished(op.jobId, false, why);
+ SyncJobService.callJobFinished(op.jobId, false, why);
if (op.isPeriodic) {
scheduleSyncOperationH(op.createOneTimeSyncOperation(), delay);
} else {
@@ -3213,7 +3146,7 @@ public class SyncManager {
// assume the clock is correct.
mSyncStorageEngine.setClockValid();
- mSyncJobService.markSyncStarted(op.jobId);
+ SyncJobService.markSyncStarted(op.jobId);
if (mStorageIsLow) {
deferSyncH(op, SYNC_DELAY_ON_LOW_STORAGE, "storage low");
@@ -3226,7 +3159,7 @@ public class SyncManager {
List<SyncOperation> ops = getAllPendingSyncs();
for (SyncOperation syncOperation: ops) {
if (syncOperation.sourcePeriodicId == op.jobId) {
- mSyncJobService.callJobFinished(op.jobId, false,
+ SyncJobService.callJobFinished(op.jobId, false,
"periodic sync, pending");
return;
}
@@ -3235,7 +3168,7 @@ public class SyncManager {
// executing according to some backoff criteria.
for (ActiveSyncContext asc: mActiveSyncContexts) {
if (asc.mSyncOperation.sourcePeriodicId == op.jobId) {
- mSyncJobService.callJobFinished(op.jobId, false,
+ SyncJobService.callJobFinished(op.jobId, false,
"periodic sync, already running");
return;
}
@@ -3272,13 +3205,13 @@ public class SyncManager {
switch (syncOpState) {
case SYNC_OP_STATE_INVALID_NO_ACCOUNT_ACCESS:
case SYNC_OP_STATE_INVALID: {
- mSyncJobService.callJobFinished(op.jobId, false,
+ SyncJobService.callJobFinished(op.jobId, false,
"invalid op state: " + syncOpState);
} return;
}
if (!dispatchSyncOperation(op)) {
- mSyncJobService.callJobFinished(op.jobId, false, "dispatchSyncOperation() failed");
+ SyncJobService.callJobFinished(op.jobId, false, "dispatchSyncOperation() failed");
}
setAuthorityPendingState(op.target);
@@ -3306,9 +3239,7 @@ public class SyncManager {
if (mLogger.enabled()) {
mLogger.log("updateRunningAccountsH: ", Arrays.toString(mRunningAccounts));
}
- if (mBootCompleted) {
- doDatabaseCleanup();
- }
+ removeStaleAccounts();
AccountAndUser[] accounts = mRunningAccounts;
for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
@@ -3453,7 +3384,7 @@ public class SyncManager {
if (op.sourcePeriodicId == syncOperation.jobId || op.jobId == syncOperation.jobId) {
ActiveSyncContext asc = findActiveSyncContextH(syncOperation.jobId);
if (asc != null) {
- mSyncJobService.callJobFinished(syncOperation.jobId, false,
+ SyncJobService.callJobFinished(syncOperation.jobId, false,
"removePeriodicSyncInternalH");
runSyncFinishedOrCanceledH(null, asc);
}
@@ -3662,7 +3593,7 @@ public class SyncManager {
false /* no config settings */)) {
continue;
}
- mSyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
+ SyncJobService.callJobFinished(activeSyncContext.mSyncOperation.jobId, false,
why);
runSyncFinishedOrCanceledH(null /* cancel => no result */, activeSyncContext);
}
diff --git a/services/core/java/com/android/server/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index 811dc755af6b..391e3b01da08 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -19,6 +19,7 @@ package com.android.server.content;
import android.accounts.Account;
import android.accounts.AccountAndUser;
import android.accounts.AccountManager;
+import android.annotation.Nullable;
import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -1005,7 +1006,7 @@ public class SyncStorageEngine {
* Called when the set of account has changed, given the new array of
* active accounts.
*/
- public void doDatabaseCleanup(Account[] accounts, int userId) {
+ public void removeStaleAccounts(@Nullable Account[] accounts, int userId) {
synchronized (mAuthorities) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "Updating for new accounts...");
@@ -1014,8 +1015,9 @@ public class SyncStorageEngine {
Iterator<AccountInfo> accIt = mAccounts.values().iterator();
while (accIt.hasNext()) {
AccountInfo acc = accIt.next();
- if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
- && acc.accountAndUser.userId == userId) {
+ if ((accounts == null) || (
+ (acc.accountAndUser.userId == userId)
+ && !ArrayUtils.contains(accounts, acc.accountAndUser.account))) {
// This account no longer exists...
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Slog.v(TAG, "Account removed: " + acc.accountAndUser);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 1c3342a9af42..b97e90487123 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -400,6 +400,7 @@ class AutomaticBrightnessController {
} else if (mLightSensorEnabled) {
mLightSensorEnabled = false;
mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
+ mScreenAutoBrightness = -1;
mRecentLightSamples = 0;
mAmbientLightRingBuffer.clear();
mCurrentLightSensorRate = -1;
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 99412c56b274..b124ac78a8ef 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -791,9 +791,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
&& mAutomaticBrightnessController != null;
final boolean userSetBrightnessChanged = updateUserSetScreenBrightness();
- if (userSetBrightnessChanged) {
- mTemporaryScreenBrightness = -1;
- }
// Use the temporary screen brightness if there isn't an override, either from
// WindowManager or based on the display state.
@@ -1514,11 +1511,13 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
if (mCurrentScreenBrightnessSetting == mPendingScreenBrightnessSetting) {
mPendingScreenBrightnessSetting = -1;
+ mTemporaryScreenBrightness = -1;
return false;
}
mCurrentScreenBrightnessSetting = mPendingScreenBrightnessSetting;
mLastUserSetScreenBrightness = mPendingScreenBrightnessSetting;
mPendingScreenBrightnessSetting = -1;
+ mTemporaryScreenBrightness = -1;
return true;
}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index a2a55e532ef4..0eb18a8f8cd5 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -200,6 +200,8 @@ final class Constants {
static final int UNKNOWN_VOLUME = -1;
+ static final String PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM =
+ "persist.sys.hdmi.addr.audiosystem";
static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
diff --git a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
index 2c1a7d5d18e6..15ec486f989b 100644
--- a/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
+++ b/services/core/java/com/android/server/hdmi/DelayedMessageBuffer.java
@@ -85,7 +85,7 @@ final class DelayedMessageBuffer {
void processAllMessages() {
// Use the copied buffer.
- ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+ ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
mBuffer.clear();
for (HdmiCecMessage message : copiedBuffer) {
mDevice.onMessage(message);
@@ -104,7 +104,7 @@ final class DelayedMessageBuffer {
* are associated with
*/
void processMessagesForDevice(int address) {
- ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+ ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
mBuffer.clear();
HdmiLogger.debug("Checking message for address:" + address);
for (HdmiCecMessage message : copiedBuffer) {
@@ -134,7 +134,7 @@ final class DelayedMessageBuffer {
* @param address logical address of the device to be the active source
*/
void processActiveSource(int address) {
- ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<HdmiCecMessage>(mBuffer);
+ ArrayList<HdmiCecMessage> copiedBuffer = new ArrayList<>(mBuffer);
mBuffer.clear();
for (HdmiCecMessage message : copiedBuffer) {
if (message.getOpcode() == Constants.MESSAGE_ACTIVE_SOURCE
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index db8dedbf12cc..b75e75feabb3 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -386,6 +386,7 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
return;
case STATE_WAITING_FOR_VENDOR_ID:
queryVendorId(address);
+ return;
default:
return;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 2949b92f82df..1dd10f550985 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -169,6 +169,8 @@ abstract class HdmiCecLocalDevice {
return new HdmiCecLocalDeviceTv(service);
case HdmiDeviceInfo.DEVICE_PLAYBACK:
return new HdmiCecLocalDevicePlayback(service);
+ case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
+ return new HdmiCecLocalDeviceAudioSystem(service);
default:
return null;
}
@@ -264,14 +266,28 @@ abstract class HdmiCecLocalDevice {
return handleRoutingChange(message);
case Constants.MESSAGE_ROUTING_INFORMATION:
return handleRoutingInformation(message);
+ case Constants.MESSAGE_REQUEST_ARC_INITIATION:
+ return handleRequestArcInitiate(message);
+ case Constants.MESSAGE_REQUEST_ARC_TERMINATION:
+ return handleRequestArcTermination(message);
case Constants.MESSAGE_INITIATE_ARC:
return handleInitiateArc(message);
case Constants.MESSAGE_TERMINATE_ARC:
return handleTerminateArc(message);
+ case Constants.MESSAGE_REPORT_ARC_INITIATED:
+ return handleReportArcInitiate(message);
+ case Constants.MESSAGE_REPORT_ARC_TERMINATED:
+ return handleReportArcTermination(message);
+ case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
+ return handleSystemAudioModeRequest(message);
case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE:
return handleSetSystemAudioMode(message);
case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS:
return handleSystemAudioModeStatus(message);
+ case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS:
+ return handleGiveSystemAudioModeStatus(message);
+ case Constants.MESSAGE_GIVE_AUDIO_STATUS:
+ return handleGiveAudioStatus(message);
case Constants.MESSAGE_REPORT_AUDIO_STATUS:
return handleReportAudioStatus(message);
case Constants.MESSAGE_STANDBY:
@@ -419,10 +435,18 @@ abstract class HdmiCecLocalDevice {
return false;
}
+ protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+ return false;
+ }
+
protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
return false;
}
+ protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
+ return false;
+ }
+
protected boolean handleTerminateArc(HdmiCecMessage message) {
return false;
}
@@ -431,10 +455,30 @@ abstract class HdmiCecLocalDevice {
return false;
}
+ protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
+ return false;
+ }
+
+ protected boolean handleRequestArcTermination(HdmiCecMessage message) {
+ return false;
+ }
+
+ protected boolean handleReportArcInitiate(HdmiCecMessage message) {
+ return false;
+ }
+
+ protected boolean handleReportArcTermination(HdmiCecMessage message) {
+ return false;
+ }
+
protected boolean handleReportAudioStatus(HdmiCecMessage message) {
return false;
}
+ protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
+ return false;
+ }
+
@ServiceThreadOnly
protected boolean handleStandby(HdmiCecMessage message) {
assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
new file mode 100644
index 000000000000..655166ee1a4b
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioManager;
+import android.os.SystemProperties;
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+
+/**
+ * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in
+ * Android system.
+ */
+public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDevice {
+
+ private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
+
+ // Whether System audio mode is activated or not.
+ // This becomes true only when all system audio sequences are finished.
+ @GuardedBy("mLock")
+ private boolean mSystemAudioActivated;
+
+ protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
+ super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void onAddressAllocated(int logicalAddress, int reason) {
+ assertRunOnServiceThread();
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+ mAddress, mService.getPhysicalAddress(), mDeviceType));
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand(
+ mAddress, mService.getVendorId()));
+ startQueuedActions();
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected int getPreferredAddress() {
+ assertRunOnServiceThread();
+ return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM,
+ Constants.ADDR_UNREGISTERED);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected void setPreferredAddress(int addr) {
+ assertRunOnServiceThread();
+ SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM,
+ String.valueOf(addr));
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement report audio status handler
+ HdmiLogger.debug(TAG + "Stub handleReportAudioStatus");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleInitiateArc(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement initiate arc handler
+ HdmiLogger.debug(TAG + "Stub handleInitiateArc");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportArcInitiate(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement report arc initiate handler
+ HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleReportArcTermination(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ // TODO(amyjojo): implement report arc terminate handler
+ HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+
+ reportAudioStatus(message);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ mService.sendCecCommand(HdmiCecMessageBuilder
+ .buildReportSystemAudioMode(mAddress, message.getSource(), mSystemAudioActivated));
+ return true;
+ }
+
+ private void reportAudioStatus(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+
+ int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
+ boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
+ int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+ int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
+
+ mService.sendCecCommand(HdmiCecMessageBuilder
+ .buildReportAudioStatus(mAddress, message.getSource(), scaledVolume, mute));
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
index fd0ff9d22bf8..c0056158a9c5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessage.java
@@ -16,9 +16,11 @@
package com.android.server.hdmi;
+import android.annotation.Nullable;
import libcore.util.EmptyArray;
import java.util.Arrays;
+import java.util.Objects;
/**
* A class to encapsulate HDMI-CEC message used for the devices connected via
@@ -44,6 +46,27 @@ public final class HdmiCecMessage {
mParams = Arrays.copyOf(params, params.length);
}
+ @Override
+ public boolean equals(@Nullable Object message) {
+ if (message instanceof HdmiCecMessage) {
+ HdmiCecMessage that = (HdmiCecMessage) message;
+ return this.mSource == that.getSource() &&
+ this.mDestination == that.getDestination() &&
+ this.mOpcode == that.getOpcode() &&
+ Arrays.equals(this.mParams, that.getParams());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ mSource,
+ mDestination,
+ mOpcode,
+ Arrays.hashCode(mParams));
+ }
+
/**
* Return the source address field of the message. It is the logical address
* of the device which generated the message.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
index 9a51e3c4e234..37f96142089a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageBuilder.java
@@ -211,6 +211,28 @@ public class HdmiCecMessageBuilder {
}
/**
+ * Build &lt;Initiate Arc&gt;
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildInitiateArc(int src, int dest) {
+ return buildCommand(src, dest, Constants.MESSAGE_INITIATE_ARC);
+ }
+
+ /**
+ * Build &lt;Terminate Arc&gt;
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildTerminateArc(int src, int dest) {
+ return buildCommand(src, dest, Constants.MESSAGE_TERMINATE_ARC);
+ }
+
+ /**
* Build &lt;Request Arc Termination&gt;
*
* @param src source address of command
@@ -372,6 +394,34 @@ public class HdmiCecMessageBuilder {
}
/**
+ * Build &lt;Set System Audio Mode&gt; command.
+ *
+ * @param src source address of command
+ * @param des destination address of command
+ * @param systemAudioStatus whether to set System Audio Mode on or off
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildSetSystemAudioMode(int src, int des, boolean systemAudioStatus) {
+ return buildCommandWithBooleanParam(src, des, Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE,
+ systemAudioStatus
+ );
+ }
+
+ /**
+ * Build &lt;Report System Audio Mode&gt; command.
+ *
+ * @param src source address of command
+ * @param des destination address of command
+ * @param systemAudioStatus whether System Audio Mode is on or off
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildReportSystemAudioMode(int src, int des, boolean systemAudioStatus) {
+ return buildCommandWithBooleanParam(src, des, Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS,
+ systemAudioStatus
+ );
+ }
+
+ /**
* Build &lt;Give Audio Status&gt; command.
*
* @param src source address of command
@@ -383,6 +433,21 @@ public class HdmiCecMessageBuilder {
}
/**
+ * Build &lt;Report Audio Status&gt; command.
+ *
+ * @param src source address of command
+ * @param dest destination address of command
+ * @param volume volume level of current device in param
+ * @param mute mute status of current device in param
+ * @return newly created {@link HdmiCecMessage}
+ */
+ static HdmiCecMessage buildReportAudioStatus(int src, int dest, int volume, boolean mute) {
+ byte status = (byte) ((byte) (mute ? 1 << 7 : 0) | ((byte) volume & 0x7F));
+ byte[] params = new byte[] { status };
+ return buildCommand(src, dest, Constants.MESSAGE_REPORT_AUDIO_STATUS, params);
+ }
+
+ /**
* Build &lt;User Control Pressed&gt; command.
*
* @param src source address of command
@@ -592,6 +657,23 @@ public class HdmiCecMessageBuilder {
return new HdmiCecMessage(src, dest, opcode, params);
}
+ /**
+ * Build a {@link HdmiCecMessage} with a boolean param and other given values.
+ *
+ * @param src source address of command
+ * @param des destination address of command
+ * @param opcode opcode for a message
+ * @param param boolean param for building the command
+ * @return newly created {@link HdmiCecMessage}
+ */
+ private static HdmiCecMessage buildCommandWithBooleanParam(int src, int des,
+ int opcode, boolean param) {
+ byte[] params = new byte[]{
+ param ? (byte) 0b1 : 0b0
+ };
+ return buildCommand(src, des, opcode, params);
+ }
+
private static byte[] physicalAddressToParam(int physicalAddress) {
return new byte[] {
(byte) ((physicalAddress >> 8) & 0xFF),
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index a1753e58c4ea..8a146397fa3e 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -96,7 +96,7 @@ public class HdmiControlService extends SystemService {
static final String PERMISSION = "android.permission.HDMI_CEC";
- // The reason code to initiate intializeCec().
+ // The reason code to initiate initializeCec().
static final int INITIATED_BY_ENABLE_CEC = 0;
static final int INITIATED_BY_BOOT_UP = 1;
static final int INITIATED_BY_SCREEN_ON = 2;
diff --git a/services/core/java/com/android/server/hdmi/HdmiLogger.java b/services/core/java/com/android/server/hdmi/HdmiLogger.java
index ebe52c0dfbf0..2309293dcbd7 100644
--- a/services/core/java/com/android/server/hdmi/HdmiLogger.java
+++ b/services/core/java/com/android/server/hdmi/HdmiLogger.java
@@ -17,7 +17,6 @@
package com.android.server.hdmi;
import android.annotation.Nullable;
-import android.os.Build;
import android.os.SystemClock;
import android.util.Pair;
import android.util.Slog;
@@ -41,7 +40,7 @@ import java.util.HashMap;
final class HdmiLogger {
private static final String TAG = "HDMI";
// Logging duration for same error message.
- private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000; // 20s
+ private static final long ERROR_LOG_DURATION_MILLIS = 20 * 1000; // 20s
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -134,6 +133,6 @@ final class HdmiLogger {
}
private static boolean shouldLogNow(@Nullable Pair<Long, Integer> timing, long curTime) {
- return timing == null || curTime - timing.first > ERROR_LOG_DURATTION_MILLIS;
+ return timing == null || curTime - timing.first > ERROR_LOG_DURATION_MILLIS;
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java
index 4ac3bba73e25..2a8117fac68d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiUtils.java
+++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java
@@ -114,7 +114,7 @@ final class HdmiUtils {
*
* @param logicalAddress the logical address to verify
* @param deviceType the device type to check
- * @throw IllegalArgumentException
+ * @throws IllegalArgumentException
*/
static void verifyAddressType(int logicalAddress, int deviceType) {
int actualDeviceType = getTypeFromAddress(logicalAddress);
diff --git a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
index 6893012342f9..a62d0b63221c 100644
--- a/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
+++ b/services/core/java/com/android/server/hdmi/PowerStatusMonitorAction.java
@@ -38,7 +38,7 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
private static final int INVALID_POWER_STATUS = POWER_STATUS_UNKNOWN - 1;
// Monitoring interval (60s)
- private static final int MONITIROING_INTERNAL_MS = 60000;
+ private static final int MONITORING_INTERNAL_MS = 60000;
// Timeout once sending <Give Device Power Status>
private static final int REPORT_POWER_STATUS_TIMEOUT_MS = 5000;
@@ -132,7 +132,7 @@ public class PowerStatusMonitorAction extends HdmiCecFeatureAction {
mState = STATE_WAIT_FOR_REPORT_POWER_STATUS;
// Add both timers, monitoring and timeout.
- addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITIROING_INTERNAL_MS);
+ addTimer(STATE_WAIT_FOR_NEXT_MONITORING, MONITORING_INTERNAL_MS);
addTimer(STATE_WAIT_FOR_REPORT_POWER_STATUS, REPORT_POWER_STATUS_TIMEOUT_MS);
}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 75a79cb0213c..c70101c43d79 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -35,7 +35,7 @@ abstract class RequestArcAction extends HdmiCecFeatureAction {
*
* @param source {@link HdmiCecLocalDevice} instance
* @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
- * @throw IllegalArugmentException if device type of sourceAddress and avrAddress
+ * @throws IllegalArgumentException if device type of sourceAddress and avrAddress
* is invalid
*/
RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
index 449b2085715c..a5477e865c40 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java
@@ -60,7 +60,7 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction {
* @param avrAddress logical address of AVR device
* @param targetStatus Whether to enable the system audio mode or not
* @param callback callback interface to be notified when it's done
- * @throw IllegalArugmentException if device type of sourceAddress and avrAddress is invalid
+ * @throws IllegalArgumentException if device type of sourceAddress and avrAddress is invalid
*/
SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus,
IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
index eb5119b8a233..6ddff91a70f7 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java
@@ -32,7 +32,7 @@ final class SystemAudioActionFromAvr extends SystemAudioAction {
* @param avrAddress logical address of AVR device
* @param targetStatus Whether to enable the system audio mode or not
* @param callback callback interface to be notified when it's done
- * @throw IllegalArugmentException if device type of tvAddress and avrAddress is invalid
+ * @throws IllegalArgumentException if device type of tvAddress and avrAddress is invalid
*/
SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress,
boolean targetStatus, IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
index 02ecd1392bc7..5c0c272f59e0 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java
@@ -32,7 +32,7 @@ final class SystemAudioActionFromTv extends SystemAudioAction {
* @param avrAddress logical address of AVR device
* @param targetStatus Whether to enable the system audio mode or not
* @param callback callback interface to be notified when it's done
- * @throw IllegalArugmentException if device type of tvAddress is invalid
+ * @throws IllegalArgumentException if device type of tvAddress is invalid
*/
SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress,
boolean targetStatus, IHdmiControlCallback callback) {
diff --git a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
index d41a36ca031f..13f0f4ae4a92 100644
--- a/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
+++ b/services/core/java/com/android/server/hdmi/SystemAudioStatusAction.java
@@ -64,7 +64,7 @@ final class SystemAudioStatusAction extends HdmiCecFeatureAction {
}
private void handleSendGiveAudioStatusFailure() {
- // Inform to all application that the audio status (volumn, mute) of
+ // Inform to all application that the audio status (volume, mute) of
// the audio amplifier is unknown.
tv().setAudioStatus(false, Constants.UNKNOWN_VOLUME);
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index 442354bbb6b9..a49cf44dc775 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -481,7 +481,7 @@ public class MediaSessionRecord implements IBinder.DeathRecipient {
mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
packageName, uid);
}
- } catch (IllegalArgumentException e) {
+ } catch (IllegalArgumentException | SecurityException e) {
Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
+ stream + ", flags=" + flags + ", packageName=" + packageName
+ ", uid=" + uid + ", useSuggested=" + useSuggested
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 71c7c881619e..dcdc2031d761 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -386,6 +386,7 @@ public class NotificationManagerService extends SystemService {
private static final String ATTR_VERSION = "version";
private RankingHelper mRankingHelper;
+ private PreferencesHelper mPreferencesHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -525,8 +526,8 @@ public class NotificationManagerService extends SystemService {
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (ZenModeConfig.ZEN_TAG.equals(parser.getName())) {
mZenModeHelper.readXml(parser, forRestore);
- } else if (RankingHelper.TAG_RANKING.equals(parser.getName())){
- mRankingHelper.readXml(parser, forRestore);
+ } else if (PreferencesHelper.TAG_RANKING.equals(parser.getName())){
+ mPreferencesHelper.readXml(parser, forRestore);
}
if (mListeners.getConfig().xmlTag.equals(parser.getName())) {
mListeners.readXml(parser, mAllowedManagedServicePackages);
@@ -608,7 +609,7 @@ public class NotificationManagerService extends SystemService {
out.startTag(null, TAG_NOTIFICATION_POLICY);
out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
mZenModeHelper.writeXml(out, forBackup, null);
- mRankingHelper.writeXml(out, forBackup);
+ mPreferencesHelper.writeXml(out, forBackup);
mListeners.writeXml(out, forBackup);
mAssistants.writeXml(out, forBackup);
mConditionProviders.writeXml(out, forBackup);
@@ -949,7 +950,7 @@ public class NotificationManagerService extends SystemService {
// update system notification channels
SystemNotificationChannels.createAll(context);
mZenModeHelper.updateDefaultZenRules();
- mRankingHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
+ mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
}
}
};
@@ -1092,7 +1093,8 @@ public class NotificationManagerService extends SystemService {
mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
mAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
- mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
+ mPreferencesHelper.onPackagesChanged(
+ removingPackage, changeUserId, pkgList, uidList);
savePolicyFile();
}
}
@@ -1152,7 +1154,7 @@ public class NotificationManagerService extends SystemService {
final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL);
mUserProfiles.updateCache(context);
mZenModeHelper.onUserRemoved(user);
- mRankingHelper.onUserRemoved(user);
+ mPreferencesHelper.onUserRemoved(user);
mListeners.onUserRemoved(user);
mConditionProviders.onUserRemoved(user);
mAssistants.onUserRemoved(user);
@@ -1210,7 +1212,7 @@ public class NotificationManagerService extends SystemService {
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, mMaxPackageEnqueueRate);
}
if (uri == null || NOTIFICATION_BADGING_URI.equals(uri)) {
- mRankingHelper.updateBadgingEnabled();
+ mPreferencesHelper.updateBadgingEnabled();
}
}
}
@@ -1332,6 +1334,9 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
+ void setPreferencesHelper(PreferencesHelper prefHelper) { mPreferencesHelper = prefHelper; }
+
+ @VisibleForTesting
void setRankingHandler(RankingHandler rankingHandler) {
mRankingHandler = rankingHandler;
}
@@ -1419,9 +1424,13 @@ public class NotificationManagerService extends SystemService {
mRankingHandler.requestSort();
}
});
- mRankingHelper = new RankingHelper(getContext(),
+ mPreferencesHelper = new PreferencesHelper(getContext(),
mPackageManagerClient,
mRankingHandler,
+ mZenModeHelper);
+ mRankingHelper = new RankingHelper(getContext(),
+ mRankingHandler,
+ mPreferencesHelper,
mZenModeHelper,
mUsageStats,
extractorNames);
@@ -1676,14 +1685,14 @@ public class NotificationManagerService extends SystemService {
}
}
final NotificationChannel preUpdate =
- mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
- mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+ mPreferencesHelper.updateNotificationChannel(pkg, uid, channel, true);
maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
final NotificationChannel modifiedChannel =
- mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
mListeners.notifyNotificationChannelChanged(
pkg, UserHandle.getUserHandleForUid(uid),
modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -1720,8 +1729,8 @@ public class NotificationManagerService extends SystemService {
Preconditions.checkNotNull(pkg);
final NotificationChannelGroup preUpdate =
- mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
- mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
+ mPreferencesHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
+ mPreferencesHelper.createNotificationChannelGroup(pkg, uid, group,
fromApp);
if (!fromApp) {
maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
@@ -2124,7 +2133,7 @@ public class NotificationManagerService extends SystemService {
public void setNotificationsEnabledForPackage(String pkg, int uid, boolean enabled) {
enforceSystemOrSystemUI("setNotificationsEnabledForPackage");
- mRankingHelper.setEnabled(pkg, uid, enabled);
+ mPreferencesHelper.setEnabled(pkg, uid, enabled);
// Now, cancel any outstanding notifications that are part of a just-disabled app
if (!enabled) {
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, null, 0, 0, true,
@@ -2162,7 +2171,7 @@ public class NotificationManagerService extends SystemService {
String pkg, int uid, boolean enabled) {
setNotificationsEnabledForPackage(pkg, uid, enabled);
- mRankingHelper.setAppImportanceLocked(pkg, uid);
+ mPreferencesHelper.setAppImportanceLocked(pkg, uid);
}
/**
@@ -2180,25 +2189,25 @@ public class NotificationManagerService extends SystemService {
public boolean areNotificationsEnabledForPackage(String pkg, int uid) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
+ return mPreferencesHelper.getImportance(pkg, uid) != IMPORTANCE_NONE;
}
@Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getImportance(pkg, Binder.getCallingUid());
+ return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid());
}
@Override
public boolean canShowBadge(String pkg, int uid) {
checkCallerIsSystem();
- return mRankingHelper.canShowBadge(pkg, uid);
+ return mPreferencesHelper.canShowBadge(pkg, uid);
}
@Override
public void setShowBadge(String pkg, int uid, boolean showBadge) {
checkCallerIsSystem();
- mRankingHelper.setShowBadge(pkg, uid, showBadge);
+ mPreferencesHelper.setShowBadge(pkg, uid, showBadge);
savePolicyFile();
}
@@ -2230,12 +2239,12 @@ public class NotificationManagerService extends SystemService {
for (int i = 0; i < channelsSize; i++) {
final NotificationChannel channel = channels.get(i);
Preconditions.checkNotNull(channel, "channel in list is null");
- mRankingHelper.createNotificationChannel(pkg, uid, channel,
+ mPreferencesHelper.createNotificationChannel(pkg, uid, channel,
true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
pkg, UserHandle.getUserId(uid)));
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(uid),
- mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
+ mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
}
savePolicyFile();
@@ -2258,7 +2267,7 @@ public class NotificationManagerService extends SystemService {
@Override
public NotificationChannel getNotificationChannel(String pkg, String channelId) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannel(
+ return mPreferencesHelper.getNotificationChannel(
pkg, Binder.getCallingUid(), channelId, false /* includeDeleted */);
}
@@ -2266,7 +2275,7 @@ public class NotificationManagerService extends SystemService {
public NotificationChannel getNotificationChannelForPackage(String pkg, int uid,
String channelId, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
+ return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, includeDeleted);
}
@Override
@@ -2278,10 +2287,10 @@ public class NotificationManagerService extends SystemService {
}
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
- mRankingHelper.deleteNotificationChannel(pkg, callingUid, channelId);
+ mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
UserHandle.getUserHandleForUid(callingUid),
- mRankingHelper.getNotificationChannel(pkg, callingUid, channelId, true),
+ mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true),
NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
savePolicyFile();
}
@@ -2289,7 +2298,7 @@ public class NotificationManagerService extends SystemService {
@Override
public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannelGroupWithChannels(
+ return mPreferencesHelper.getNotificationChannelGroupWithChannels(
pkg, Binder.getCallingUid(), groupId, false);
}
@@ -2297,7 +2306,7 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannelGroups(
+ return mPreferencesHelper.getNotificationChannelGroups(
pkg, Binder.getCallingUid(), false, false);
}
@@ -2307,10 +2316,10 @@ public class NotificationManagerService extends SystemService {
final int callingUid = Binder.getCallingUid();
NotificationChannelGroup groupToDelete =
- mRankingHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
+ mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, callingUid);
if (groupToDelete != null) {
List<NotificationChannel> deletedChannels =
- mRankingHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
+ mPreferencesHelper.deleteNotificationChannelGroup(pkg, callingUid, groupId);
for (int i = 0; i < deletedChannels.size(); i++) {
final NotificationChannel deletedChannel = deletedChannels.get(i);
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, deletedChannel.getId(), 0, 0,
@@ -2341,47 +2350,47 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg,
int uid, boolean includeDeleted) {
enforceSystemOrSystemUI("getNotificationChannelsForPackage");
- return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted);
+ return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted);
}
@Override
public int getNumNotificationChannelsForPackage(String pkg, int uid,
boolean includeDeleted) {
enforceSystemOrSystemUI("getNumNotificationChannelsForPackage");
- return mRankingHelper.getNotificationChannels(pkg, uid, includeDeleted)
+ return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted)
.getList().size();
}
@Override
public boolean onlyHasDefaultChannel(String pkg, int uid) {
enforceSystemOrSystemUI("onlyHasDefaultChannel");
- return mRankingHelper.onlyHasDefaultChannel(pkg, uid);
+ return mPreferencesHelper.onlyHasDefaultChannel(pkg, uid);
}
@Override
public int getDeletedChannelCount(String pkg, int uid) {
enforceSystemOrSystemUI("getDeletedChannelCount");
- return mRankingHelper.getDeletedChannelCount(pkg, uid);
+ return mPreferencesHelper.getDeletedChannelCount(pkg, uid);
}
@Override
public int getBlockedChannelCount(String pkg, int uid) {
enforceSystemOrSystemUI("getBlockedChannelCount");
- return mRankingHelper.getBlockedChannelCount(pkg, uid);
+ return mPreferencesHelper.getBlockedChannelCount(pkg, uid);
}
@Override
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
checkCallerIsSystem();
- return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
+ return mPreferencesHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
}
@Override
public NotificationChannelGroup getPopulatedNotificationChannelGroupForPackage(
String pkg, int uid, String groupId, boolean includeDeleted) {
enforceSystemOrSystemUI("getPopulatedNotificationChannelGroupForPackage");
- return mRankingHelper.getNotificationChannelGroupWithChannels(
+ return mPreferencesHelper.getNotificationChannelGroupWithChannels(
pkg, uid, groupId, includeDeleted);
}
@@ -2389,13 +2398,13 @@ public class NotificationManagerService extends SystemService {
public NotificationChannelGroup getNotificationChannelGroupForPackage(
String groupId, String pkg, int uid) {
enforceSystemOrSystemUI("getNotificationChannelGroupForPackage");
- return mRankingHelper.getNotificationChannelGroup(groupId, pkg, uid);
+ return mPreferencesHelper.getNotificationChannelGroup(groupId, pkg, uid);
}
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mRankingHelper.getNotificationChannels(
+ return mPreferencesHelper.getNotificationChannels(
pkg, Binder.getCallingUid(), false /* includeDeleted */);
}
@@ -2412,12 +2421,12 @@ public class NotificationManagerService extends SystemService {
@Override
public int getBlockedAppCount(int userId) {
checkCallerIsSystem();
- return mRankingHelper.getBlockedAppCount(userId);
+ return mPreferencesHelper.getBlockedAppCount(userId);
}
@Override
public boolean areChannelsBypassingDnd() {
- return mRankingHelper.areChannelsBypassingDnd();
+ return mPreferencesHelper.areChannelsBypassingDnd();
}
@Override
@@ -2440,7 +2449,7 @@ public class NotificationManagerService extends SystemService {
// Reset notification preferences
if (!fromApp) {
- mRankingHelper.onPackagesChanged(
+ mPreferencesHelper.onPackagesChanged(
true, UserHandle.getCallingUserId(), packages, uids);
}
@@ -3512,7 +3521,7 @@ public class NotificationManagerService extends SystemService {
Preconditions.checkNotNull(user);
verifyPrivilegedListener(token, user);
- return mRankingHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
+ return mPreferencesHelper.getNotificationChannels(pkg, getUidForPackageAndUser(pkg, user),
false /* includeDeleted */);
}
@@ -3525,7 +3534,7 @@ public class NotificationManagerService extends SystemService {
verifyPrivilegedListener(token, user);
List<NotificationChannelGroup> groups = new ArrayList<>();
- groups.addAll(mRankingHelper.getNotificationChannelGroups(
+ groups.addAll(mPreferencesHelper.getNotificationChannelGroups(
pkg, getUidForPackageAndUser(pkg, user)));
return new ParceledListSlice<>(groups);
}
@@ -3706,10 +3715,10 @@ public class NotificationManagerService extends SystemService {
JSONObject dump = new JSONObject();
try {
dump.put("service", "Notification Manager");
- dump.put("bans", mRankingHelper.dumpBansJson(filter));
- dump.put("ranking", mRankingHelper.dumpJson(filter));
+ dump.put("bans", mPreferencesHelper.dumpBansJson(filter));
+ dump.put("ranking", mPreferencesHelper.dumpJson(filter));
dump.put("stats", mUsageStats.dumpJson(filter));
- dump.put("channels", mRankingHelper.dumpChannelsJson(filter));
+ dump.put("channels", mPreferencesHelper.dumpChannelsJson(filter));
} catch (JSONException e) {
e.printStackTrace();
}
@@ -3782,6 +3791,7 @@ public class NotificationManagerService extends SystemService {
long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
mRankingHelper.dump(proto, filter);
+ mPreferencesHelper.dump(proto, filter);
proto.end(rankingToken);
}
@@ -3890,6 +3900,9 @@ public class NotificationManagerService extends SystemService {
pw.println("\n Ranking Config:");
mRankingHelper.dump(pw, " ", filter);
+ pw.println("\n Notification Preferences:");
+ mPreferencesHelper.dump(pw, " ", filter);
+
pw.println("\n Notification listeners:");
mListeners.dump(pw, filter);
pw.print(" mListenerHints: "); pw.println(mListenerHints);
@@ -3953,7 +3966,7 @@ public class NotificationManagerService extends SystemService {
@Override
public NotificationChannel getNotificationChannel(String pkg, int uid, String
channelId) {
- return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+ return mPreferencesHelper.getNotificationChannel(pkg, uid, channelId, false);
}
@Override
@@ -4049,7 +4062,7 @@ public class NotificationManagerService extends SystemService {
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
- final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
+ final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
notificationUid, channelId, false /* includeDeleted */);
if (channel == null) {
final String noChannelStr = "No Channel found for "
@@ -4064,7 +4077,7 @@ public class NotificationManagerService extends SystemService {
+ ", notificationUid=" + notificationUid
+ ", notification=" + notification;
Log.e(TAG, noChannelStr);
- boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
+ boolean appNotificationsOff = mPreferencesHelper.getImportance(pkg, notificationUid)
== NotificationManager.IMPORTANCE_NONE;
if (!appNotificationsOff) {
@@ -4079,7 +4092,7 @@ public class NotificationManagerService extends SystemService {
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
- r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));
+ r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
final boolean fgServiceShown = channel.isFgServiceShown();
@@ -4098,7 +4111,7 @@ public class NotificationManagerService extends SystemService {
channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
channel.setFgServiceShown(true);
}
- mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+ mPreferencesHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
r.updateNotificationChannel(channel);
}
} else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
@@ -4273,8 +4286,8 @@ public class NotificationManagerService extends SystemService {
return isPackageSuspended;
}
final boolean isBlocked =
- mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
- || mRankingHelper.getImportance(pkg, callingUid)
+ mPreferencesHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
+ || mPreferencesHelper.getImportance(pkg, callingUid)
== NotificationManager.IMPORTANCE_NONE
|| r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
if (isBlocked) {
@@ -5247,6 +5260,7 @@ public class NotificationManagerService extends SystemService {
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
+ ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
orderBefore.add(r.getKey());
@@ -5258,6 +5272,7 @@ public class NotificationManagerService extends SystemService {
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
userSentimentBefore.add(r.getUserSentiment());
suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
+ smartActionsBefore.add(r.getSmartActions());
mRankingHelper.extractSignals(r);
}
mRankingHelper.sort(mNotificationList);
@@ -5272,7 +5287,8 @@ public class NotificationManagerService extends SystemService {
|| !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
|| !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
|| !Objects.equals(suppressVisuallyBefore.get(i),
- r.getSuppressedVisualEffects())) {
+ r.getSuppressedVisualEffects())
+ || !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())) {
mHandler.scheduleSendRankingUpdate();
return;
}
@@ -6255,6 +6271,7 @@ public class NotificationManagerService extends SystemService {
Bundle showBadge = new Bundle();
Bundle userSentiment = new Bundle();
Bundle hidden = new Bundle();
+ Bundle smartActions = new Bundle();
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.sbn, info)) {
@@ -6282,6 +6299,7 @@ public class NotificationManagerService extends SystemService {
showBadge.putBoolean(key, record.canShowBadge());
userSentiment.putInt(key, record.getUserSentiment());
hidden.putBoolean(key, record.isHidden());
+ smartActions.putParcelableArrayList(key, record.getSmartActions());
}
final int M = keys.size();
String[] keysAr = keys.toArray(new String[M]);
@@ -6292,7 +6310,8 @@ public class NotificationManagerService extends SystemService {
}
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
+ smartActions);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index 1ab05ec94476..0154c72482c7 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -159,6 +159,7 @@ public final class NotificationRecord {
private Light mLight;
private String mGroupLogTag;
private String mChannelIdLogTag;
+ private ArrayList<Notification.Action> mSmartActions;
private final List<Adjustment> mAdjustments;
private final NotificationStats mStats;
@@ -630,6 +631,9 @@ public final class NotificationRecord {
Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
}
}
+ if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
+ setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
+ }
}
}
}
@@ -1049,6 +1053,14 @@ public final class NotificationRecord {
mHasSeenSmartReplies = hasSeenSmartReplies;
}
+ public void setSmartActions(ArrayList<Notification.Action> smartActions) {
+ mSmartActions = smartActions;
+ }
+
+ public ArrayList<Notification.Action> getSmartActions() {
+ return mSmartActions;
+ }
+
/**
* @return all {@link Uri} that should have permission granted to whoever
* will be rendering it. This list has already been vetted to only
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
new file mode 100644
index 000000000000..dfc61d98c604
--- /dev/null
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -0,0 +1,1373 @@
+/**
+ * Copyright (c) 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.metrics.LogMaker;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.RankingHelperProto;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.XmlUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PreferencesHelper implements RankingConfig {
+ private static final String TAG = "NotificationPrefHelper";
+ private static final int XML_VERSION = 1;
+
+ @VisibleForTesting
+ static final String TAG_RANKING = "ranking";
+ private static final String TAG_PACKAGE = "package";
+ private static final String TAG_CHANNEL = "channel";
+ private static final String TAG_GROUP = "channelGroup";
+
+ private static final String ATT_VERSION = "version";
+ private static final String ATT_NAME = "name";
+ private static final String ATT_UID = "uid";
+ private static final String ATT_ID = "id";
+ private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_VISIBILITY = "visibility";
+ private static final String ATT_IMPORTANCE = "importance";
+ private static final String ATT_SHOW_BADGE = "show_badge";
+ private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
+
+ private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
+ private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
+ private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
+ private static final boolean DEFAULT_SHOW_BADGE = true;
+ /**
+ * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
+ * fields.
+ */
+ private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
+
+ /**
+ * All user-lockable fields for a given application.
+ */
+ @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
+ public @interface LockableAppFields {
+ int USER_LOCKED_IMPORTANCE = 0x00000001;
+ }
+
+ // pkg|uid => PackagePreferences
+ private final ArrayMap<String, PackagePreferences> mPackagePreferencess = new ArrayMap<>();
+ // pkg => PackagePreferences
+ private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>();
+
+
+ private final Context mContext;
+ private final PackageManager mPm;
+ private final RankingHandler mRankingHandler;
+ private final ZenModeHelper mZenModeHelper;
+
+ private SparseBooleanArray mBadgingEnabled;
+ private boolean mAreChannelsBypassingDnd;
+
+
+ public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+ ZenModeHelper zenHelper) {
+ mContext = context;
+ mZenModeHelper = zenHelper;
+ mRankingHandler = rankingHandler;
+ mPm = pm;
+
+ updateBadgingEnabled();
+
+ mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
+ updateChannelsBypassingDnd();
+
+ }
+
+ public void readXml(XmlPullParser parser, boolean forRestore)
+ throws XmlPullParserException, IOException {
+ int type = parser.getEventType();
+ if (type != XmlPullParser.START_TAG) return;
+ String tag = parser.getName();
+ if (!TAG_RANKING.equals(tag)) return;
+ // Clobber groups and channels with the xml, but don't delete other data that wasn't present
+ // at the time of serialization.
+ mRestoredWithoutUids.clear();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
+ return;
+ }
+ if (type == XmlPullParser.START_TAG) {
+ if (TAG_PACKAGE.equals(tag)) {
+ int uid = XmlUtils.readIntAttribute(parser, ATT_UID,
+ PackagePreferences.UNKNOWN_UID);
+ String name = parser.getAttributeValue(null, ATT_NAME);
+ if (!TextUtils.isEmpty(name)) {
+ if (forRestore) {
+ try {
+ //TODO: http://b/22388012
+ uid = mPm.getPackageUidAsUser(name,
+ UserHandle.USER_SYSTEM);
+ } catch (PackageManager.NameNotFoundException e) {
+ // noop
+ }
+ }
+
+ PackagePreferences r = getOrCreatePackagePreferences(name, uid,
+ XmlUtils.readIntAttribute(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
+ XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
+ XmlUtils.readIntAttribute(
+ parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+ XmlUtils.readBooleanAttribute(
+ parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
+ r.importance = XmlUtils.readIntAttribute(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ r.priority = XmlUtils.readIntAttribute(
+ parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ r.visibility = XmlUtils.readIntAttribute(
+ parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+ r.showBadge = XmlUtils.readBooleanAttribute(
+ parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
+ r.lockedAppFields = XmlUtils.readIntAttribute(parser,
+ ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
+
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ // Channel groups
+ if (TAG_GROUP.equals(tagName)) {
+ String id = parser.getAttributeValue(null, ATT_ID);
+ CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
+ if (!TextUtils.isEmpty(id)) {
+ NotificationChannelGroup group
+ = new NotificationChannelGroup(id, groupName);
+ group.populateFromXml(parser);
+ r.groups.put(id, group);
+ }
+ }
+ // Channels
+ if (TAG_CHANNEL.equals(tagName)) {
+ String id = parser.getAttributeValue(null, ATT_ID);
+ String channelName = parser.getAttributeValue(null, ATT_NAME);
+ int channelImportance = XmlUtils.readIntAttribute(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
+ NotificationChannel channel = new NotificationChannel(id,
+ channelName, channelImportance);
+ if (forRestore) {
+ channel.populateFromXmlForRestore(parser, mContext);
+ } else {
+ channel.populateFromXml(parser);
+ }
+ r.channels.put(id, channel);
+ }
+ }
+ }
+
+ try {
+ deleteDefaultChannelIfNeeded(r);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
+ }
+ }
+ }
+ }
+ }
+ throw new IllegalStateException("Failed to reach END_DOCUMENT");
+ }
+
+ private PackagePreferences getPackagePreferences(String pkg, int uid) {
+ final String key = packagePreferencesKey(pkg, uid);
+ synchronized (mPackagePreferencess) {
+ return mPackagePreferencess.get(key);
+ }
+ }
+
+ private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid,
+ DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
+ }
+
+ private PackagePreferences getOrCreatePackagePreferences(String pkg, int uid, int importance,
+ int priority, int visibility, boolean showBadge) {
+ final String key = packagePreferencesKey(pkg, uid);
+ synchronized (mPackagePreferencess) {
+ PackagePreferences
+ r = (uid == PackagePreferences.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg)
+ : mPackagePreferencess.get(key);
+ if (r == null) {
+ r = new PackagePreferences();
+ r.pkg = pkg;
+ r.uid = uid;
+ r.importance = importance;
+ r.priority = priority;
+ r.visibility = visibility;
+ r.showBadge = showBadge;
+
+ try {
+ createDefaultChannelIfNeeded(r);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
+ }
+
+ if (r.uid == PackagePreferences.UNKNOWN_UID) {
+ mRestoredWithoutUids.put(pkg, r);
+ } else {
+ mPackagePreferencess.put(key, r);
+ }
+ }
+ return r;
+ }
+ }
+
+ private boolean shouldHaveDefaultChannel(PackagePreferences r) throws
+ PackageManager.NameNotFoundException {
+ final int userId = UserHandle.getUserId(r.uid);
+ final ApplicationInfo applicationInfo =
+ mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
+ if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
+ // O apps should not have the default channel.
+ return false;
+ }
+
+ // Otherwise, this app should have the default channel.
+ return true;
+ }
+
+ private void deleteDefaultChannelIfNeeded(PackagePreferences r) throws
+ PackageManager.NameNotFoundException {
+ if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ // Not present
+ return;
+ }
+
+ if (shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Remove Default Channel.
+ r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
+ }
+
+ private void createDefaultChannelIfNeeded(PackagePreferences r) throws
+ PackageManager.NameNotFoundException {
+ if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(mContext.getString(
+ com.android.internal.R.string.default_notification_channel_label));
+ return;
+ }
+
+ if (!shouldHaveDefaultChannel(r)) {
+ // Keep the default channel until upgraded.
+ return;
+ }
+
+ // Create Default Channel
+ NotificationChannel channel;
+ channel = new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID,
+ mContext.getString(R.string.default_notification_channel_label),
+ r.importance);
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ channel.setLockscreenVisibility(r.visibility);
+ if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ r.channels.put(channel.getId(), channel);
+ }
+
+ public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
+ out.startTag(null, TAG_RANKING);
+ out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
+
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ //TODO: http://b/22388012
+ if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
+ continue;
+ }
+ final boolean hasNonDefaultSettings =
+ r.importance != DEFAULT_IMPORTANCE
+ || r.priority != DEFAULT_PRIORITY
+ || r.visibility != DEFAULT_VISIBILITY
+ || r.showBadge != DEFAULT_SHOW_BADGE
+ || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
+ || r.channels.size() > 0
+ || r.groups.size() > 0;
+ if (hasNonDefaultSettings) {
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, r.pkg);
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
+ }
+ out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
+ out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
+ Integer.toString(r.lockedAppFields));
+
+ if (!forBackup) {
+ out.attribute(null, ATT_UID, Integer.toString(r.uid));
+ }
+
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeXml(out);
+ }
+
+ for (NotificationChannel channel : r.channels.values()) {
+ if (forBackup) {
+ if (!channel.isDeleted()) {
+ channel.writeXmlForBackup(out, mContext);
+ }
+ } else {
+ channel.writeXml(out);
+ }
+ }
+
+ out.endTag(null, TAG_PACKAGE);
+ }
+ }
+ }
+ out.endTag(null, TAG_RANKING);
+ }
+
+ /**
+ * Gets importance.
+ */
+ @Override
+ public int getImportance(String packageName, int uid) {
+ return getOrCreatePackagePreferences(packageName, uid).importance;
+ }
+
+
+ /**
+ * Returns whether the importance of the corresponding notification is user-locked and shouldn't
+ * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
+ * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
+ */
+ public boolean getIsAppImportanceLocked(String packageName, int uid) {
+ int userLockedFields = getOrCreatePackagePreferences(packageName, uid).lockedAppFields;
+ return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
+ }
+
+ @Override
+ public boolean canShowBadge(String packageName, int uid) {
+ return getOrCreatePackagePreferences(packageName, uid).showBadge;
+ }
+
+ @Override
+ public void setShowBadge(String packageName, int uid, boolean showBadge) {
+ getOrCreatePackagePreferences(packageName, uid).showBadge = showBadge;
+ updateConfig();
+ }
+
+ @Override
+ public boolean isGroupBlocked(String packageName, int uid, String groupId) {
+ if (groupId == null) {
+ return false;
+ }
+ PackagePreferences r = getOrCreatePackagePreferences(packageName, uid);
+ NotificationChannelGroup group = r.groups.get(groupId);
+ if (group == null) {
+ return false;
+ }
+ return group.isBlocked();
+ }
+
+ int getPackagePriority(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid).priority;
+ }
+
+ int getPackageVisibility(String pkg, int uid) {
+ return getOrCreatePackagePreferences(pkg, uid).visibility;
+ }
+
+ @Override
+ public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
+ boolean fromTargetApp) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(group);
+ Preconditions.checkNotNull(group.getId());
+ Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
+ if (!group.equals(oldGroup)) {
+ // will log for new entries as well as name/description changes
+ MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
+ }
+ if (oldGroup != null) {
+ group.setChannels(oldGroup.getChannels());
+
+ if (fromTargetApp) {
+ group.setBlocked(oldGroup.isBlocked());
+ }
+ }
+ r.groups.put(group.getId(), group);
+ }
+
+ @Override
+ public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean fromTargetApp, boolean hasDndAccess) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(channel);
+ Preconditions.checkNotNull(channel.getId());
+ Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
+ throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
+ }
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
+ throw new IllegalArgumentException("Reserved id");
+ }
+ NotificationChannel existing = r.channels.get(channel.getId());
+ // Keep most of the existing settings
+ if (existing != null && fromTargetApp) {
+ if (existing.isDeleted()) {
+ existing.setDeleted(false);
+
+ // log a resurrected channel as if it's new again
+ MetricsLogger.action(getChannelLog(channel, pkg).setType(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+ }
+
+ existing.setName(channel.getName().toString());
+ existing.setDescription(channel.getDescription());
+ existing.setBlockableSystem(channel.isBlockableSystem());
+ if (existing.getGroup() == null) {
+ existing.setGroup(channel.getGroup());
+ }
+
+ // Apps are allowed to downgrade channel importance if the user has not changed any
+ // fields on this channel yet.
+ if (existing.getUserLockedFields() == 0 &&
+ channel.getImportance() < existing.getImportance()) {
+ existing.setImportance(channel.getImportance());
+ }
+
+ // system apps and dnd access apps can bypass dnd if the user hasn't changed any
+ // fields on the channel yet
+ if (existing.getUserLockedFields() == 0 && hasDndAccess) {
+ boolean bypassDnd = channel.canBypassDnd();
+ existing.setBypassDnd(bypassDnd);
+
+ if (bypassDnd != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ }
+
+ updateConfig();
+ return;
+ }
+ if (channel.getImportance() < IMPORTANCE_NONE
+ || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
+ throw new IllegalArgumentException("Invalid importance level");
+ }
+
+ // Reset fields that apps aren't allowed to set.
+ if (fromTargetApp && !hasDndAccess) {
+ channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
+ }
+ if (fromTargetApp) {
+ channel.setLockscreenVisibility(r.visibility);
+ }
+ clearLockedFields(channel);
+ if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ channel.setLockscreenVisibility(
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ if (!r.showBadge) {
+ channel.setShowBadge(false);
+ }
+
+ r.channels.put(channel.getId(), channel);
+ if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ MetricsLogger.action(getChannelLog(channel, pkg).setType(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN));
+ }
+
+ void clearLockedFields(NotificationChannel channel) {
+ channel.unlockFields(channel.getUserLockedFields());
+ }
+
+ @Override
+ public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+ boolean fromUser) {
+ Preconditions.checkNotNull(updatedChannel);
+ Preconditions.checkNotNull(updatedChannel.getId());
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ throw new IllegalArgumentException("Invalid package");
+ }
+ NotificationChannel channel = r.channels.get(updatedChannel.getId());
+ if (channel == null || channel.isDeleted()) {
+ throw new IllegalArgumentException("Channel does not exist");
+ }
+ if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
+ updatedChannel.setLockscreenVisibility(
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ }
+ if (!fromUser) {
+ updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+ }
+ if (fromUser) {
+ updatedChannel.lockFields(channel.getUserLockedFields());
+ lockFieldsForUpdate(channel, updatedChannel);
+ }
+ r.channels.put(updatedChannel.getId(), updatedChannel);
+
+ if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
+ // copy settings to app level so they are inherited by new channels
+ // when the app migrates
+ r.importance = updatedChannel.getImportance();
+ r.priority = updatedChannel.canBypassDnd()
+ ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
+ r.visibility = updatedChannel.getLockscreenVisibility();
+ r.showBadge = updatedChannel.canShowBadge();
+ }
+
+ if (!channel.equals(updatedChannel)) {
+ // only log if there are real changes
+ MetricsLogger.action(getChannelLog(updatedChannel, pkg));
+ }
+
+ if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
+ updateChannelsBypassingDnd();
+ }
+ updateConfig();
+ }
+
+ @Override
+ public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+ boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r == null) {
+ return null;
+ }
+ if (channelId == null) {
+ channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
+ }
+ final NotificationChannel nc = r.channels.get(channelId);
+ if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ return nc;
+ }
+ return null;
+ }
+
+ @Override
+ public void deleteNotificationChannel(String pkg, int uid, String channelId) {
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ NotificationChannel channel = r.channels.get(channelId);
+ if (channel != null) {
+ channel.setDeleted(true);
+ LogMaker lm = getChannelLog(channel, pkg);
+ lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
+ MetricsLogger.action(lm);
+
+ if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
+ updateChannelsBypassingDnd();
+ }
+ }
+ }
+
+ @Override
+ @VisibleForTesting
+ public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(channelId);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ r.channels.remove(channelId);
+ }
+
+ @Override
+ public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return;
+ }
+ int N = r.channels.size() - 1;
+ for (int i = N; i >= 0; i--) {
+ String key = r.channels.keyAt(i);
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
+ r.channels.remove(key);
+ }
+ }
+ }
+
+ public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
+ int uid, String groupId, boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
+ return null;
+ }
+ NotificationChannelGroup group = r.groups.get(groupId).clone();
+ group.setChannels(new ArrayList<>());
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ if (groupId.equals(nc.getGroup())) {
+ group.addChannel(nc);
+ }
+ }
+ }
+ return group;
+ }
+
+ public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
+ int uid) {
+ Preconditions.checkNotNull(pkg);
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return null;
+ }
+ return r.groups.get(groupId);
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+ int uid, boolean includeDeleted, boolean includeNonGrouped) {
+ Preconditions.checkNotNull(pkg);
+ Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return ParceledListSlice.emptyList();
+ }
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ if (nc.getGroup() != null) {
+ if (r.groups.get(nc.getGroup()) != null) {
+ NotificationChannelGroup ncg = groups.get(nc.getGroup());
+ if (ncg == null) {
+ ncg = r.groups.get(nc.getGroup()).clone();
+ ncg.setChannels(new ArrayList<>());
+ groups.put(nc.getGroup(), ncg);
+
+ }
+ ncg.addChannel(nc);
+ }
+ } else {
+ nonGrouped.addChannel(nc);
+ }
+ }
+ }
+ if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
+ groups.put(null, nonGrouped);
+ }
+ return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+ }
+
+ public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
+ String groupId) {
+ List<NotificationChannel> deletedChannels = new ArrayList<>();
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null || TextUtils.isEmpty(groupId)) {
+ return deletedChannels;
+ }
+
+ r.groups.remove(groupId);
+
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (groupId.equals(nc.getGroup())) {
+ nc.setDeleted(true);
+ deletedChannels.add(nc);
+ }
+ }
+ return deletedChannels;
+ }
+
+ @Override
+ public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
+ int uid) {
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return new ArrayList<>();
+ }
+ return r.groups.values();
+ }
+
+ @Override
+ public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+ boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
+ List<NotificationChannel> channels = new ArrayList<>();
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return ParceledListSlice.emptyList();
+ }
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (includeDeleted || !nc.isDeleted()) {
+ channels.add(nc);
+ }
+ }
+ return new ParceledListSlice<>(channels);
+ }
+
+ /**
+ * True for pre-O apps that only have the default channel, or pre O apps that have no
+ * channels yet. This method will create the default channel for pre-O apps that don't have it.
+ * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
+ * upgrades.
+ */
+ public boolean onlyHasDefaultChannel(String pkg, int uid) {
+ PackagePreferences r = getOrCreatePackagePreferences(pkg, uid);
+ if (r.channels.size() == 1
+ && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getDeletedChannelCount(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ int deletedCount = 0;
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return deletedCount;
+ }
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (nc.isDeleted()) {
+ deletedCount++;
+ }
+ }
+ return deletedCount;
+ }
+
+ public int getBlockedChannelCount(String pkg, int uid) {
+ Preconditions.checkNotNull(pkg);
+ int blockedCount = 0;
+ PackagePreferences r = getPackagePreferences(pkg, uid);
+ if (r == null) {
+ return blockedCount;
+ }
+ int N = r.channels.size();
+ for (int i = 0; i < N; i++) {
+ final NotificationChannel nc = r.channels.valueAt(i);
+ if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
+ blockedCount++;
+ }
+ }
+ return blockedCount;
+ }
+
+ public int getBlockedAppCount(int userId) {
+ int count = 0;
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ if (userId == UserHandle.getUserId(r.uid)
+ && r.importance == IMPORTANCE_NONE) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ public void updateChannelsBypassingDnd() {
+ synchronized (mPackagePreferencess) {
+ final int numPackagePreferencess = mPackagePreferencess.size();
+ for (int PackagePreferencesIndex = 0; PackagePreferencesIndex < numPackagePreferencess;
+ PackagePreferencesIndex++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(PackagePreferencesIndex);
+ final int numChannels = r.channels.size();
+
+ for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
+ NotificationChannel channel = r.channels.valueAt(channelIndex);
+ if (!channel.isDeleted() && channel.canBypassDnd()) {
+ // If any channel bypasses DND, synchronize state and return early.
+ if (!mAreChannelsBypassingDnd) {
+ mAreChannelsBypassingDnd = true;
+ updateZenPolicy(true);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ // If no channels bypass DND, update the zen policy once to disable DND bypass.
+ if (mAreChannelsBypassingDnd) {
+ mAreChannelsBypassingDnd = false;
+ updateZenPolicy(false);
+ }
+ }
+
+ public void updateZenPolicy(boolean areChannelsBypassingDnd) {
+ NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
+ mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
+ policy.priorityCategories, policy.priorityCallSenders,
+ policy.priorityMessageSenders, policy.suppressedVisualEffects,
+ (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
+ : 0)));
+ }
+
+ public boolean areChannelsBypassingDnd() {
+ return mAreChannelsBypassingDnd;
+ }
+
+ /**
+ * Sets importance.
+ */
+ @Override
+ public void setImportance(String pkgName, int uid, int importance) {
+ getOrCreatePackagePreferences(pkgName, uid).importance = importance;
+ updateConfig();
+ }
+
+ public void setEnabled(String packageName, int uid, boolean enabled) {
+ boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
+ if (wasEnabled == enabled) {
+ return;
+ }
+ setImportance(packageName, uid,
+ enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
+ }
+
+ /**
+ * Sets whether any notifications from the app, represented by the given {@code pkgName} and
+ * {@code uid}, have their importance locked by the user. Locked notifications don't get
+ * considered for sentiment adjustments (and thus never show a blocking helper).
+ */
+ public void setAppImportanceLocked(String packageName, int uid) {
+ PackagePreferences PackagePreferences = getOrCreatePackagePreferences(packageName, uid);
+ if ((PackagePreferences.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
+ return;
+ }
+
+ PackagePreferences.lockedAppFields =
+ PackagePreferences.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
+ updateConfig();
+ }
+
+ @VisibleForTesting
+ void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
+ if (original.canBypassDnd() != update.canBypassDnd()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ }
+ if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
+ }
+ if (original.getImportance() != update.getImportance()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ }
+ if (original.shouldShowLights() != update.shouldShowLights()
+ || original.getLightColor() != update.getLightColor()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
+ }
+ if (!Objects.equals(original.getSound(), update.getSound())) {
+ update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
+ }
+ if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
+ || original.shouldVibrate() != update.shouldVibrate()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
+ }
+ if (original.canShowBadge() != update.canShowBadge()) {
+ update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix,
+ @NonNull NotificationManagerService.DumpFilter filter) {
+ pw.print(prefix);
+ pw.println("per-package config:");
+
+ pw.println("PackagePreferencess:");
+ synchronized (mPackagePreferencess) {
+ dumpPackagePreferencess(pw, prefix, filter, mPackagePreferencess);
+ }
+ pw.println("Restored without uid:");
+ dumpPackagePreferencess(pw, prefix, filter, mRestoredWithoutUids);
+ }
+
+ public void dump(ProtoOutputStream proto,
+ @NonNull NotificationManagerService.DumpFilter filter) {
+ synchronized (mPackagePreferencess) {
+ dumpPackagePreferencess(proto, RankingHelperProto.RECORDS, filter,
+ mPackagePreferencess);
+ }
+ dumpPackagePreferencess(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+ mRestoredWithoutUids);
+ }
+
+ private static void dumpPackagePreferencess(PrintWriter pw, String prefix,
+ @NonNull NotificationManagerService.DumpFilter filter,
+ ArrayMap<String, PackagePreferences> PackagePreferencess) {
+ final int N = PackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = PackagePreferencess.valueAt(i);
+ if (filter.matches(r.pkg)) {
+ pw.print(prefix);
+ pw.print(" AppSettings: ");
+ pw.print(r.pkg);
+ pw.print(" (");
+ pw.print(r.uid == PackagePreferences.UNKNOWN_UID ? "UNKNOWN_UID"
+ : Integer.toString(r.uid));
+ pw.print(')');
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ pw.print(" importance=");
+ pw.print(NotificationListenerService.Ranking.importanceToString(r.importance));
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ pw.print(" priority=");
+ pw.print(Notification.priorityToString(r.priority));
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ pw.print(" visibility=");
+ pw.print(Notification.visibilityToString(r.visibility));
+ }
+ pw.print(" showBadge=");
+ pw.print(Boolean.toString(r.showBadge));
+ pw.println();
+ for (NotificationChannel channel : r.channels.values()) {
+ pw.print(prefix);
+ channel.dump(pw, " ", filter.redact);
+ }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(" ");
+ pw.println(group);
+ }
+ }
+ }
+ }
+
+ private static void dumpPackagePreferencess(ProtoOutputStream proto, long fieldId,
+ @NonNull NotificationManagerService.DumpFilter filter,
+ ArrayMap<String, PackagePreferences> PackagePreferencess) {
+ final int N = PackagePreferencess.size();
+ long fToken;
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = PackagePreferencess.valueAt(i);
+ if (filter.matches(r.pkg)) {
+ fToken = proto.start(fieldId);
+
+ proto.write(RankingHelperProto.RecordProto.PACKAGE, r.pkg);
+ proto.write(RankingHelperProto.RecordProto.UID, r.uid);
+ proto.write(RankingHelperProto.RecordProto.IMPORTANCE, r.importance);
+ proto.write(RankingHelperProto.RecordProto.PRIORITY, r.priority);
+ proto.write(RankingHelperProto.RecordProto.VISIBILITY, r.visibility);
+ proto.write(RankingHelperProto.RecordProto.SHOW_BADGE, r.showBadge);
+
+ for (NotificationChannel channel : r.channels.values()) {
+ channel.writeToProto(proto, RankingHelperProto.RecordProto.CHANNELS);
+ }
+ for (NotificationChannelGroup group : r.groups.values()) {
+ group.writeToProto(proto, RankingHelperProto.RecordProto.CHANNEL_GROUPS);
+ }
+
+ proto.end(fToken);
+ }
+ }
+ }
+
+ public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
+ JSONObject ranking = new JSONObject();
+ JSONArray PackagePreferencess = new JSONArray();
+ try {
+ ranking.put("noUid", mRestoredWithoutUids.size());
+ } catch (JSONException e) {
+ // pass
+ }
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ if (filter == null || filter.matches(r.pkg)) {
+ JSONObject PackagePreferences = new JSONObject();
+ try {
+ PackagePreferences.put("userId", UserHandle.getUserId(r.uid));
+ PackagePreferences.put("packageName", r.pkg);
+ if (r.importance != DEFAULT_IMPORTANCE) {
+ PackagePreferences.put("importance",
+ NotificationListenerService.Ranking.importanceToString(
+ r.importance));
+ }
+ if (r.priority != DEFAULT_PRIORITY) {
+ PackagePreferences.put("priority",
+ Notification.priorityToString(r.priority));
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ PackagePreferences.put("visibility",
+ Notification.visibilityToString(r.visibility));
+ }
+ if (r.showBadge != DEFAULT_SHOW_BADGE) {
+ PackagePreferences.put("showBadge", Boolean.valueOf(r.showBadge));
+ }
+ JSONArray channels = new JSONArray();
+ for (NotificationChannel channel : r.channels.values()) {
+ channels.put(channel.toJson());
+ }
+ PackagePreferences.put("channels", channels);
+ JSONArray groups = new JSONArray();
+ for (NotificationChannelGroup group : r.groups.values()) {
+ groups.put(group.toJson());
+ }
+ PackagePreferences.put("groups", groups);
+ } catch (JSONException e) {
+ // pass
+ }
+ PackagePreferencess.put(PackagePreferences);
+ }
+ }
+ }
+ try {
+ ranking.put("PackagePreferencess", PackagePreferencess);
+ } catch (JSONException e) {
+ // pass
+ }
+ return ranking;
+ }
+
+ /**
+ * Dump only the ban information as structured JSON for the stats collector.
+ *
+ * This is intentionally redundant with {#link dumpJson} because the old
+ * scraper will expect this format.
+ *
+ * @param filter
+ * @return
+ */
+ public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
+ JSONArray bans = new JSONArray();
+ Map<Integer, String> packageBans = getPackageBans();
+ for (Map.Entry<Integer, String> ban : packageBans.entrySet()) {
+ final int userId = UserHandle.getUserId(ban.getKey());
+ final String packageName = ban.getValue();
+ if (filter == null || filter.matches(packageName)) {
+ JSONObject banJson = new JSONObject();
+ try {
+ banJson.put("userId", userId);
+ banJson.put("packageName", packageName);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ bans.put(banJson);
+ }
+ }
+ return bans;
+ }
+
+ public Map<Integer, String> getPackageBans() {
+ synchronized (mPackagePreferencess) {
+ final int N = mPackagePreferencess.size();
+ ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
+ for (int i = 0; i < N; i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ if (r.importance == IMPORTANCE_NONE) {
+ packageBans.put(r.uid, r.pkg);
+ }
+ }
+
+ return packageBans;
+ }
+ }
+
+ /**
+ * Dump only the channel information as structured JSON for the stats collector.
+ *
+ * This is intentionally redundant with {#link dumpJson} because the old
+ * scraper will expect this format.
+ *
+ * @param filter
+ * @return
+ */
+ public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
+ JSONArray channels = new JSONArray();
+ Map<String, Integer> packageChannels = getPackageChannels();
+ for (Map.Entry<String, Integer> channelCount : packageChannels.entrySet()) {
+ final String packageName = channelCount.getKey();
+ if (filter == null || filter.matches(packageName)) {
+ JSONObject channelCountJson = new JSONObject();
+ try {
+ channelCountJson.put("packageName", packageName);
+ channelCountJson.put("channelCount", channelCount.getValue());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ channels.put(channelCountJson);
+ }
+ }
+ return channels;
+ }
+
+ private Map<String, Integer> getPackageChannels() {
+ ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
+ synchronized (mPackagePreferencess) {
+ for (int i = 0; i < mPackagePreferencess.size(); i++) {
+ final PackagePreferences r = mPackagePreferencess.valueAt(i);
+ int channelCount = 0;
+ for (int j = 0; j < r.channels.size(); j++) {
+ if (!r.channels.valueAt(j).isDeleted()) {
+ channelCount++;
+ }
+ }
+ packageChannels.put(r.pkg, channelCount);
+ }
+ }
+ return packageChannels;
+ }
+
+ public void onUserRemoved(int userId) {
+ synchronized (mPackagePreferencess) {
+ int N = mPackagePreferencess.size();
+ for (int i = N - 1; i >= 0; i--) {
+ PackagePreferences PackagePreferences = mPackagePreferencess.valueAt(i);
+ if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
+ mPackagePreferencess.removeAt(i);
+ }
+ }
+ }
+ }
+
+ protected void onLocaleChanged(Context context, int userId) {
+ synchronized (mPackagePreferencess) {
+ int N = mPackagePreferencess.size();
+ for (int i = 0; i < N; i++) {
+ PackagePreferences PackagePreferences = mPackagePreferencess.valueAt(i);
+ if (UserHandle.getUserId(PackagePreferences.uid) == userId) {
+ if (PackagePreferences.channels.containsKey(
+ NotificationChannel.DEFAULT_CHANNEL_ID)) {
+ PackagePreferences.channels.get(
+ NotificationChannel.DEFAULT_CHANNEL_ID).setName(
+ context.getResources().getString(
+ R.string.default_notification_channel_label));
+ }
+ }
+ }
+ }
+ }
+
+ public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
+ int[] uidList) {
+ if (pkgList == null || pkgList.length == 0) {
+ return; // nothing to do
+ }
+ boolean updated = false;
+ if (removingPackage) {
+ // Remove notification settings for uninstalled package
+ int size = Math.min(pkgList.length, uidList.length);
+ for (int i = 0; i < size; i++) {
+ final String pkg = pkgList[i];
+ final int uid = uidList[i];
+ synchronized (mPackagePreferencess) {
+ mPackagePreferencess.remove(packagePreferencesKey(pkg, uid));
+ }
+ mRestoredWithoutUids.remove(pkg);
+ updated = true;
+ }
+ } else {
+ for (String pkg : pkgList) {
+ // Package install
+ final PackagePreferences r = mRestoredWithoutUids.get(pkg);
+ if (r != null) {
+ try {
+ r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
+ mRestoredWithoutUids.remove(pkg);
+ synchronized (mPackagePreferencess) {
+ mPackagePreferencess.put(packagePreferencesKey(r.pkg, r.uid), r);
+ }
+ updated = true;
+ } catch (PackageManager.NameNotFoundException e) {
+ // noop
+ }
+ }
+ // Package upgrade
+ try {
+ PackagePreferences fullPackagePreferences = getPackagePreferences(pkg,
+ mPm.getPackageUidAsUser(pkg, changeUserId));
+ if (fullPackagePreferences != null) {
+ createDefaultChannelIfNeeded(fullPackagePreferences);
+ deleteDefaultChannelIfNeeded(fullPackagePreferences);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ }
+ }
+
+ if (updated) {
+ updateConfig();
+ }
+ }
+
+ private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
+ return new LogMaker(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .ACTION_NOTIFICATION_CHANNEL)
+ .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .setPackageName(pkg)
+ .addTaggedData(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .FIELD_NOTIFICATION_CHANNEL_ID,
+ channel.getId())
+ .addTaggedData(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
+ channel.getImportance());
+ }
+
+ private LogMaker getChannelGroupLog(String groupId, String pkg) {
+ return new LogMaker(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .ACTION_NOTIFICATION_CHANNEL_GROUP)
+ .setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_UPDATE)
+ .addTaggedData(
+ com.android.internal.logging.nano.MetricsProto.MetricsEvent
+ .FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
+ groupId)
+ .setPackageName(pkg);
+ }
+
+
+ public void updateBadgingEnabled() {
+ if (mBadgingEnabled == null) {
+ mBadgingEnabled = new SparseBooleanArray();
+ }
+ boolean changed = false;
+ // update the cached values
+ for (int index = 0; index < mBadgingEnabled.size(); index++) {
+ int userId = mBadgingEnabled.keyAt(index);
+ final boolean oldValue = mBadgingEnabled.get(userId);
+ final boolean newValue = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_BADGING,
+ DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
+ mBadgingEnabled.put(userId, newValue);
+ changed |= oldValue != newValue;
+ }
+ if (changed) {
+ updateConfig();
+ }
+ }
+
+ public boolean badgingEnabled(UserHandle userHandle) {
+ int userId = userHandle.getIdentifier();
+ if (userId == UserHandle.USER_ALL) {
+ return false;
+ }
+ if (mBadgingEnabled.indexOfKey(userId) < 0) {
+ mBadgingEnabled.put(userId,
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_BADGING,
+ DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
+ }
+ return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
+ }
+
+ private void updateConfig() {
+ mRankingHandler.requestSort();
+ }
+
+ private static String packagePreferencesKey(String pkg, int uid) {
+ return pkg + "|" + uid;
+ }
+
+ private static class PackagePreferences {
+ static int UNKNOWN_UID = UserHandle.USER_NULL;
+
+ String pkg;
+ int uid = UNKNOWN_UID;
+ int importance = DEFAULT_IMPORTANCE;
+ int priority = DEFAULT_PRIORITY;
+ int visibility = DEFAULT_VISIBILITY;
+ boolean showBadge = DEFAULT_SHOW_BADGE;
+ int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
+
+ ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
+ Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
+ }
+}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 61b5415ec7a3..f5e58ea9b27b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -15,123 +15,37 @@
*/
package com.android.server.notification;
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-
-import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.ParceledListSlice;
-import android.metrics.LogMaker;
-import android.os.Build;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.RankingHelperProto;
-import android.service.notification.RankingHelperProto.RecordProto;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
-import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-public class RankingHelper implements RankingConfig {
+public class RankingHelper {
private static final String TAG = "RankingHelper";
- private static final int XML_VERSION = 1;
-
- static final String TAG_RANKING = "ranking";
- private static final String TAG_PACKAGE = "package";
- private static final String TAG_CHANNEL = "channel";
- private static final String TAG_GROUP = "channelGroup";
-
- private static final String ATT_VERSION = "version";
- private static final String ATT_NAME = "name";
- private static final String ATT_UID = "uid";
- private static final String ATT_ID = "id";
- private static final String ATT_PRIORITY = "priority";
- private static final String ATT_VISIBILITY = "visibility";
- private static final String ATT_IMPORTANCE = "importance";
- private static final String ATT_SHOW_BADGE = "show_badge";
- private static final String ATT_APP_USER_LOCKED_FIELDS = "app_user_locked_fields";
-
- private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
- private static final int DEFAULT_VISIBILITY = NotificationManager.VISIBILITY_NO_OVERRIDE;
- private static final int DEFAULT_IMPORTANCE = NotificationManager.IMPORTANCE_UNSPECIFIED;
- private static final boolean DEFAULT_SHOW_BADGE = true;
- /**
- * Default value for what fields are user locked. See {@link LockableAppFields} for all lockable
- * fields.
- */
- private static final int DEFAULT_LOCKED_APP_FIELDS = 0;
-
- /**
- * All user-lockable fields for a given application.
- */
- @IntDef({LockableAppFields.USER_LOCKED_IMPORTANCE})
- public @interface LockableAppFields {
- int USER_LOCKED_IMPORTANCE = 0x00000001;
- }
-
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator;
private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
- private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
- private final ArrayMap<String, Record> mRestoredWithoutUids = new ArrayMap<>(); // pkg => Record
private final Context mContext;
private final RankingHandler mRankingHandler;
- private final PackageManager mPm;
- private SparseBooleanArray mBadgingEnabled;
- private boolean mAreChannelsBypassingDnd;
- private ZenModeHelper mZenModeHelper;
- public RankingHelper(Context context, PackageManager pm, RankingHandler rankingHandler,
+ public RankingHelper(Context context, RankingHandler rankingHandler, RankingConfig config,
ZenModeHelper zenHelper, NotificationUsageStats usageStats, String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
- mPm = pm;
- mZenModeHelper= zenHelper;
-
mPreliminaryComparator = new NotificationComparator(mContext);
- updateBadgingEnabled();
-
final int N = extractorNames.length;
mSignalExtractors = new NotificationSignalExtractor[N];
for (int i = 0; i < N; i++) {
@@ -140,7 +54,7 @@ public class RankingHelper implements RankingConfig {
NotificationSignalExtractor extractor =
(NotificationSignalExtractor) extractorClass.newInstance();
extractor.initialize(mContext, usageStats);
- extractor.setConfig(this);
+ extractor.setConfig(config);
extractor.setZenHelper(zenHelper);
mSignalExtractors[i] = extractor;
} catch (ClassNotFoundException e) {
@@ -151,10 +65,6 @@ public class RankingHelper implements RankingConfig {
Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
}
}
-
- mAreChannelsBypassingDnd = (mZenModeHelper.getNotificationPolicy().state &
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND) == 1;
- updateChannelsBypassingDnd();
}
@SuppressWarnings("unchecked")
@@ -184,279 +94,6 @@ public class RankingHelper implements RankingConfig {
}
}
- public void readXml(XmlPullParser parser, boolean forRestore)
- throws XmlPullParserException, IOException {
- int type = parser.getEventType();
- if (type != XmlPullParser.START_TAG) return;
- String tag = parser.getName();
- if (!TAG_RANKING.equals(tag)) return;
- // Clobber groups and channels with the xml, but don't delete other data that wasn't present
- // at the time of serialization.
- mRestoredWithoutUids.clear();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
- tag = parser.getName();
- if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
- return;
- }
- if (type == XmlPullParser.START_TAG) {
- if (TAG_PACKAGE.equals(tag)) {
- int uid = XmlUtils.readIntAttribute(parser, ATT_UID, Record.UNKNOWN_UID);
- String name = parser.getAttributeValue(null, ATT_NAME);
- if (!TextUtils.isEmpty(name)) {
- if (forRestore) {
- try {
- //TODO: http://b/22388012
- uid = mPm.getPackageUidAsUser(name, UserHandle.USER_SYSTEM);
- } catch (NameNotFoundException e) {
- // noop
- }
- }
-
- Record r = getOrCreateRecord(name, uid,
- XmlUtils.readIntAttribute(
- parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
- XmlUtils.readIntAttribute(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
- XmlUtils.readIntAttribute(
- parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
- XmlUtils.readBooleanAttribute(
- parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
- r.importance = XmlUtils.readIntAttribute(
- parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
- r.priority = XmlUtils.readIntAttribute(
- parser, ATT_PRIORITY, DEFAULT_PRIORITY);
- r.visibility = XmlUtils.readIntAttribute(
- parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
- r.showBadge = XmlUtils.readBooleanAttribute(
- parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
- r.lockedAppFields = XmlUtils.readIntAttribute(parser,
- ATT_APP_USER_LOCKED_FIELDS, DEFAULT_LOCKED_APP_FIELDS);
-
- final int innerDepth = parser.getDepth();
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG
- || parser.getDepth() > innerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- // Channel groups
- if (TAG_GROUP.equals(tagName)) {
- String id = parser.getAttributeValue(null, ATT_ID);
- CharSequence groupName = parser.getAttributeValue(null, ATT_NAME);
- if (!TextUtils.isEmpty(id)) {
- NotificationChannelGroup group
- = new NotificationChannelGroup(id, groupName);
- group.populateFromXml(parser);
- r.groups.put(id, group);
- }
- }
- // Channels
- if (TAG_CHANNEL.equals(tagName)) {
- String id = parser.getAttributeValue(null, ATT_ID);
- String channelName = parser.getAttributeValue(null, ATT_NAME);
- int channelImportance = XmlUtils.readIntAttribute(
- parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
- if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
- NotificationChannel channel = new NotificationChannel(id,
- channelName, channelImportance);
- if (forRestore) {
- channel.populateFromXmlForRestore(parser, mContext);
- } else {
- channel.populateFromXml(parser);
- }
- r.channels.put(id, channel);
- }
- }
- }
-
- try {
- deleteDefaultChannelIfNeeded(r);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "deleteDefaultChannelIfNeeded - Exception: " + e);
- }
- }
- }
- }
- }
- throw new IllegalStateException("Failed to reach END_DOCUMENT");
- }
-
- private static String recordKey(String pkg, int uid) {
- return pkg + "|" + uid;
- }
-
- private Record getRecord(String pkg, int uid) {
- final String key = recordKey(pkg, uid);
- synchronized (mRecords) {
- return mRecords.get(key);
- }
- }
-
- private Record getOrCreateRecord(String pkg, int uid) {
- return getOrCreateRecord(pkg, uid,
- DEFAULT_IMPORTANCE, DEFAULT_PRIORITY, DEFAULT_VISIBILITY, DEFAULT_SHOW_BADGE);
- }
-
- private Record getOrCreateRecord(String pkg, int uid, int importance, int priority,
- int visibility, boolean showBadge) {
- final String key = recordKey(pkg, uid);
- synchronized (mRecords) {
- Record r = (uid == Record.UNKNOWN_UID) ? mRestoredWithoutUids.get(pkg) : mRecords.get(
- key);
- if (r == null) {
- r = new Record();
- r.pkg = pkg;
- r.uid = uid;
- r.importance = importance;
- r.priority = priority;
- r.visibility = visibility;
- r.showBadge = showBadge;
-
- try {
- createDefaultChannelIfNeeded(r);
- } catch (NameNotFoundException e) {
- Slog.e(TAG, "createDefaultChannelIfNeeded - Exception: " + e);
- }
-
- if (r.uid == Record.UNKNOWN_UID) {
- mRestoredWithoutUids.put(pkg, r);
- } else {
- mRecords.put(key, r);
- }
- }
- return r;
- }
- }
-
- private boolean shouldHaveDefaultChannel(Record r) throws NameNotFoundException {
- final int userId = UserHandle.getUserId(r.uid);
- final ApplicationInfo applicationInfo = mPm.getApplicationInfoAsUser(r.pkg, 0, userId);
- if (applicationInfo.targetSdkVersion >= Build.VERSION_CODES.O) {
- // O apps should not have the default channel.
- return false;
- }
-
- // Otherwise, this app should have the default channel.
- return true;
- }
-
- private void deleteDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
- if (!r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- // Not present
- return;
- }
-
- if (shouldHaveDefaultChannel(r)) {
- // Keep the default channel until upgraded.
- return;
- }
-
- // Remove Default Channel.
- r.channels.remove(NotificationChannel.DEFAULT_CHANNEL_ID);
- }
-
- private void createDefaultChannelIfNeeded(Record r) throws NameNotFoundException {
- if (r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- r.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
- mContext.getString(R.string.default_notification_channel_label));
- return;
- }
-
- if (!shouldHaveDefaultChannel(r)) {
- // Keep the default channel until upgraded.
- return;
- }
-
- // Create Default Channel
- NotificationChannel channel;
- channel = new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID,
- mContext.getString(R.string.default_notification_channel_label),
- r.importance);
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- channel.setLockscreenVisibility(r.visibility);
- if (r.importance != NotificationManager.IMPORTANCE_UNSPECIFIED) {
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- }
- if (r.priority != DEFAULT_PRIORITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- channel.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- }
- r.channels.put(channel.getId(), channel);
- }
-
- public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
- out.startTag(null, TAG_RANKING);
- out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
-
- synchronized (mRecords) {
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- //TODO: http://b/22388012
- if (forBackup && UserHandle.getUserId(r.uid) != UserHandle.USER_SYSTEM) {
- continue;
- }
- final boolean hasNonDefaultSettings =
- r.importance != DEFAULT_IMPORTANCE
- || r.priority != DEFAULT_PRIORITY
- || r.visibility != DEFAULT_VISIBILITY
- || r.showBadge != DEFAULT_SHOW_BADGE
- || r.lockedAppFields != DEFAULT_LOCKED_APP_FIELDS
- || r.channels.size() > 0
- || r.groups.size() > 0;
- if (hasNonDefaultSettings) {
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, r.pkg);
- if (r.importance != DEFAULT_IMPORTANCE) {
- out.attribute(null, ATT_IMPORTANCE, Integer.toString(r.importance));
- }
- if (r.priority != DEFAULT_PRIORITY) {
- out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
- }
- out.attribute(null, ATT_SHOW_BADGE, Boolean.toString(r.showBadge));
- out.attribute(null, ATT_APP_USER_LOCKED_FIELDS,
- Integer.toString(r.lockedAppFields));
-
- if (!forBackup) {
- out.attribute(null, ATT_UID, Integer.toString(r.uid));
- }
-
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeXml(out);
- }
-
- for (NotificationChannel channel : r.channels.values()) {
- if (forBackup) {
- if (!channel.isDeleted()) {
- channel.writeXmlForBackup(out, mContext);
- }
- } else {
- channel.writeXml(out);
- }
- }
-
- out.endTag(null, TAG_PACKAGE);
- }
- }
- }
- out.endTag(null, TAG_RANKING);
- }
-
- private void updateConfig() {
- final int N = mSignalExtractors.length;
- for (int i = 0; i < N; i++) {
- mSignalExtractors[i].setConfig(this);
- }
- mRankingHandler.requestSort();
- }
-
public void sort(ArrayList<NotificationRecord> notificationList) {
final int N = notificationList.size();
// clear global sort keys
@@ -521,562 +158,6 @@ public class RankingHelper implements RankingConfig {
return Collections.binarySearch(notificationList, target, mFinalComparator);
}
- /**
- * Gets importance.
- */
- @Override
- public int getImportance(String packageName, int uid) {
- return getOrCreateRecord(packageName, uid).importance;
- }
-
-
- /**
- * Returns whether the importance of the corresponding notification is user-locked and shouldn't
- * be adjusted by an assistant (via means of a blocking helper, for example). For the channel
- * locking field, see {@link NotificationChannel#USER_LOCKED_IMPORTANCE}.
- */
- public boolean getIsAppImportanceLocked(String packageName, int uid) {
- int userLockedFields = getOrCreateRecord(packageName, uid).lockedAppFields;
- return (userLockedFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0;
- }
-
- @Override
- public boolean canShowBadge(String packageName, int uid) {
- return getOrCreateRecord(packageName, uid).showBadge;
- }
-
- @Override
- public void setShowBadge(String packageName, int uid, boolean showBadge) {
- getOrCreateRecord(packageName, uid).showBadge = showBadge;
- updateConfig();
- }
-
- @Override
- public boolean isGroupBlocked(String packageName, int uid, String groupId) {
- if (groupId == null) {
- return false;
- }
- Record r = getOrCreateRecord(packageName, uid);
- NotificationChannelGroup group = r.groups.get(groupId);
- if (group == null) {
- return false;
- }
- return group.isBlocked();
- }
-
- int getPackagePriority(String pkg, int uid) {
- return getOrCreateRecord(pkg, uid).priority;
- }
-
- int getPackageVisibility(String pkg, int uid) {
- return getOrCreateRecord(pkg, uid).visibility;
- }
-
- @Override
- public void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
- boolean fromTargetApp) {
- Preconditions.checkNotNull(pkg);
- Preconditions.checkNotNull(group);
- Preconditions.checkNotNull(group.getId());
- Preconditions.checkNotNull(!TextUtils.isEmpty(group.getName()));
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- final NotificationChannelGroup oldGroup = r.groups.get(group.getId());
- if (!group.equals(oldGroup)) {
- // will log for new entries as well as name/description changes
- MetricsLogger.action(getChannelGroupLog(group.getId(), pkg));
- }
- if (oldGroup != null) {
- group.setChannels(oldGroup.getChannels());
-
- if (fromTargetApp) {
- group.setBlocked(oldGroup.isBlocked());
- }
- }
- r.groups.put(group.getId(), group);
- }
-
- @Override
- public void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
- boolean fromTargetApp, boolean hasDndAccess) {
- Preconditions.checkNotNull(pkg);
- Preconditions.checkNotNull(channel);
- Preconditions.checkNotNull(channel.getId());
- Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()));
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- if (channel.getGroup() != null && !r.groups.containsKey(channel.getGroup())) {
- throw new IllegalArgumentException("NotificationChannelGroup doesn't exist");
- }
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) {
- throw new IllegalArgumentException("Reserved id");
- }
- NotificationChannel existing = r.channels.get(channel.getId());
- // Keep most of the existing settings
- if (existing != null && fromTargetApp) {
- if (existing.isDeleted()) {
- existing.setDeleted(false);
-
- // log a resurrected channel as if it's new again
- MetricsLogger.action(getChannelLog(channel, pkg).setType(
- MetricsProto.MetricsEvent.TYPE_OPEN));
- }
-
- existing.setName(channel.getName().toString());
- existing.setDescription(channel.getDescription());
- existing.setBlockableSystem(channel.isBlockableSystem());
- if (existing.getGroup() == null) {
- existing.setGroup(channel.getGroup());
- }
-
- // Apps are allowed to downgrade channel importance if the user has not changed any
- // fields on this channel yet.
- if (existing.getUserLockedFields() == 0 &&
- channel.getImportance() < existing.getImportance()) {
- existing.setImportance(channel.getImportance());
- }
-
- // system apps and dnd access apps can bypass dnd if the user hasn't changed any
- // fields on the channel yet
- if (existing.getUserLockedFields() == 0 && hasDndAccess) {
- boolean bypassDnd = channel.canBypassDnd();
- existing.setBypassDnd(bypassDnd);
-
- if (bypassDnd != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- }
-
- updateConfig();
- return;
- }
- if (channel.getImportance() < IMPORTANCE_NONE
- || channel.getImportance() > NotificationManager.IMPORTANCE_MAX) {
- throw new IllegalArgumentException("Invalid importance level");
- }
-
- // Reset fields that apps aren't allowed to set.
- if (fromTargetApp && !hasDndAccess) {
- channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX);
- }
- if (fromTargetApp) {
- channel.setLockscreenVisibility(r.visibility);
- }
- clearLockedFields(channel);
- if (channel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
- channel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
- }
- if (!r.showBadge) {
- channel.setShowBadge(false);
- }
-
- r.channels.put(channel.getId(), channel);
- if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- MetricsLogger.action(getChannelLog(channel, pkg).setType(
- MetricsProto.MetricsEvent.TYPE_OPEN));
- }
-
- void clearLockedFields(NotificationChannel channel) {
- channel.unlockFields(channel.getUserLockedFields());
- }
-
- @Override
- public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
- boolean fromUser) {
- Preconditions.checkNotNull(updatedChannel);
- Preconditions.checkNotNull(updatedChannel.getId());
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- throw new IllegalArgumentException("Invalid package");
- }
- NotificationChannel channel = r.channels.get(updatedChannel.getId());
- if (channel == null || channel.isDeleted()) {
- throw new IllegalArgumentException("Channel does not exist");
- }
- if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
- updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
- }
- if (!fromUser) {
- updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
- }
- if (fromUser) {
- updatedChannel.lockFields(channel.getUserLockedFields());
- lockFieldsForUpdate(channel, updatedChannel);
- }
- r.channels.put(updatedChannel.getId(), updatedChannel);
-
- if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
- // copy settings to app level so they are inherited by new channels
- // when the app migrates
- r.importance = updatedChannel.getImportance();
- r.priority = updatedChannel.canBypassDnd()
- ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
- r.visibility = updatedChannel.getLockscreenVisibility();
- r.showBadge = updatedChannel.canShowBadge();
- }
-
- if (!channel.equals(updatedChannel)) {
- // only log if there are real changes
- MetricsLogger.action(getChannelLog(updatedChannel, pkg));
- }
-
- if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd) {
- updateChannelsBypassingDnd();
- }
- updateConfig();
- }
-
- @Override
- public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
- boolean includeDeleted) {
- Preconditions.checkNotNull(pkg);
- Record r = getOrCreateRecord(pkg, uid);
- if (r == null) {
- return null;
- }
- if (channelId == null) {
- channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
- }
- final NotificationChannel nc = r.channels.get(channelId);
- if (nc != null && (includeDeleted || !nc.isDeleted())) {
- return nc;
- }
- return null;
- }
-
- @Override
- public void deleteNotificationChannel(String pkg, int uid, String channelId) {
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return;
- }
- NotificationChannel channel = r.channels.get(channelId);
- if (channel != null) {
- channel.setDeleted(true);
- LogMaker lm = getChannelLog(channel, pkg);
- lm.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
- MetricsLogger.action(lm);
-
- if (mAreChannelsBypassingDnd && channel.canBypassDnd()) {
- updateChannelsBypassingDnd();
- }
- }
- }
-
- @Override
- @VisibleForTesting
- public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) {
- Preconditions.checkNotNull(pkg);
- Preconditions.checkNotNull(channelId);
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return;
- }
- r.channels.remove(channelId);
- }
-
- @Override
- public void permanentlyDeleteNotificationChannels(String pkg, int uid) {
- Preconditions.checkNotNull(pkg);
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return;
- }
- int N = r.channels.size() - 1;
- for (int i = N; i >= 0; i--) {
- String key = r.channels.keyAt(i);
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(key)) {
- r.channels.remove(key);
- }
- }
- }
-
- public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg,
- int uid, String groupId, boolean includeDeleted) {
- Preconditions.checkNotNull(pkg);
- Record r = getRecord(pkg, uid);
- if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
- return null;
- }
- NotificationChannelGroup group = r.groups.get(groupId).clone();
- group.setChannels(new ArrayList<>());
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- if (groupId.equals(nc.getGroup())) {
- group.addChannel(nc);
- }
- }
- }
- return group;
- }
-
- public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg,
- int uid) {
- Preconditions.checkNotNull(pkg);
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return null;
- }
- return r.groups.get(groupId);
- }
-
- @Override
- public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted, boolean includeNonGrouped) {
- Preconditions.checkNotNull(pkg);
- Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return ParceledListSlice.emptyList();
- }
- NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- if (nc.getGroup() != null) {
- if (r.groups.get(nc.getGroup()) != null) {
- NotificationChannelGroup ncg = groups.get(nc.getGroup());
- if (ncg == null) {
- ncg = r.groups.get(nc.getGroup()).clone();
- ncg.setChannels(new ArrayList<>());
- groups.put(nc.getGroup(), ncg);
-
- }
- ncg.addChannel(nc);
- }
- } else {
- nonGrouped.addChannel(nc);
- }
- }
- }
- if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
- groups.put(null, nonGrouped);
- }
- return new ParceledListSlice<>(new ArrayList<>(groups.values()));
- }
-
- public List<NotificationChannel> deleteNotificationChannelGroup(String pkg, int uid,
- String groupId) {
- List<NotificationChannel> deletedChannels = new ArrayList<>();
- Record r = getRecord(pkg, uid);
- if (r == null || TextUtils.isEmpty(groupId)) {
- return deletedChannels;
- }
-
- r.groups.remove(groupId);
-
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (groupId.equals(nc.getGroup())) {
- nc.setDeleted(true);
- deletedChannels.add(nc);
- }
- }
- return deletedChannels;
- }
-
- @Override
- public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid) {
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return new ArrayList<>();
- }
- return r.groups.values();
- }
-
- @Override
- public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
- boolean includeDeleted) {
- Preconditions.checkNotNull(pkg);
- List<NotificationChannel> channels = new ArrayList<>();
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return ParceledListSlice.emptyList();
- }
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- channels.add(nc);
- }
- }
- return new ParceledListSlice<>(channels);
- }
-
- /**
- * True for pre-O apps that only have the default channel, or pre O apps that have no
- * channels yet. This method will create the default channel for pre-O apps that don't have it.
- * Should never be true for O+ targeting apps, but that's enforced on boot/when an app
- * upgrades.
- */
- public boolean onlyHasDefaultChannel(String pkg, int uid) {
- Record r = getOrCreateRecord(pkg, uid);
- if (r.channels.size() == 1
- && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- return true;
- }
- return false;
- }
-
- public int getDeletedChannelCount(String pkg, int uid) {
- Preconditions.checkNotNull(pkg);
- int deletedCount = 0;
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return deletedCount;
- }
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (nc.isDeleted()) {
- deletedCount++;
- }
- }
- return deletedCount;
- }
-
- public int getBlockedChannelCount(String pkg, int uid) {
- Preconditions.checkNotNull(pkg);
- int blockedCount = 0;
- Record r = getRecord(pkg, uid);
- if (r == null) {
- return blockedCount;
- }
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (!nc.isDeleted() && IMPORTANCE_NONE == nc.getImportance()) {
- blockedCount++;
- }
- }
- return blockedCount;
- }
-
- public int getBlockedAppCount(int userId) {
- int count = 0;
- synchronized (mRecords) {
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- if (userId == UserHandle.getUserId(r.uid)
- && r.importance == IMPORTANCE_NONE) {
- count++;
- }
- }
- }
- return count;
- }
-
- public void updateChannelsBypassingDnd() {
- synchronized (mRecords) {
- final int numRecords = mRecords.size();
- for (int recordIndex = 0; recordIndex < numRecords; recordIndex++) {
- final Record r = mRecords.valueAt(recordIndex);
- final int numChannels = r.channels.size();
-
- for (int channelIndex = 0; channelIndex < numChannels; channelIndex++) {
- NotificationChannel channel = r.channels.valueAt(channelIndex);
- if (!channel.isDeleted() && channel.canBypassDnd()) {
- if (!mAreChannelsBypassingDnd) {
- mAreChannelsBypassingDnd = true;
- updateZenPolicy(true);
- }
- return;
- }
- }
- }
- }
-
- if (mAreChannelsBypassingDnd) {
- mAreChannelsBypassingDnd = false;
- updateZenPolicy(false);
- }
- }
-
- public void updateZenPolicy(boolean areChannelsBypassingDnd) {
- NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy();
- mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy(
- policy.priorityCategories, policy.priorityCallSenders,
- policy.priorityMessageSenders, policy.suppressedVisualEffects,
- (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND
- : 0)));
- }
-
- public boolean areChannelsBypassingDnd() {
- return mAreChannelsBypassingDnd;
- }
-
- /**
- * Sets importance.
- */
- @Override
- public void setImportance(String pkgName, int uid, int importance) {
- getOrCreateRecord(pkgName, uid).importance = importance;
- updateConfig();
- }
-
- public void setEnabled(String packageName, int uid, boolean enabled) {
- boolean wasEnabled = getImportance(packageName, uid) != IMPORTANCE_NONE;
- if (wasEnabled == enabled) {
- return;
- }
- setImportance(packageName, uid,
- enabled ? DEFAULT_IMPORTANCE : IMPORTANCE_NONE);
- }
-
- /**
- * Sets whether any notifications from the app, represented by the given {@code pkgName} and
- * {@code uid}, have their importance locked by the user. Locked notifications don't get
- * considered for sentiment adjustments (and thus never show a blocking helper).
- */
- public void setAppImportanceLocked(String packageName, int uid) {
- Record record = getOrCreateRecord(packageName, uid);
- if ((record.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) {
- return;
- }
-
- record.lockedAppFields = record.lockedAppFields | LockableAppFields.USER_LOCKED_IMPORTANCE;
- updateConfig();
- }
-
- @VisibleForTesting
- void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
- if (original.canBypassDnd() != update.canBypassDnd()) {
- update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- }
- if (original.getLockscreenVisibility() != update.getLockscreenVisibility()) {
- update.lockFields(NotificationChannel.USER_LOCKED_VISIBILITY);
- }
- if (original.getImportance() != update.getImportance()) {
- update.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
- }
- if (original.shouldShowLights() != update.shouldShowLights()
- || original.getLightColor() != update.getLightColor()) {
- update.lockFields(NotificationChannel.USER_LOCKED_LIGHTS);
- }
- if (!Objects.equals(original.getSound(), update.getSound())) {
- update.lockFields(NotificationChannel.USER_LOCKED_SOUND);
- }
- if (!Arrays.equals(original.getVibrationPattern(), update.getVibrationPattern())
- || original.shouldVibrate() != update.shouldVibrate()) {
- update.lockFields(NotificationChannel.USER_LOCKED_VIBRATION);
- }
- if (original.canShowBadge() != update.canShowBadge()) {
- update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
- }
- }
-
public void dump(PrintWriter pw, String prefix,
@NonNull NotificationManagerService.DumpFilter filter) {
final int N = mSignalExtractors.length;
@@ -1088,16 +169,6 @@ public class RankingHelper implements RankingConfig {
pw.print(" ");
pw.println(mSignalExtractors[i].getClass().getSimpleName());
}
-
- pw.print(prefix);
- pw.println("per-package config:");
-
- pw.println("Records:");
- synchronized (mRecords) {
- dumpRecords(pw, prefix, filter, mRecords);
- }
- pw.println("Restored without uid:");
- dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
}
public void dump(ProtoOutputStream proto,
@@ -1105,375 +176,7 @@ public class RankingHelper implements RankingConfig {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
- mSignalExtractors[i].getClass().getSimpleName());
+ mSignalExtractors[i].getClass().getSimpleName());
}
- synchronized (mRecords) {
- dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
- }
- dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
- mRestoredWithoutUids);
}
-
- private static void dumpRecords(ProtoOutputStream proto, long fieldId,
- @NonNull NotificationManagerService.DumpFilter filter,
- ArrayMap<String, Record> records) {
- final int N = records.size();
- long fToken;
- for (int i = 0; i < N; i++) {
- final Record r = records.valueAt(i);
- if (filter.matches(r.pkg)) {
- fToken = proto.start(fieldId);
-
- proto.write(RecordProto.PACKAGE, r.pkg);
- proto.write(RecordProto.UID, r.uid);
- proto.write(RecordProto.IMPORTANCE, r.importance);
- proto.write(RecordProto.PRIORITY, r.priority);
- proto.write(RecordProto.VISIBILITY, r.visibility);
- proto.write(RecordProto.SHOW_BADGE, r.showBadge);
-
- for (NotificationChannel channel : r.channels.values()) {
- channel.writeToProto(proto, RecordProto.CHANNELS);
- }
- for (NotificationChannelGroup group : r.groups.values()) {
- group.writeToProto(proto, RecordProto.CHANNEL_GROUPS);
- }
-
- proto.end(fToken);
- }
- }
- }
-
- private static void dumpRecords(PrintWriter pw, String prefix,
- @NonNull NotificationManagerService.DumpFilter filter,
- ArrayMap<String, Record> records) {
- final int N = records.size();
- for (int i = 0; i < N; i++) {
- final Record r = records.valueAt(i);
- if (filter.matches(r.pkg)) {
- pw.print(prefix);
- pw.print(" AppSettings: ");
- pw.print(r.pkg);
- pw.print(" (");
- pw.print(r.uid == Record.UNKNOWN_UID ? "UNKNOWN_UID" : Integer.toString(r.uid));
- pw.print(')');
- if (r.importance != DEFAULT_IMPORTANCE) {
- pw.print(" importance=");
- pw.print(Ranking.importanceToString(r.importance));
- }
- if (r.priority != DEFAULT_PRIORITY) {
- pw.print(" priority=");
- pw.print(Notification.priorityToString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- pw.print(" visibility=");
- pw.print(Notification.visibilityToString(r.visibility));
- }
- pw.print(" showBadge=");
- pw.print(Boolean.toString(r.showBadge));
- pw.println();
- for (NotificationChannel channel : r.channels.values()) {
- pw.print(prefix);
- pw.print(" ");
- pw.print(" ");
- pw.println(channel);
- }
- for (NotificationChannelGroup group : r.groups.values()) {
- pw.print(prefix);
- pw.print(" ");
- pw.print(" ");
- pw.println(group);
- }
- }
- }
- }
-
- public JSONObject dumpJson(NotificationManagerService.DumpFilter filter) {
- JSONObject ranking = new JSONObject();
- JSONArray records = new JSONArray();
- try {
- ranking.put("noUid", mRestoredWithoutUids.size());
- } catch (JSONException e) {
- // pass
- }
- synchronized (mRecords) {
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- if (filter == null || filter.matches(r.pkg)) {
- JSONObject record = new JSONObject();
- try {
- record.put("userId", UserHandle.getUserId(r.uid));
- record.put("packageName", r.pkg);
- if (r.importance != DEFAULT_IMPORTANCE) {
- record.put("importance", Ranking.importanceToString(r.importance));
- }
- if (r.priority != DEFAULT_PRIORITY) {
- record.put("priority", Notification.priorityToString(r.priority));
- }
- if (r.visibility != DEFAULT_VISIBILITY) {
- record.put("visibility", Notification.visibilityToString(r.visibility));
- }
- if (r.showBadge != DEFAULT_SHOW_BADGE) {
- record.put("showBadge", Boolean.valueOf(r.showBadge));
- }
- JSONArray channels = new JSONArray();
- for (NotificationChannel channel : r.channels.values()) {
- channels.put(channel.toJson());
- }
- record.put("channels", channels);
- JSONArray groups = new JSONArray();
- for (NotificationChannelGroup group : r.groups.values()) {
- groups.put(group.toJson());
- }
- record.put("groups", groups);
- } catch (JSONException e) {
- // pass
- }
- records.put(record);
- }
- }
- }
- try {
- ranking.put("records", records);
- } catch (JSONException e) {
- // pass
- }
- return ranking;
- }
-
- /**
- * Dump only the ban information as structured JSON for the stats collector.
- *
- * This is intentionally redundant with {#link dumpJson} because the old
- * scraper will expect this format.
- *
- * @param filter
- * @return
- */
- public JSONArray dumpBansJson(NotificationManagerService.DumpFilter filter) {
- JSONArray bans = new JSONArray();
- Map<Integer, String> packageBans = getPackageBans();
- for(Entry<Integer, String> ban : packageBans.entrySet()) {
- final int userId = UserHandle.getUserId(ban.getKey());
- final String packageName = ban.getValue();
- if (filter == null || filter.matches(packageName)) {
- JSONObject banJson = new JSONObject();
- try {
- banJson.put("userId", userId);
- banJson.put("packageName", packageName);
- } catch (JSONException e) {
- e.printStackTrace();
- }
- bans.put(banJson);
- }
- }
- return bans;
- }
-
- public Map<Integer, String> getPackageBans() {
- synchronized (mRecords) {
- final int N = mRecords.size();
- ArrayMap<Integer, String> packageBans = new ArrayMap<>(N);
- for (int i = 0; i < N; i++) {
- final Record r = mRecords.valueAt(i);
- if (r.importance == IMPORTANCE_NONE) {
- packageBans.put(r.uid, r.pkg);
- }
- }
-
- return packageBans;
- }
- }
-
- /**
- * Dump only the channel information as structured JSON for the stats collector.
- *
- * This is intentionally redundant with {#link dumpJson} because the old
- * scraper will expect this format.
- *
- * @param filter
- * @return
- */
- public JSONArray dumpChannelsJson(NotificationManagerService.DumpFilter filter) {
- JSONArray channels = new JSONArray();
- Map<String, Integer> packageChannels = getPackageChannels();
- for(Entry<String, Integer> channelCount : packageChannels.entrySet()) {
- final String packageName = channelCount.getKey();
- if (filter == null || filter.matches(packageName)) {
- JSONObject channelCountJson = new JSONObject();
- try {
- channelCountJson.put("packageName", packageName);
- channelCountJson.put("channelCount", channelCount.getValue());
- } catch (JSONException e) {
- e.printStackTrace();
- }
- channels.put(channelCountJson);
- }
- }
- return channels;
- }
-
- private Map<String, Integer> getPackageChannels() {
- ArrayMap<String, Integer> packageChannels = new ArrayMap<>();
- synchronized (mRecords) {
- for (int i = 0; i < mRecords.size(); i++) {
- final Record r = mRecords.valueAt(i);
- int channelCount = 0;
- for (int j = 0; j < r.channels.size(); j++) {
- if (!r.channels.valueAt(j).isDeleted()) {
- channelCount++;
- }
- }
- packageChannels.put(r.pkg, channelCount);
- }
- }
- return packageChannels;
- }
-
- public void onUserRemoved(int userId) {
- synchronized (mRecords) {
- int N = mRecords.size();
- for (int i = N - 1; i >= 0 ; i--) {
- Record record = mRecords.valueAt(i);
- if (UserHandle.getUserId(record.uid) == userId) {
- mRecords.removeAt(i);
- }
- }
- }
- }
-
- protected void onLocaleChanged(Context context, int userId) {
- synchronized (mRecords) {
- int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- Record record = mRecords.valueAt(i);
- if (UserHandle.getUserId(record.uid) == userId) {
- if (record.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) {
- record.channels.get(NotificationChannel.DEFAULT_CHANNEL_ID).setName(
- context.getResources().getString(
- R.string.default_notification_channel_label));
- }
- }
- }
- }
- }
-
- public void onPackagesChanged(boolean removingPackage, int changeUserId, String[] pkgList,
- int[] uidList) {
- if (pkgList == null || pkgList.length == 0) {
- return; // nothing to do
- }
- boolean updated = false;
- if (removingPackage) {
- // Remove notification settings for uninstalled package
- int size = Math.min(pkgList.length, uidList.length);
- for (int i = 0; i < size; i++) {
- final String pkg = pkgList[i];
- final int uid = uidList[i];
- synchronized (mRecords) {
- mRecords.remove(recordKey(pkg, uid));
- }
- mRestoredWithoutUids.remove(pkg);
- updated = true;
- }
- } else {
- for (String pkg : pkgList) {
- // Package install
- final Record r = mRestoredWithoutUids.get(pkg);
- if (r != null) {
- try {
- r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId);
- mRestoredWithoutUids.remove(pkg);
- synchronized (mRecords) {
- mRecords.put(recordKey(r.pkg, r.uid), r);
- }
- updated = true;
- } catch (NameNotFoundException e) {
- // noop
- }
- }
- // Package upgrade
- try {
- Record fullRecord = getRecord(pkg,
- mPm.getPackageUidAsUser(pkg, changeUserId));
- if (fullRecord != null) {
- createDefaultChannelIfNeeded(fullRecord);
- deleteDefaultChannelIfNeeded(fullRecord);
- }
- } catch (NameNotFoundException e) {}
- }
- }
-
- if (updated) {
- updateConfig();
- }
- }
-
- private LogMaker getChannelLog(NotificationChannel channel, String pkg) {
- return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL)
- .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
- .setPackageName(pkg)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_ID,
- channel.getId())
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_IMPORTANCE,
- channel.getImportance());
- }
-
- private LogMaker getChannelGroupLog(String groupId, String pkg) {
- return new LogMaker(MetricsProto.MetricsEvent.ACTION_NOTIFICATION_CHANNEL_GROUP)
- .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
- .addTaggedData(MetricsProto.MetricsEvent.FIELD_NOTIFICATION_CHANNEL_GROUP_ID,
- groupId)
- .setPackageName(pkg);
- }
-
- public void updateBadgingEnabled() {
- if (mBadgingEnabled == null) {
- mBadgingEnabled = new SparseBooleanArray();
- }
- boolean changed = false;
- // update the cached values
- for (int index = 0; index < mBadgingEnabled.size(); index++) {
- int userId = mBadgingEnabled.keyAt(index);
- final boolean oldValue = mBadgingEnabled.get(userId);
- final boolean newValue = Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NOTIFICATION_BADGING,
- DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0;
- mBadgingEnabled.put(userId, newValue);
- changed |= oldValue != newValue;
- }
- if (changed) {
- updateConfig();
- }
- }
-
- public boolean badgingEnabled(UserHandle userHandle) {
- int userId = userHandle.getIdentifier();
- if (userId == UserHandle.USER_ALL) {
- return false;
- }
- if (mBadgingEnabled.indexOfKey(userId) < 0) {
- mBadgingEnabled.put(userId,
- Secure.getIntForUser(mContext.getContentResolver(),
- Secure.NOTIFICATION_BADGING,
- DEFAULT_SHOW_BADGE ? 1 : 0, userId) != 0);
- }
- return mBadgingEnabled.get(userId, DEFAULT_SHOW_BADGE);
- }
-
-
- private static class Record {
- static int UNKNOWN_UID = UserHandle.USER_NULL;
-
- String pkg;
- int uid = UNKNOWN_UID;
- int importance = DEFAULT_IMPORTANCE;
- int priority = DEFAULT_PRIORITY;
- int visibility = DEFAULT_VISIBILITY;
- boolean showBadge = DEFAULT_SHOW_BADGE;
- int lockedAppFields = DEFAULT_LOCKED_APP_FIELDS;
-
- ArrayMap<String, NotificationChannel> channels = new ArrayMap<>();
- Map<String, NotificationChannelGroup> groups = new ConcurrentHashMap<>();
- }
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index fa934fe23fff..b92d52cb870a 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -685,13 +685,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// inserted above to hold the session active.
try {
final Int64Ref last = new Int64Ref(0);
- FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, (long progress) -> {
- if (params.sizeBytes > 0) {
- final long delta = progress - last.value;
- last.value = progress;
- addClientProgress((float) delta / (float) params.sizeBytes);
- }
- }, null, lengthBytes);
+ FileUtils.copy(incomingFd.getFileDescriptor(), targetFd, lengthBytes, null,
+ Runnable::run, (long progress) -> {
+ if (params.sizeBytes > 0) {
+ final long delta = progress - last.value;
+ last.value = progress;
+ addClientProgress((float) delta / (float) params.sizeBytes);
+ }
+ });
} finally {
IoUtils.closeQuietly(targetFd);
IoUtils.closeQuietly(incomingFd);
@@ -930,6 +931,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private void commitLocked()
throws PackageManagerException {
+ if (mRelinquished) {
+ Slog.d(TAG, "Ignoring commit after previous commit relinquished control");
+ return;
+ }
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 734a872b0fcc..3d5d079fc8e1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2568,6 +2568,8 @@ public class PackageManagerService extends IPackageManager.Stub
mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_MR1;
+ int preUpgradeSdkVersion = ver.sdkVersion;
+
// save off the names of pre-existing system packages prior to scanning; we don't
// want to automatically grant runtime permissions for new system apps
if (mPromoteSystemApps) {
@@ -3161,6 +3163,58 @@ public class PackageManagerService extends IPackageManager.Stub
checkDefaultBrowser();
+ // If a granted permission is split, all new permissions should be granted too
+ if (mIsUpgrade) {
+ final int callingUid = getCallingUid();
+
+ final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+ for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+ final PackageParser.SplitPermissionInfo splitPerm =
+ PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+ final String rootPerm = splitPerm.rootPerm;
+
+ if (preUpgradeSdkVersion >= splitPerm.targetSdk) {
+ continue;
+ }
+
+ final int numPackages = mPackages.size();
+ for (int packageNum = 0; packageNum < numPackages; packageNum++) {
+ final PackageParser.Package pkg = mPackages.valueAt(packageNum);
+
+ if (pkg.applicationInfo.targetSdkVersion >= splitPerm.targetSdk
+ || !pkg.requestedPermissions.contains(rootPerm)) {
+ continue;
+ }
+
+ final int userId = UserHandle.getUserId(pkg.applicationInfo.uid);
+ final String pkgName = pkg.packageName;
+
+ if (checkPermission(rootPerm, pkgName, userId) == PERMISSION_DENIED) {
+ continue;
+ }
+
+ final String[] newPerms = splitPerm.newPerms;
+
+ final int numNewPerms = newPerms.length;
+ for (int newPermNum = 0; newPermNum < numNewPerms; newPermNum++) {
+ final String newPerm = newPerms[newPermNum];
+ if (checkPermission(newPerm, pkgName, userId) == PERMISSION_GRANTED) {
+ continue;
+ }
+
+ if (DEBUG_PERMISSIONS) {
+ Slog.v(TAG, "Granting " + newPerm + " to " + pkgName
+ + " as the root permission " + rootPerm
+ + " is already granted");
+ }
+
+ mPermissionManager.grantRuntimePermission(newPerm, pkgName, true,
+ callingUid, userId, null);
+ }
+ }
+ }
+ }
+
// clear only after permissions and other defaults have been updated
mExistingSystemPackages.clear();
mPromoteSystemApps = false;
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 1ae59cbea452..50e6f8d5b905 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -1193,12 +1193,27 @@ public final class DefaultPermissionGrantPolicy {
}
}
- private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
- boolean systemFixed, boolean ignoreSystemPackage, int userId) {
+ private void grantRuntimePermissions(PackageParser.Package pkg,
+ Set<String> permissionsWithoutSplits, boolean systemFixed, boolean ignoreSystemPackage,
+ int userId) {
if (pkg.requestedPermissions.isEmpty()) {
return;
}
+ final ArraySet<String> permissions = new ArraySet<>(permissionsWithoutSplits);
+
+ // Automatically attempt to grant split permissions to older APKs
+ final int numSplitPerms = PackageParser.SPLIT_PERMISSIONS.length;
+ for (int splitPermNum = 0; splitPermNum < numSplitPerms; splitPermNum++) {
+ final PackageParser.SplitPermissionInfo splitPerm =
+ PackageParser.SPLIT_PERMISSIONS[splitPermNum];
+
+ if (pkg.applicationInfo.targetSdkVersion < splitPerm.targetSdk
+ && permissionsWithoutSplits.contains(splitPerm.rootPerm)) {
+ Collections.addAll(permissions, splitPerm.newPerms);
+ }
+ }
+
List<String> requestedPermissions = pkg.requestedPermissions;
Set<String> grantablePermissions = null;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5bc35e7296c3..e6195b47a586 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -290,6 +290,7 @@ import com.android.server.wm.AppTransition;
import com.android.server.wm.DisplayFrames;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+import com.android.server.wm.utils.InsetUtils;
import java.io.File;
import java.io.FileReader;
@@ -4536,16 +4537,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
// TODO: Should probably be moved into DisplayFrames.
- public boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets, Rect outStableInsets,
+ public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds,
+ DisplayFrames displayFrames, boolean floatingStack, Rect outFrame,
+ Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) {
final int fl = PolicyControl.getWindowFlags(null, attrs);
final int pfl = attrs.privateFlags;
final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);
final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs);
final int displayRotation = displayFrames.mRotation;
- final int displayWidth = displayFrames.mDisplayWidth;
- final int displayHeight = displayFrames.mDisplayHeight;
final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
if (useOutsets) {
@@ -4569,45 +4569,40 @@ public class PhoneWindowManager implements WindowManagerPolicy {
final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
if (layoutInScreenAndInsetDecor && !screenDecor) {
- int availRight, availBottom;
if (canHideNavigationBar() &&
(sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
outFrame.set(displayFrames.mUnrestricted);
- availRight = displayFrames.mUnrestricted.right;
- availBottom = displayFrames.mUnrestricted.bottom;
} else {
outFrame.set(displayFrames.mRestricted);
- availRight = displayFrames.mRestricted.right;
- availBottom = displayFrames.mRestricted.bottom;
}
- outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top,
- availRight - displayFrames.mStable.right,
- availBottom - displayFrames.mStable.bottom);
- if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ final Rect sf;
+ if (floatingStack) {
+ sf = null;
+ } else {
+ sf = displayFrames.mStable;
+ }
+
+ final Rect cf;
+ if (floatingStack) {
+ cf = null;
+ } else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
if ((fl & FLAG_FULLSCREEN) != 0) {
- outContentInsets.set(displayFrames.mStableFullscreen.left,
- displayFrames.mStableFullscreen.top,
- availRight - displayFrames.mStableFullscreen.right,
- availBottom - displayFrames.mStableFullscreen.bottom);
+ cf = displayFrames.mStableFullscreen;
} else {
- outContentInsets.set(outStableInsets);
+ cf = displayFrames.mStable;
}
} else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
- outContentInsets.setEmpty();
+ cf = displayFrames.mOverscan;
} else {
- outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top,
- availRight - displayFrames.mCurrent.right,
- availBottom - displayFrames.mCurrent.bottom);
+ cf = displayFrames.mCurrent;
}
if (taskBounds != null) {
- calculateRelevantTaskInsets(taskBounds, outContentInsets,
- displayWidth, displayHeight);
- calculateRelevantTaskInsets(taskBounds, outStableInsets,
- displayWidth, displayHeight);
outFrame.intersect(taskBounds);
}
+ InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets);
+ InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets);
outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)
.getDisplayCutout());
return mForceShowSystemBars;
@@ -4628,22 +4623,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- /**
- * For any given task bounds, the insets relevant for these bounds given the insets relevant
- * for the entire display.
- */
- private void calculateRelevantTaskInsets(Rect taskBounds, Rect inOutInsets, int displayWidth,
- int displayHeight) {
- mTmpRect.set(0, 0, displayWidth, displayHeight);
- mTmpRect.inset(inOutInsets);
- mTmpRect.intersect(taskBounds);
- int leftInset = mTmpRect.left - taskBounds.left;
- int topInset = mTmpRect.top - taskBounds.top;
- int rightInset = taskBounds.right - mTmpRect.right;
- int bottomInset = taskBounds.bottom - mTmpRect.bottom;
- inOutInsets.set(leftInset, topInset, rightInset, bottomInset);
- }
-
private boolean shouldUseOutsets(WindowManager.LayoutParams attrs, int fl) {
return attrs.type == TYPE_WALLPAPER || (fl & (WindowManager.LayoutParams.FLAG_FULLSCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN)) != 0;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 9fbe4194049b..1ebbe3ac36fa 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -71,7 +71,6 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
-import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -1180,6 +1179,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @param taskBounds The bounds of the task this window is on or {@code null} if no task is
* associated with the window.
* @param displayFrames display frames.
+ * @param floatingStack Whether the window's stack is floating.
* @param outFrame The frame of the window.
* @param outContentInsets The areas covered by system windows, expressed as positive insets.
* @param outStableInsets The areas covered by stable system windows irrespective of their
@@ -1190,8 +1190,8 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* See {@link #isNavBarForcedShownLw(WindowState)}.
*/
default boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
- DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets,
- Rect outStableInsets, Rect outOutsets,
+ DisplayFrames displayFrames, boolean floatingStack,
+ Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout) {
return false;
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 4d6544034cef..c7c24a562696 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -57,11 +57,14 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.ModemActivityInfo;
import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.StatsLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.net.NetworkStatsFactory;
+import com.android.internal.os.BinderCallsStats;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
import com.android.internal.os.KernelCpuSpeedReader;
import com.android.internal.os.KernelUidCpuTimeReader;
import com.android.internal.os.KernelUidCpuClusterTimeReader;
@@ -891,6 +894,26 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
}
}
+ private void pullBinderCallsStats(int tagId, List<StatsLogEventWrapper> pulledData) {
+ List<ExportedCallStat> callStats = BinderCallsStats.getInstance().getExportedCallStats();
+ long elapsedNanos = SystemClock.elapsedRealtimeNanos();
+ for (ExportedCallStat callStat : callStats) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(elapsedNanos, tagId, 11 /* fields */);
+ e.writeInt(callStat.uid);
+ e.writeString(callStat.className);
+ e.writeString(callStat.methodName);
+ e.writeLong(callStat.callCount);
+ e.writeLong(callStat.exceptionCount);
+ e.writeLong(callStat.latencyMicros);
+ e.writeLong(callStat.maxLatencyMicros);
+ e.writeLong(callStat.cpuTimeMicros);
+ e.writeLong(callStat.maxCpuTimeMicros);
+ e.writeLong(callStat.maxReplySizeBytes);
+ e.writeLong(callStat.maxRequestSizeBytes);
+ pulledData.add(e);
+ }
+ }
+
/**
* Pulls various data.
*/
@@ -973,6 +996,10 @@ public class StatsCompanionService extends IStatsCompanionService.Stub {
pullProcessMemoryState(tagId, ret);
break;
}
+ case StatsLog.BINDER_CALLS: {
+ pullBinderCallsStats(tagId, ret);
+ break;
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
index 2bfff269e457..cb504607420c 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@@ -26,6 +26,8 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.PowerManagerInternal;
import android.util.ArrayMap;
import android.view.Choreographer;
import android.view.SurfaceControl;
@@ -57,6 +59,7 @@ class SurfaceAnimationRunner {
private final AnimationHandler mAnimationHandler;
private final Transaction mFrameTransaction;
private final AnimatorFactory mAnimatorFactory;
+ private final PowerManagerInternal mPowerManagerInternal;
private boolean mApplyScheduled;
@GuardedBy("mLock")
@@ -70,13 +73,15 @@ class SurfaceAnimationRunner {
@GuardedBy("mLock")
private boolean mAnimationStartDeferred;
- SurfaceAnimationRunner() {
- this(null /* callbackProvider */, null /* animatorFactory */, new Transaction());
+ SurfaceAnimationRunner(PowerManagerInternal powerManagerInternal) {
+ this(null /* callbackProvider */, null /* animatorFactory */, new Transaction(),
+ powerManagerInternal);
}
@VisibleForTesting
SurfaceAnimationRunner(@Nullable AnimationFrameCallbackProvider callbackProvider,
- AnimatorFactory animatorFactory, Transaction frameTransaction) {
+ AnimatorFactory animatorFactory, Transaction frameTransaction,
+ PowerManagerInternal powerManagerInternal) {
SurfaceAnimationThread.getHandler().runWithScissors(() -> mChoreographer = getSfInstance(),
0 /* timeout */);
mFrameTransaction = frameTransaction;
@@ -87,6 +92,7 @@ class SurfaceAnimationRunner {
mAnimatorFactory = animatorFactory != null
? animatorFactory
: SfValueAnimator::new;
+ mPowerManagerInternal = powerManagerInternal;
}
/**
@@ -231,6 +237,7 @@ class SurfaceAnimationRunner {
synchronized (mLock) {
startPendingAnimationsLocked();
}
+ mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
}
private void scheduleApplyTransaction() {
diff --git a/services/core/java/com/android/server/wm/TEST_MAPPING b/services/core/java/com/android/server/wm/TEST_MAPPING
new file mode 100644
index 000000000000..e885afa8031d
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TEST_MAPPING
@@ -0,0 +1,42 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsWindowManagerDeviceTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.wm."
+ },
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ },
+ {
+ "exclude-annotation": "android.support.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsWindowManagerDeviceTestCases"
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.wm."
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 54703b3c3c37..732a82829684 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1069,7 +1069,7 @@ public class WindowManagerService extends IWindowManager.Stub
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
mHoldingScreenWakeLock.setReferenceCounted(false);
- mSurfaceAnimationRunner = new SurfaceAnimationRunner();
+ mSurfaceAnimationRunner = new SurfaceAnimationRunner(mPowerManagerInternal);
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
@@ -1469,14 +1469,18 @@ public class WindowManagerService extends IWindowManager.Stub
displayFrames.onDisplayInfoUpdated(displayInfo,
displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation));
final Rect taskBounds;
+ final boolean floatingStack;
if (atoken != null && atoken.getTask() != null) {
taskBounds = mTmpRect;
atoken.getTask().getBounds(mTmpRect);
+ floatingStack = atoken.getTask().isFloating();
} else {
taskBounds = null;
+ floatingStack = false;
}
- if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, outFrame,
- outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
+ if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
+ outFrame, outContentInsets, outStableInsets, outOutsets,
+ outDisplayCutout)) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
}
diff --git a/services/core/java/com/android/server/wm/utils/InsetUtils.java b/services/core/java/com/android/server/wm/utils/InsetUtils.java
index b4a998add374..c5b103fdb40b 100644
--- a/services/core/java/com/android/server/wm/utils/InsetUtils.java
+++ b/services/core/java/com/android/server/wm/utils/InsetUtils.java
@@ -16,8 +16,11 @@
package com.android.server.wm.utils;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
+
/**
* Utility methods to handle insets represented as rects.
*/
@@ -35,4 +38,30 @@ public class InsetUtils {
inOutInsets.right += insetsToAdd.right;
inOutInsets.bottom += insetsToAdd.bottom;
}
+
+ /**
+ * Calculates the insets from the {@code outerFrame} to the {@code innerFrame}.
+ *
+ * Note that if a side of the outer frame is not actually on the outside, the inset for that
+ * side will be clamped to zero.
+ *
+ * @param outerFrame the reference frame from which the insets are calculated
+ * @param innerFrame the inset frame, to which the insets are calculated,
+ * or null to clear the insets.
+ * @param outInsets is set to the result of the inset calculation.
+ */
+ public static void insetsBetweenFrames(@NonNull Rect outerFrame, @Nullable Rect innerFrame,
+ @NonNull Rect outInsets) {
+ if (innerFrame == null) {
+ outInsets.setEmpty();
+ return;
+ }
+ final int w = outerFrame.width();
+ final int h = outerFrame.height();
+ outInsets.set(
+ Math.min(w, Math.max(0, innerFrame.left - outerFrame.left)),
+ Math.min(h, Math.max(0, innerFrame.top - outerFrame.top)),
+ Math.min(w, Math.max(0, outerFrame.right - innerFrame.right)),
+ Math.min(h, Math.max(0, outerFrame.bottom - innerFrame.bottom)));
+ }
}
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 4fbc14c0097f..e8266a574bf5 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -243,7 +243,7 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks,
intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null));
intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder());
intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob);
- intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
IntentSender intentSender = PendingIntent.getActivityAsUser(
mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
new file mode 100644
index 000000000000..865050fd6082
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
+import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertEquals;
+
+import android.media.AudioManager;
+import android.support.test.filters.SmallTest;
+import junit.framework.Assert;
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+/**
+ * Tests for {@link HdmiCecLocalDeviceAudioSystem} class.
+ */
+public class HdmiCecLocalDeviceAudioSystemTest {
+
+
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
+ private HdmiCecMessage mResultMessage;
+ private int mMusicVolume;
+ private int mMusicMaxVolume;
+ private boolean mMusicMute;
+
+ @Before
+ public void SetUp() {
+ mHdmiControlService = new HdmiControlService(null) {
+ @Override
+ AudioManager getAudioManager() {
+ return new AudioManager() {
+ @Override
+ public int getStreamVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicVolume;
+ default:
+ return 0;
+ }
+ }
+
+ @Override
+ public boolean isStreamMute(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMute;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public int getStreamMaxVolume(int streamType) {
+ switch (streamType) {
+ case STREAM_MUSIC:
+ return mMusicMaxVolume;
+ default:
+ return 100;
+ }
+ }
+ };
+ }
+
+ @Override
+ void sendCecCommand(HdmiCecMessage command) {
+ mResultMessage = command;
+ }
+ };
+ mHdmiCecLocalDeviceAudioSystem = new HdmiCecLocalDeviceAudioSystem(mHdmiControlService);
+ }
+
+ @Test
+ public void handleGiveAudioStatus_volume_10_mute_true() {
+ mMusicVolume = 10;
+ mMusicMute = true;
+ mMusicMaxVolume = 20;
+ int scaledVolume = VolumeControlAction.scaleToCecVolume(10, mMusicMaxVolume);
+ HdmiCecMessage expectMessage = HdmiCecMessageBuilder.buildReportAudioStatus(
+ ADDR_UNREGISTERED, ADDR_TV, scaledVolume, true);
+
+ HdmiCecMessage message = HdmiCecMessageBuilder.buildGiveAudioStatus(
+ ADDR_TV, ADDR_AUDIO_SYSTEM);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.handleGiveAudioStatus(message));
+
+ assertTrue(mResultMessage.equals(expectMessage));
+ }
+
+ @Test
+ public void handleGiveSystemAudioModeStatus_off() {
+ HdmiCecMessage expectMessage = HdmiCecMessageBuilder
+ .buildReportSystemAudioMode(ADDR_UNREGISTERED, ADDR_TV, false);
+
+ HdmiCecMessage message = HdmiCecMessageBuilder
+ .buildGiveSystemAudioModeStatus(ADDR_TV, ADDR_AUDIO_SYSTEM);
+ assertTrue(mHdmiCecLocalDeviceAudioSystem.handleGiveSystemAudioModeStatus(message));
+
+ assertTrue(mResultMessage.equals(expectMessage));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
index 97a716f6bd99..cb94ec7caaca 100644
--- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java
@@ -22,7 +22,6 @@ import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -309,8 +308,8 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase {
final Rect stable = new Rect();
final Rect outsets = new Rect();
final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper();
- mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content,
- stable, outsets, cutout);
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames,
+ false /* floatingStack */, frame, content, stable, outsets, cutout);
assertThat(frame, equalTo(mFrames.mUnrestricted));
assertThat(content, equalTo(new Rect()));
@@ -331,8 +330,8 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase {
final DisplayCutout.ParcelableWrapper outDisplayCutout =
new DisplayCutout.ParcelableWrapper();
- mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets,
- outStableInsets, outOutsets, outDisplayCutout);
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, false /* floatingStack */,
+ outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
assertThat(outFrame, is(mFrames.mUnrestricted));
assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT)));
@@ -355,8 +354,35 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase {
final DisplayCutout.ParcelableWrapper outDisplayCutout =
new DisplayCutout.ParcelableWrapper();
- mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets,
- outStableInsets, outOutsets, outDisplayCutout);
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, false /* floatingStack */,
+ outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
+
+ assertThat(outFrame, is(taskBounds));
+ assertThat(outContentInsets, is(new Rect()));
+ assertThat(outStableInsets, is(new Rect()));
+ assertThat(outOutsets, is(new Rect()));
+ assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper()));
+ }
+
+ @Test
+ public void layoutHint_appWindowInTask_outsideContentFrame() {
+ // Initialize DisplayFrames
+ mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
+
+ // Task is in the nav bar area (usually does not happen, but this is similar enough to the
+ // possible overlap with the IME)
+ final Rect taskBounds = new Rect(100, mFrames.mContent.bottom + 1,
+ 200, mFrames.mContent.bottom + 10);
+
+ final Rect outFrame = new Rect();
+ final Rect outContentInsets = new Rect();
+ final Rect outStableInsets = new Rect();
+ final Rect outOutsets = new Rect();
+ final DisplayCutout.ParcelableWrapper outDisplayCutout =
+ new DisplayCutout.ParcelableWrapper();
+
+ mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, true /* floatingStack */,
+ outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout);
assertThat(outFrame, is(taskBounds));
assertThat(outContentInsets, is(new Rect()));
diff --git a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index edac8a5202d7..79e9bb4c769d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -20,24 +20,23 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.animation.AnimationHandler;
import android.animation.AnimationHandler.AnimationFrameCallbackProvider;
import android.animation.ValueAnimator;
import android.graphics.Matrix;
import android.graphics.Point;
+import android.os.PowerManagerInternal;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.FlakyTest;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
-import android.util.Log;
import android.view.Choreographer;
import android.view.Choreographer.FrameCallback;
import android.view.SurfaceControl;
@@ -46,7 +45,6 @@ import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
-import com.android.server.wm.SurfaceAnimationRunner.AnimatorFactory;
import org.junit.Before;
import org.junit.Rule;
@@ -71,6 +69,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
@Mock SurfaceControl mMockSurface;
@Mock Transaction mMockTransaction;
@Mock AnimationSpec mMockAnimationSpec;
+ @Mock PowerManagerInternal mMockPowerManager;
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
private SurfaceAnimationRunner mSurfaceAnimationRunner;
@@ -81,7 +80,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
super.setUp();
mFinishCallbackLatch = new CountDownLatch(1);
mSurfaceAnimationRunner = new SurfaceAnimationRunner(null /* callbackProvider */, null,
- mMockTransaction);
+ mMockTransaction, mMockPowerManager);
}
private void finishedCallback() {
@@ -113,7 +112,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
@Test
public void testCancel_notStarted() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
- mMockTransaction);
+ mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner
.startAnimation(createTranslateAnimation(), mMockSurface, mMockTransaction,
this::finishedCallback);
@@ -126,7 +125,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
@Test
public void testCancel_running() throws Exception {
mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
- mMockTransaction);
+ mMockTransaction, mMockPowerManager);
mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
mMockTransaction, this::finishedCallback);
waitUntilNextFrame();
@@ -156,7 +155,7 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
listener.onAnimationUpdate(animation);
});
}
- }, mMockTransaction);
+ }, mMockTransaction, mMockPowerManager);
when(mMockAnimationSpec.getDuration()).thenReturn(200L);
mSurfaceAnimationRunner.startAnimation(mMockAnimationSpec, mMockSurface, mMockTransaction,
this::finishedCallback);
@@ -184,6 +183,19 @@ public class SurfaceAnimationRunnerTest extends WindowTestsBase {
assertFinishCallbackCalled();
}
+ @Test
+ public void testPowerHint() throws Exception {
+ mSurfaceAnimationRunner = new SurfaceAnimationRunner(new NoOpFrameCallbackProvider(), null,
+ mMockTransaction, mMockPowerManager);
+ mSurfaceAnimationRunner.startAnimation(createTranslateAnimation(), mMockSurface,
+ mMockTransaction, this::finishedCallback);
+ waitUntilNextFrame();
+
+ // TODO: For some reason we don't have access to PowerHint definition from the tests. For
+ // now let's just verify that we got some kind of hint.
+ verify(mMockPowerManager).powerHint(anyInt(), anyInt());
+ }
+
private void waitUntilNextFrame() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mSurfaceAnimationRunner.mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
index fd674f0c3858..f17a30ddb1b6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java
@@ -25,6 +25,9 @@ import static junit.framework.Assert.assertTrue;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.Adjustment;
@@ -54,6 +57,9 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
ArrayList<String> people = new ArrayList<>();
people.add("you");
signals.putStringArrayList(Adjustment.KEY_PEOPLE, people);
+ ArrayList<Notification.Action> smartActions = new ArrayList<>();
+ smartActions.add(createAction());
+ signals.putParcelableArrayList(Adjustment.KEY_SMART_ACTIONS, smartActions);
Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0);
r.addAdjustment(adjustment);
@@ -66,6 +72,7 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
assertEquals(people, r.getPeopleOverride());
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
+ assertEquals(smartActions, r.getSmartActions());
}
@Test
@@ -114,4 +121,11 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
0, n, UserHandle.ALL, null, System.currentTimeMillis());
return new NotificationRecord(getContext(), sbn, channel);
}
+
+ private Notification.Action createAction() {
+ return new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
+ "action",
+ PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index ef9ba78b8263..742ad65f159a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -31,11 +31,14 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
+import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcel;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationRankingUpdate;
@@ -91,6 +94,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
assertEquals(getShowBadge(i), ranking.canShowBadge());
assertEquals(getUserSentiment(i), ranking.getUserSentiment());
assertEquals(getHidden(i), ranking.isSuspended());
+ assertActionsEqual(getSmartActions(key, i), ranking.getSmartActions());
}
}
@@ -107,6 +111,7 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
int[] importance = new int[mKeys.length];
Bundle userSentiment = new Bundle();
Bundle mHidden = new Bundle();
+ Bundle smartActions = new Bundle();
for (int i = 0; i < mKeys.length; i++) {
String key = mKeys[i];
@@ -124,11 +129,13 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
showBadge.putBoolean(key, getShowBadge(i));
userSentiment.putInt(key, getUserSentiment(i));
mHidden.putBoolean(key, getHidden(i));
+ smartActions.putParcelableArrayList(key, getSmartActions(key, i));
}
NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
interceptedKeys.toArray(new String[0]), visibilityOverrides,
suppressedVisualEffects, importance, explanation, overrideGroupKeys,
- channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden);
+ channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden,
+ smartActions);
return update;
}
@@ -196,6 +203,29 @@ public class NotificationListenerServiceTest extends UiServiceTestCase {
return snooze;
}
+ private ArrayList<Notification.Action> getSmartActions(String key, int index) {
+ ArrayList<Notification.Action> actions = new ArrayList<>();
+ for (int i = 0; i < index; i++) {
+ PendingIntent intent = PendingIntent.getBroadcast(
+ getContext(),
+ index /*requestCode*/,
+ new Intent("ACTION_" + key),
+ 0 /*flags*/);
+ actions.add(new Notification.Action.Builder(null /*icon*/, key, intent).build());
+ }
+ return actions;
+ }
+
+ private void assertActionsEqual(
+ List<Notification.Action> expecteds, List<Notification.Action> actuals) {
+ assertEquals(expecteds.size(), actuals.size());
+ for (int i = 0; i < expecteds.size(); i++) {
+ Notification.Action expected = expecteds.get(i);
+ Notification.Action actual = actuals.get(i);
+ assertEquals(expected.title, actual.title);
+ }
+ }
+
public static class TestListenerService extends NotificationListenerService {
private final IBinder binder = new LocalBinder();
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 45a3c4183999..e7a8b581ecdb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -158,6 +158,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private TestableLooper mTestableLooper;
@Mock
private RankingHelper mRankingHelper;
+ @Mock private PreferencesHelper mPreferencesHelper;
AtomicFile mPolicyFile;
File mFile;
@Mock
@@ -600,8 +601,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testBlockedNotifications_blockedChannelGroup() throws Exception {
when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.isGroupBlocked(anyString(), anyInt(), anyString())).thenReturn(true);
NotificationChannel channel = new NotificationChannel("id", "name",
NotificationManager.IMPORTANCE_HIGH);
@@ -1222,36 +1223,36 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testTvExtenderChannelOverride_onTv() throws Exception {
mService.setIsTelevision(true);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mRankingHelper, times(1)).getNotificationChannel(
+ verify(mPreferencesHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean());
}
@Test
public void testTvExtenderChannelOverride_notOnTv() throws Exception {
mService.setIsTelevision(false);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
mTestNotificationChannel);
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mRankingHelper, times(1)).getNotificationChannel(
+ verify(mPreferencesHelper, times(1)).getNotificationChannel(
anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
}
@Test
public void testUpdateAppNotifyCreatorBlock() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
mBinderService.setNotificationsEnabledForPackage(PKG, 0, false);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -1265,7 +1266,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateAppNotifyCreatorUnblock() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
mBinderService.setNotificationsEnabledForPackage(PKG, 0, true);
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
@@ -1279,8 +1280,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateChannelNotifyCreatorBlock() throws Exception {
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
@@ -1305,8 +1306,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationChannel existingChannel =
new NotificationChannel(mTestNotificationChannel.getId(),
mTestNotificationChannel.getName(), IMPORTANCE_NONE);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(existingChannel);
@@ -1327,8 +1328,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationChannel existingChannel =
new NotificationChannel(mTestNotificationChannel.getId(),
mTestNotificationChannel.getName(), IMPORTANCE_MAX);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(existingChannel);
@@ -1339,8 +1340,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateGroupNotifyCreatorBlock() throws Exception {
NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
.thenReturn(existing);
NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
@@ -1362,8 +1363,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testUpdateGroupNotifyCreatorUnblock() throws Exception {
NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
existing.setBlocked(true);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
.thenReturn(existing);
mBinderService.updateNotificationChannelGroupForPackage(
@@ -1382,8 +1383,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
.thenReturn(existing);
mBinderService.updateNotificationChannelGroupForPackage(
@@ -1396,12 +1397,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
NotificationChannel channel2 = new NotificationChannel("a", "b", IMPORTANCE_LOW);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(channel2.getId()), anyBoolean()))
.thenReturn(channel2);
@@ -1421,7 +1422,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
NotificationChannelGroup group1 = new NotificationChannelGroup("a", "b");
NotificationChannelGroup group2 = new NotificationChannelGroup("n", "m");
@@ -1441,9 +1442,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
mTestNotificationChannel.setLightColor(Color.CYAN);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
@@ -1459,8 +1460,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
reset(mListeners);
@@ -1476,8 +1477,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
NotificationChannelGroup ncg = new NotificationChannelGroup("a", "b/c");
- mService.setRankingHelper(mRankingHelper);
- when(mRankingHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
+ mService.setPreferencesHelper(mPreferencesHelper);
+ when(mPreferencesHelper.getNotificationChannelGroup(eq(ncg.getId()), eq(PKG), anyInt()))
.thenReturn(ncg);
reset(mListeners);
mBinderService.deleteNotificationChannelGroup(PKG, ncg.getId());
@@ -1488,18 +1489,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_success() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
- when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+ when(mPreferencesHelper.getNotificationChannel(eq(PKG), anyInt(),
eq(mTestNotificationChannel.getId()), anyBoolean()))
.thenReturn(mTestNotificationChannel);
mBinderService.updateNotificationChannelFromPrivilegedListener(
null, PKG, Process.myUserHandle(), mTestNotificationChannel);
- verify(mRankingHelper, times(1)).updateNotificationChannel(
+ verify(mPreferencesHelper, times(1)).updateNotificationChannel(
anyString(), anyInt(), any(), anyBoolean());
verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1509,7 +1510,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1521,7 +1522,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// pass
}
- verify(mRankingHelper, never()).updateNotificationChannel(
+ verify(mPreferencesHelper, never()).updateNotificationChannel(
anyString(), anyInt(), any(), anyBoolean());
verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1531,7 +1532,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testUpdateNotificationChannelFromPrivilegedListener_badUser() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1548,7 +1549,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// pass
}
- verify(mRankingHelper, never()).updateNotificationChannel(
+ verify(mPreferencesHelper, never()).updateNotificationChannel(
anyString(), anyInt(), any(), anyBoolean());
verify(mListeners, never()).notifyNotificationChannelChanged(eq(PKG),
@@ -1558,7 +1559,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testGetNotificationChannelFromPrivilegedListener_success() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1566,13 +1567,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.getNotificationChannelsFromPrivilegedListener(
null, PKG, Process.myUserHandle());
- verify(mRankingHelper, times(1)).getNotificationChannels(
+ verify(mPreferencesHelper, times(1)).getNotificationChannels(
anyString(), anyInt(), anyBoolean());
}
@Test
public void testGetNotificationChannelFromPrivilegedListener_noAccess() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1584,13 +1585,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// pass
}
- verify(mRankingHelper, never()).getNotificationChannels(
+ verify(mPreferencesHelper, never()).getNotificationChannels(
anyString(), anyInt(), anyBoolean());
}
@Test
public void testGetNotificationChannelFromPrivilegedListener_badUser() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1606,13 +1607,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// pass
}
- verify(mRankingHelper, never()).getNotificationChannels(
+ verify(mPreferencesHelper, never()).getNotificationChannels(
anyString(), anyInt(), anyBoolean());
}
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_success() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
associations.add("a");
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1620,12 +1621,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mBinderService.getNotificationChannelGroupsFromPrivilegedListener(
null, PKG, Process.myUserHandle());
- verify(mRankingHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt());
+ verify(mPreferencesHelper, times(1)).getNotificationChannelGroups(anyString(), anyInt());
}
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_noAccess() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
@@ -1637,12 +1638,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// pass
}
- verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
+ verify(mPreferencesHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
}
@Test
public void testGetNotificationChannelGroupsFromPrivilegedListener_badUser() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
List<String> associations = new ArrayList<>();
when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
mListener = mock(ManagedServices.ManagedServiceInfo.class);
@@ -1657,7 +1658,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// pass
}
- verify(mRankingHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
+ verify(mPreferencesHelper, never()).getNotificationChannelGroups(anyString(), anyInt());
}
@Test
@@ -2182,7 +2183,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Test
public void testHandleRankingSort_sendsUpdateOnSignalExtractorChange() throws Exception {
- mService.setRankingHelper(mRankingHelper);
+ mService.setPreferencesHelper(mPreferencesHelper);
NotificationManagerService.WorkerHandler handler = mock(
NotificationManagerService.WorkerHandler.class);
mService.setHandler(handler);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
index e28699113a3d..bd6416dd5d4d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java
@@ -34,8 +34,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -48,6 +46,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
+import android.graphics.drawable.Icon;
import android.media.AudioAttributes;
import android.metrics.LogMaker;
import android.net.Uri;
@@ -59,8 +58,8 @@ import android.service.notification.Adjustment;
import android.service.notification.StatusBarNotification;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.server.UiServiceTestCase;
@@ -70,6 +69,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.Objects;
@SmallTest
@@ -697,4 +697,20 @@ public class NotificationRecordTest extends UiServiceTestCase {
record.calculateGrantableUris();
// should not throw
}
+
+ @Test
+ public void testSmartActions() {
+ StatusBarNotification sbn = getNotification(PKG_O, true /* noisy */,
+ true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
+ false /* lights */, false /* defaultLights */, groupId /* group */);
+ NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
+ assertNull(record.getSmartActions());
+
+ ArrayList<Notification.Action> smartActions = new ArrayList<>();
+ smartActions.add(new Notification.Action.Builder(
+ Icon.createWithResource(getContext(), R.drawable.btn_default),
+ "text", null).build());
+ record.setSmartActions(smartActions);
+ assertEquals(smartActions, record.getSmartActions());
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
new file mode 100644
index 000000000000..02d5869b46d0
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -0,0 +1,1742 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+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;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.media.AudioAttributes;
+import android.net.Uri;
+import android.os.Build;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.TestableContentResolver;
+import android.util.ArrayMap;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+import com.android.server.UiServiceTestCase;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ThreadLocalRandom;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferencesHelperTest extends UiServiceTestCase {
+ private static final String PKG = "com.android.server.notification";
+ private static final int UID = 0;
+ private static final UserHandle USER = UserHandle.of(0);
+ private static final String UPDATED_PKG = "updatedPkg";
+ private static final int UID2 = 1111;
+ private static final String SYSTEM_PKG = "android";
+ private static final int SYSTEM_UID= 1000;
+ private static final UserHandle USER2 = UserHandle.of(10);
+ private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private static final String TEST_AUTHORITY = "test";
+ private static final Uri SOUND_URI =
+ Uri.parse("content://" + TEST_AUTHORITY + "/internal/audio/media/10");
+ private static final Uri CANONICAL_SOUND_URI =
+ Uri.parse("content://" + TEST_AUTHORITY
+ + "/internal/audio/media/10?title=Test&canonical=1");
+
+ @Mock NotificationUsageStats mUsageStats;
+ @Mock RankingHandler mHandler;
+ @Mock PackageManager mPm;
+ @Mock IContentProvider mTestIContentProvider;
+ @Mock Context mContext;
+ @Mock ZenModeHelper mMockZenModeHelper;
+
+ private NotificationManager.Policy mTestNotificationPolicy;
+
+ private PreferencesHelper mHelper;
+ private AudioAttributes mAudioAttributes;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ UserHandle user = UserHandle.ALL;
+
+ final ApplicationInfo legacy = new ApplicationInfo();
+ legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ final ApplicationInfo upgrade = new ApplicationInfo();
+ upgrade.targetSdkVersion = Build.VERSION_CODES.O;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getApplicationInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(upgrade);
+ when(mPm.getPackageUidAsUser(eq(PKG), anyInt())).thenReturn(UID);
+ when(mPm.getPackageUidAsUser(eq(UPDATED_PKG), anyInt())).thenReturn(UID2);
+ when(mPm.getPackageUidAsUser(eq(SYSTEM_PKG), anyInt())).thenReturn(SYSTEM_UID);
+ PackageInfo info = mock(PackageInfo.class);
+ info.signatures = new Signature[] {mock(Signature.class)};
+ when(mPm.getPackageInfoAsUser(eq(SYSTEM_PKG), anyInt(), anyInt())).thenReturn(info);
+ when(mPm.getPackageInfoAsUser(eq(PKG), anyInt(), anyInt()))
+ .thenReturn(mock(PackageInfo.class));
+ when(mContext.getResources()).thenReturn(
+ InstrumentationRegistry.getContext().getResources());
+ when(mContext.getContentResolver()).thenReturn(
+ InstrumentationRegistry.getContext().getContentResolver());
+ when(mContext.getPackageManager()).thenReturn(mPm);
+ when(mContext.getApplicationInfo()).thenReturn(legacy);
+ // most tests assume badging is enabled
+ TestableContentResolver contentResolver = getContext().getContentResolver();
+ contentResolver.setFallbackToExisting(false);
+ Secure.putIntForUser(contentResolver,
+ Secure.NOTIFICATION_BADGING, 1, UserHandle.getUserId(UID));
+
+ ContentProvider testContentProvider = mock(ContentProvider.class);
+ when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
+ contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
+
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI)))
+ .thenReturn(CANONICAL_SOUND_URI);
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(CANONICAL_SOUND_URI);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(SOUND_URI);
+
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ resetZenModeHelper();
+
+ mAudioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+ .build();
+ }
+
+ private NotificationChannel getDefaultChannel() {
+ return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
+ IMPORTANCE_LOW);
+ }
+
+ private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
+ String... channelIds)
+ throws Exception {
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ mHelper.writeXml(serializer, forBackup);
+ serializer.endDocument();
+ serializer.flush();
+ for (String channelId : channelIds) {
+ mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
+ }
+ return baos;
+ }
+
+ private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
+ loadByteArrayXml(stream.toByteArray(), forRestore);
+ }
+
+ private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+ parser.nextTag();
+ mHelper.readXml(parser, forRestore);
+ }
+
+ private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
+ assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
+ assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
+ assertEquals(expected.getImportance(), actual.getImportance());
+ assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
+ assertEquals(expected.getSound(), actual.getSound());
+ assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
+ assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(expected.getGroup(), actual.getGroup());
+ assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
+ assertEquals(expected.getLightColor(), actual.getLightColor());
+ }
+
+ private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getName(), actual.getName());
+ assertEquals(expected.getDescription(), actual.getDescription());
+ assertEquals(expected.isBlocked(), actual.isBlocked());
+ }
+
+ private NotificationChannel getChannel() {
+ return new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ }
+
+ private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
+ for (NotificationChannel channel : channels) {
+ if (channel.getId().equals(id)) {
+ return channel;
+ }
+ }
+ return null;
+ }
+
+ private void resetZenModeHelper() {
+ reset(mMockZenModeHelper);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ }
+
+ @Test
+ public void testChannelXml() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ ncg.setBlocked(true);
+ ncg.setDescription("group desc");
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setDescription("descriptions for all");
+ channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel2.enableLights(true);
+ channel2.setBypassDnd(true);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel2.enableVibration(true);
+ channel2.setGroup(ncg.getId());
+ channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
+ channel2.setLightColor(Color.BLUE);
+
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+
+ mHelper.setShowBadge(PKG, UID, true);
+ mHelper.setAppImportanceLocked(PKG, UID);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
+ channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
+
+ loadStreamXml(baos, false);
+
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ compareChannels(channel2,
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+
+ List<NotificationChannelGroup> actualGroups =
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
+ boolean foundNcg = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (ncg.getId().equals(actual.getId())) {
+ foundNcg = true;
+ compareGroups(ncg, actual);
+ } else if (ncg2.getId().equals(actual.getId())) {
+ compareGroups(ncg2, actual);
+ }
+ }
+ assertTrue(foundNcg);
+
+ boolean foundChannel2Group = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
+ foundChannel2Group = true;
+ break;
+ }
+ }
+ assertTrue(foundChannel2Group);
+ }
+
+ @Test
+ public void testChannelXmlForBackup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+ NotificationChannel channel1 =
+ 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);
+ channel2.setBypassDnd(true);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel2.enableVibration(false);
+ channel2.setGroup(ncg.getId());
+ channel2.setLightColor(Color.BLUE);
+ NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
+ channel3.enableVibration(true);
+
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, false, false);
+ mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
+
+ mHelper.setShowBadge(PKG, UID, true);
+
+ mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
+ channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG},
+ new int[]{UID, UID2});
+
+ mHelper.setShowBadge(UPDATED_PKG, UID2, true);
+
+ loadStreamXml(baos, true);
+
+ assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ compareChannels(channel2,
+ mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+ compareChannels(channel3,
+ mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+
+ List<NotificationChannelGroup> actualGroups =
+ mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
+ boolean foundNcg = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (ncg.getId().equals(actual.getId())) {
+ foundNcg = true;
+ compareGroups(ncg, actual);
+ } else if (ncg2.getId().equals(actual.getId())) {
+ compareGroups(ncg2, actual);
+ }
+ }
+ assertTrue(foundNcg);
+
+ boolean foundChannel2Group = false;
+ for (NotificationChannelGroup actual : actualGroups) {
+ if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
+ foundChannel2Group = true;
+ break;
+ }
+ }
+ assertTrue(foundChannel2Group);
+ }
+
+ @Test
+ public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ // Testing that in restore we are given the canonical version
+ loadStreamXml(baos, true);
+ verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
+ }
+
+ @Test
+ public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
+ Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
+ Uri canonicalBasedOnLocal = localUri.buildUpon()
+ .appendQueryParameter("title", "Test")
+ .appendQueryParameter("canonical", "1")
+ .build();
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(canonicalBasedOnLocal);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(localUri);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
+ .thenReturn(localUri);
+
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(localUri, actualChannel.getSound());
+ }
+
+ @Test
+ public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
+ Thread.sleep(3000);
+ when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(null);
+ when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
+ .thenReturn(null);
+
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(SOUND_URI, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+ }
+
+
+ /**
+ * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
+ * handle its restore properly.
+ */
+ @Test
+ public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
+ // Not a local uncanonicalized uri, simulating that it fails to exist locally
+ when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
+ String id = "id";
+ String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
+ + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
+ + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
+ + "sound=\"" + SOUND_URI + "\" "
+ + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
+ + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
+ + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
+ + "</package>\n"
+ + "</ranking>\n";
+
+ loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
+ assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
+ }
+
+ @Test
+ public void testBackupRestoreXml_withNullSoundUri() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id", "name", IMPORTANCE_LOW);
+ channel.setSound(null, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
+
+ loadStreamXml(baos, true);
+
+ NotificationChannel actualChannel = mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false);
+ assertEquals(null, actualChannel.getSound());
+ }
+
+ @Test
+ public void testChannelXml_backup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel3 =
+ new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
+ channel3.setGroup(ncg.getId());
+
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
+ mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
+ channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
+ mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, true);
+
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
+ assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
+ assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
+ //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
+ }
+
+ @Test
+ public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ loadStreamXml(baos, false);
+
+ final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
+ assertFalse(updated.canBypassDnd());
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
+ assertEquals(0, updated.getUserLockedFields());
+ }
+
+ @Test
+ public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
+
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID);
+
+ loadStreamXml(baos, false);
+
+ assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
+ }
+
+ @Test
+ public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
+ final String preupgradeXml = "<ranking version=\"1\">\n"
+ + "<package name=\"" + PKG
+ + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
+ + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
+ + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
+ + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
+ + Notification.VISIBILITY_PRIVATE + "\" />\n"
+ + "</ranking>";
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
+ null);
+ parser.nextTag();
+ mHelper.readXml(parser, false);
+
+ final NotificationChannel updated1 =
+ mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
+ assertTrue(updated1.canBypassDnd());
+ assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
+ assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
+ | NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ updated1.getUserLockedFields());
+
+ // No Default Channel created for updated packages
+ assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @Test
+ public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertTrue(defaultChannel != null);
+ ByteArrayOutputStream baos =
+ writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos, false);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @Test
+ public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+
+ // Load package at higher sdk.
+ final ApplicationInfo upgraded = new ApplicationInfo();
+ upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
+ loadStreamXml(baos, false);
+
+ // Default Channel should be gone.
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @Test
+ public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
+ ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
+ NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+
+ loadStreamXml(baos, false);
+
+ // Should still have the newly created channel that wasn't in the xml.
+ assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
+ }
+
+ @Test
+ public void testCreateChannel_blocked() throws Exception {
+ mHelper.setImportance(PKG, UID, IMPORTANCE_NONE);
+
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
+ }
+
+ @Test
+ public void testCreateChannel_badImportance() throws Exception {
+ try {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
+ true, false);
+ fail("Was allowed to create a channel with invalid importance");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ try {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
+ true, false);
+ fail("Was allowed to create a channel with invalid importance");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ try {
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
+ true, false);
+ fail("Was allowed to create a channel with invalid importance");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
+ mHelper.createNotificationChannel(PKG, UID,
+ new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
+ }
+
+
+ @Test
+ public void testUpdate() throws Exception {
+ // no fields locked by user
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, false, false);
+
+ // same id, try to update all fields
+ final NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
+ channel2.enableLights(false);
+ channel2.setBypassDnd(false);
+ channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
+
+ mHelper.updateNotificationChannel(PKG, UID, channel2, true);
+
+ // all fields should be changed
+ assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
+ public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
+ mHelper.setImportance(PKG, UID, IMPORTANCE_UNSPECIFIED);
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+ mHelper.getPackageVisibility(PKG, UID));
+ assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
+
+ NotificationChannel defaultChannel = mHelper.getNotificationChannel(
+ PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
+
+ defaultChannel.setShowBadge(false);
+ defaultChannel.setImportance(IMPORTANCE_NONE);
+ defaultChannel.setBypassDnd(true);
+ defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.setAppImportanceLocked(PKG, UID);
+ mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
+
+ // ensure app level fields are changed
+ assertFalse(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
+ assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
+ assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
+ }
+
+ @Test
+ public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
+ final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, false, false);
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+ mHelper.getPackageVisibility(PKG, UID));
+
+ channel.setShowBadge(false);
+ channel.setImportance(IMPORTANCE_NONE);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+
+ // ensure app level fields are not changed
+ assertTrue(mHelper.canShowBadge(PKG, UID));
+ assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
+ assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
+ mHelper.getPackageVisibility(PKG, UID));
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ }
+
+ @Test
+ public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
+ assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
+ }
+
+ @Test
+ public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(true);
+ int lockMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(lockMask);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ NotificationChannel savedChannel =
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+
+ assertEquals(channel.getName(), savedChannel.getName());
+ assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+ assertFalse(savedChannel.canBypassDnd());
+ assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
+
+ verify(mHandler, never()).requestSort();
+ }
+
+ @Test
+ public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
+ final NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.setShowBadge(true);
+ int lockMask = 0;
+ for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
+ lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
+ }
+ channel.lockFields(lockMask);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ NotificationChannel savedChannel =
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+
+ assertEquals(channel.getName(), savedChannel.getName());
+ assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
+ assertFalse(savedChannel.canBypassDnd());
+ assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
+ assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
+ }
+
+ @Test
+ public void testClearLockedFields() throws Exception {
+ final NotificationChannel channel = getChannel();
+ mHelper.clearLockedFields(channel);
+ assertEquals(0, channel.getUserLockedFields());
+
+ channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_IMPORTANCE);
+ mHelper.clearLockedFields(channel);
+ assertEquals(0, channel.getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_soundAndVibration() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setSound(new Uri.Builder().scheme("test").build(),
+ new AudioAttributes.Builder().build());
+ update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_SOUND,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ NotificationChannel update2 = getChannel();
+ update2.enableVibration(true);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_SOUND
+ | NotificationChannel.USER_LOCKED_VIBRATION,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_vibrationAndLights() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setVibrationPattern(new long[]{7945, 46 ,246});
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.enableLights(true);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
+ | NotificationChannel.USER_LOCKED_LIGHTS,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_lightsAndImportance() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+
+ final NotificationChannel update1 = getChannel();
+ update1.setLightColor(Color.GREEN);
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.setImportance(IMPORTANCE_DEFAULT);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
+ | NotificationChannel.USER_LOCKED_IMPORTANCE,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testLockFields_visibilityAndDndAndBadge() throws Exception {
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+ assertEquals(0,
+ mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update1 = getChannel();
+ update1.setBypassDnd(true);
+ mHelper.updateNotificationChannel(PKG, UID, update1, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
+ mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update2 = getChannel();
+ update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ mHelper.updateNotificationChannel(PKG, UID, update2, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY,
+ mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
+ .getUserLockedFields());
+
+ final NotificationChannel update3 = getChannel();
+ update3.setShowBadge(false);
+ mHelper.updateNotificationChannel(PKG, UID, update3, true);
+ assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
+ | NotificationChannel.USER_LOCKED_VISIBILITY
+ | NotificationChannel.USER_LOCKED_SHOW_BADGE,
+ mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
+ .getUserLockedFields());
+ }
+
+ @Test
+ public void testDeleteNonExistentChannel() throws Exception {
+ mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
+ }
+
+ @Test
+ public void testGetDeletedChannel() throws Exception {
+ NotificationChannel channel = getChannel();
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
+ channel.enableVibration(true);
+ channel.setVibrationPattern(new long[]{100, 67, 145, 156});
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+ // Does not return deleted channel
+ NotificationChannel response =
+ mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
+ assertNull(response);
+
+ // Returns deleted channel
+ response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
+ compareChannels(channel, response);
+ assertTrue(response.isDeleted());
+ }
+
+ @Test
+ public void testGetDeletedChannels() throws Exception {
+ Map<String, NotificationChannel> channelMap = new HashMap<>();
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
+ channel.enableLights(true);
+ channel.setBypassDnd(true);
+ channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
+ channel.enableVibration(true);
+ channel.setVibrationPattern(new long[]{100, 67, 145, 156});
+ channelMap.put(channel.getId(), channel);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+ channelMap.put(channel2.getId(), channel2);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+ // Returns only non-deleted channels
+ List<NotificationChannel> channels =
+ mHelper.getNotificationChannels(PKG, UID, false).getList();
+ assertEquals(2, channels.size()); // Default channel + non-deleted channel
+ for (NotificationChannel nc : channels) {
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ compareChannels(channel2, nc);
+ }
+ }
+
+ // Returns deleted channels too
+ channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
+ assertEquals(3, channels.size()); // Includes default channel
+ for (NotificationChannel nc : channels) {
+ if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
+ compareChannels(channelMap.get(nc.getId()), nc);
+ }
+ }
+ }
+
+ @Test
+ public void testGetDeletedChannelCount() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel3 =
+ new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
+
+ assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
+ }
+
+ @Test
+ public void testGetBlockedChannelCount() throws Exception {
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ NotificationChannel channel2 =
+ new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
+ NotificationChannel channel3 =
+ new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
+
+ assertEquals(1, mHelper.getBlockedChannelCount(PKG, UID));
+ assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID2));
+ }
+
+ @Test
+ public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // create notification channel that can bypass dnd
+ // expected result: areChannelsBypassingDnd = true
+ NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel2.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // delete channels
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+ assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testUpdateCanChannelsBypassDnd() throws Exception {
+ // create notification channel that can't bypass dnd
+ // expected result: areChannelsBypassingDnd = false
+ // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
+ NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // update channel so it CAN bypass dnd:
+ // expected result: areChannelsBypassingDnd = true
+ channel.setBypassDnd(true);
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+ assertTrue(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+
+ // update channel so it can't bypass dnd:
+ // expected result: areChannelsBypassingDnd = false
+ channel.setBypassDnd(false);
+ mHelper.updateNotificationChannel(PKG, UID, channel, true);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testSetupNewZenModeHelper_canBypass() {
+ // start notification policy off with mAreChannelsBypassingDnd = true, but
+ // RankingHelper should change to false
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
+ NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testSetupNewZenModeHelper_cannotBypass() {
+ // start notification policy off with mAreChannelsBypassingDnd = false
+ mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
+ when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
+ mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper);
+ assertFalse(mHelper.areChannelsBypassingDnd());
+ verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
+ resetZenModeHelper();
+ }
+
+ @Test
+ public void testCreateDeletedChannel() throws Exception {
+ long[] vibration = new long[]{100, 67, 145, 156};
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setVibrationPattern(vibration);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
+
+ NotificationChannel newChannel = new NotificationChannel(
+ channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+ newChannel.setVibrationPattern(new long[]{100});
+
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
+
+ // No long deleted, using old settings
+ compareChannels(channel,
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
+ }
+
+ @Test
+ public void testOnlyHasDefaultChannel() throws Exception {
+ assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID));
+ assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2));
+
+ mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
+ assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID));
+ }
+
+ @Test
+ public void testCreateChannel_defaultChannelId() throws Exception {
+ try {
+ mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
+ NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
+ fail("Allowed to create default channel");
+ } catch (IllegalArgumentException e) {
+ // pass
+ }
+ }
+
+ @Test
+ public void testCreateChannel_alreadyExists() throws Exception {
+ long[] vibration = new long[]{100, 67, 145, 156};
+ NotificationChannel channel =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+ channel.setVibrationPattern(vibration);
+
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+
+ NotificationChannel newChannel = new NotificationChannel(
+ channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+ newChannel.setVibrationPattern(new long[]{100});
+
+ mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
+
+ // Old settings not overridden
+ compareChannels(channel,
+ mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
+ }
+
+ @Test
+ public void testCreateChannel_noOverrideSound() throws Exception {
+ Uri sound = new Uri.Builder().scheme("test").build();
+ final NotificationChannel channel = new NotificationChannel("id2", "name2",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setSound(sound, mAudioAttributes);
+ mHelper.createNotificationChannel(PKG, UID, channel, true, false);
+ assertEquals(sound, mHelper.getNotificationChannel(
+ PKG, UID, channel.getId(), false).getSound());
+ }
+
+ @Test
+ public void testPermanentlyDeleteChannels() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
+
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
+
+ mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
+
+ // Only default channel remains
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
+ }
+
+ @Test
+ public void testDeleteGroup() throws Exception {
+ NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
+ NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
+ NotificationChannel nonGroupedNonDeletedChannel =
+ new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
+ NotificationChannel groupedButNotDeleted =
+ new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
+ groupedButNotDeleted.setGroup("not");
+ NotificationChannel groupedAndDeleted =
+ new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
+ groupedAndDeleted.setGroup("totally");
+
+ mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
+ mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true, false);
+ mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false);
+ mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false);
+
+ mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
+
+ assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
+ assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
+
+ assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
+ compareChannels(groupedAndDeleted,
+ mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
+
+ compareChannels(groupedButNotDeleted,
+ mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
+ compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
+ PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
+
+ // notDeleted
+ assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
+
+ verify(mHandler, never()).requestSort();
+ }
+
+ @Test
+ public void testOnUserRemoved() throws Exception {
+ int[] user0Uids = {98, 235, 16, 3782};
+ int[] user1Uids = new int[user0Uids.length];
+ for (int i = 0; i < user0Uids.length; i++) {
+ user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];
+
+ final ApplicationInfo legacy = new ApplicationInfo();
+ legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
+
+ // create records with the default channel for all user 0 and user 1 uids
+ mHelper.getImportance(PKG, user0Uids[i]);
+ mHelper.getImportance(PKG, user1Uids[i]);
+ }
+
+ mHelper.onUserRemoved(1);
+
+ // user 0 records remain
+ for (int i = 0; i < user0Uids.length; i++) {
+ assertEquals(1,
+ mHelper.getNotificationChannels(PKG, user0Uids[i], false).getList().size());
+ }
+ // user 1 records are gone
+ for (int i = 0; i < user1Uids.length; i++) {
+ assertEquals(0,
+ mHelper.getNotificationChannels(PKG, user1Uids[i], false).getList().size());
+ }
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval() throws Exception {
+ // Deleted
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+ assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
+
+ // Not deleted
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+ mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+ assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval_importance() throws Exception {
+ mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ }
+
+ @Test
+ public void testOnPackageChanged_packageRemoval_groups() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+
+ mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
+
+ assertEquals(0,
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
+ }
+
+ @Test
+ public void testOnPackageChange_downgradeTargetSdk() throws Exception {
+ // create channel as api 26
+ mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
+
+ // install new app version targeting 25
+ final ApplicationInfo legacy = new ApplicationInfo();
+ legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(legacy);
+ mHelper.onPackagesChanged(
+ false, UserHandle.USER_SYSTEM, new String[]{UPDATED_PKG}, new int[]{UID2});
+
+ // make sure the default channel was readded
+ //assertEquals(2, mHelper.getNotificationChannels(UPDATED_PKG, UID2, false).getList().size());
+ assertNotNull(mHelper.getNotificationChannel(
+ UPDATED_PKG, UID2, NotificationChannel.DEFAULT_CHANNEL_ID, false));
+ }
+
+ @Test
+ public void testRecordDefaults() throws Exception {
+ assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
+ assertEquals(true, mHelper.canShowBadge(PKG, UID));
+ assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
+ }
+
+ @Test
+ public void testCreateGroup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
+ verify(mHandler, never()).requestSort();
+ }
+
+ @Test
+ public void testCannotCreateChannel_badGroup() throws Exception {
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup("garbage");
+ try {
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ fail("Created a channel with a bad group");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ @Test
+ public void testCannotCreateChannel_goodGroup() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+
+ assertEquals(ncg.getId(),
+ mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
+ }
+
+ @Test
+ public void testGetChannelGroups() throws Exception {
+ NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
+ mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+ NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
+
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ NotificationChannel channel1a =
+ new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1a.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel1a, true, false);
+
+ NotificationChannel channel2 =
+ new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel2.setGroup(ncg2.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
+
+ NotificationChannel channel3 =
+ new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
+
+ List<NotificationChannelGroup> actual =
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+ assertEquals(3, actual.size());
+ for (NotificationChannelGroup group : actual) {
+ if (group.getId() == null) {
+ assertEquals(2, group.getChannels().size()); // misc channel too
+ assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
+ || channel3.getId().equals(group.getChannels().get(1).getId()));
+ } else if (group.getId().equals(ncg.getId())) {
+ assertEquals(2, group.getChannels().size());
+ if (group.getChannels().get(0).getId().equals(channel1.getId())) {
+ assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
+ } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
+ assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
+ } else {
+ fail("expected channel not found");
+ }
+ } else if (group.getId().equals(ncg2.getId())) {
+ assertEquals(1, group.getChannels().size());
+ assertEquals(channel2.getId(), group.getChannels().get(0).getId());
+ }
+ }
+ }
+
+ @Test
+ public void testGetChannelGroups_noSideEffects() throws Exception {
+ NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
+ mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
+
+ NotificationChannel channel1 =
+ new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
+ channel1.setGroup(ncg.getId());
+ mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+
+ channel1.setImportance(IMPORTANCE_LOW);
+ mHelper.updateNotificationChannel(PKG, UID, channel1, true);
+
+ List<NotificationChannelGroup> actual =
+ mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
+
+ assertEquals(2, actual.size());
+ for (NotificationChannelGroup group : actual) {
+ if (Objects.equals(group.getId(), ncg.getId())) {
+ assertEquals(1, group.getChannels().size());
+ }
+ }
+ }
+
+ @Test
+ public void testCreateChannel_updateName() throws Exception {
+ NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertEquals("hello", actual.getName());
+
+ nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertEquals("goodbye", actual.getName());
+ assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
+ public void testCreateChannel_addToGroup() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+ NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertNull(actual.getGroup());
+
+ nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
+ nc.setGroup(group.getId());
+ mHelper.createNotificationChannel(PKG, UID, nc, true, false);
+
+ actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
+ assertNotNull(actual.getGroup());
+ assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+
+ verify(mHandler, times(1)).requestSort();
+ }
+
+ @Test
+ public void testDumpChannelsJson() throws Exception {
+ final ApplicationInfo upgrade = new ApplicationInfo();
+ upgrade.targetSdkVersion = Build.VERSION_CODES.O;
+ try {
+ when(mPm.getApplicationInfoAsUser(
+ anyString(), anyInt(), anyInt())).thenReturn(upgrade);
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
+ int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
+ for (int i = 0; i < numPackages; i++) {
+ String pkgName = "pkg" + i;
+ int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
+ for (int j = 0; j < numChannels; j++) {
+ mHelper.createNotificationChannel(pkgName, UID,
+ new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
+ }
+ expectedChannels.put(pkgName, numChannels);
+ }
+
+ // delete the first channel of the first package
+ String pkg = expectedChannels.keyAt(0);
+ mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
+ // dump should not include deleted channels
+ int count = expectedChannels.get(pkg);
+ expectedChannels.put(pkg, count - 1);
+
+ JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
+ assertEquals(numPackages, actual.length());
+ for (int i = 0; i < numPackages; i++) {
+ JSONObject object = actual.getJSONObject(i);
+ assertTrue(expectedChannels.containsKey(object.get("packageName")));
+ assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
+ object.getInt("channelCount"));
+ }
+ }
+
+ @Test
+ public void testBadgingOverrideTrue() throws Exception {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 1,
+ USER.getIdentifier());
+ mHelper.updateBadgingEnabled(); // would be called by settings observer
+ assertTrue(mHelper.badgingEnabled(USER));
+ }
+
+ @Test
+ public void testBadgingOverrideFalse() throws Exception {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 0,
+ USER.getIdentifier());
+ mHelper.updateBadgingEnabled(); // would be called by settings observer
+ assertFalse(mHelper.badgingEnabled(USER));
+ }
+
+ @Test
+ public void testBadgingForUserAll() throws Exception {
+ try {
+ mHelper.badgingEnabled(UserHandle.ALL);
+ } catch (Exception e) {
+ fail("just don't throw");
+ }
+ }
+
+ @Test
+ public void testBadgingOverrideUserIsolation() throws Exception {
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 0,
+ USER.getIdentifier());
+ Secure.putIntForUser(getContext().getContentResolver(),
+ Secure.NOTIFICATION_BADGING, 1,
+ USER2.getIdentifier());
+ mHelper.updateBadgingEnabled(); // would be called by settings observer
+ assertFalse(mHelper.badgingEnabled(USER));
+ assertTrue(mHelper.badgingEnabled(USER2));
+ }
+
+ @Test
+ public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
+ String newLabel = "bananas!";
+ final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false);
+ assertFalse(newLabel.equals(defaultChannel.getName()));
+
+ Resources res = mock(Resources.class);
+ when(mContext.getResources()).thenReturn(res);
+ when(res.getString(com.android.internal.R.string.default_notification_channel_label))
+ .thenReturn(newLabel);
+
+ mHelper.onLocaleChanged(mContext, USER.getIdentifier());
+
+ assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
+ NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
+ }
+
+ @Test
+ public void testIsGroupBlocked_noGroup() throws Exception {
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
+
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
+ }
+
+ @Test
+ public void testIsGroupBlocked_notBlocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+
+ assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testIsGroupBlocked_blocked() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ group.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG, UID, group, false);
+
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testIsGroup_appCannotResetBlock() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ NotificationChannelGroup group2 = group.clone();
+ group2.setBlocked(true);
+ mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+
+ NotificationChannelGroup group3 = group.clone();
+ group3.setBlocked(false);
+ mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
+ assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
+ }
+
+ @Test
+ public void testGetNotificationChannelGroupWithChannels() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("group", "");
+ NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
+ mHelper.createNotificationChannelGroup(PKG, UID, group, true);
+ mHelper.createNotificationChannelGroup(PKG, UID, other, true);
+
+ NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
+ a.setGroup(group.getId());
+ NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
+ b.setGroup(other.getId());
+ NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
+ c.setGroup(group.getId());
+ NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
+
+ mHelper.createNotificationChannel(PKG, UID, a, true, false);
+ mHelper.createNotificationChannel(PKG, UID, b, true, false);
+ mHelper.createNotificationChannel(PKG, UID, c, true, false);
+ mHelper.createNotificationChannel(PKG, UID, d, true, false);
+ mHelper.deleteNotificationChannel(PKG, UID, c.getId());
+
+ NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
+ PKG, UID, group.getId(), true);
+ assertEquals(2, retrieved.getChannels().size());
+ compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+ compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
+
+ retrieved = mHelper.getNotificationChannelGroupWithChannels(
+ PKG, UID, group.getId(), false);
+ assertEquals(1, retrieved.getChannels().size());
+ compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
+ }
+
+ @Test
+ public void testAndroidPkgCannotBypassDnd_creation() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ test.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
+
+ assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
+ .canBypassDnd());
+ }
+
+ @Test
+ public void testDndPkgCanBypassDnd_creation() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ test.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(PKG, UID, test, true, true);
+
+ assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testNormalPkgCannotBypassDnd_creation() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ test.setBypassDnd(true);
+
+ mHelper.createNotificationChannel(PKG, 1000, test, true, false);
+
+ assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testAndroidPkgCannotBypassDnd_update() throws Exception {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
+
+ NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
+
+ assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
+ .canBypassDnd());
+ }
+
+ @Test
+ public void testDndPkgCanBypassDnd_update() throws Exception {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, UID, test, true, true);
+
+ NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, UID, update, true, true);
+
+ assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testNormalPkgCannotBypassDnd_update() {
+ NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ mHelper.createNotificationChannel(PKG, 1000, test, true, false);
+ NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
+ update.setBypassDnd(true);
+ mHelper.createNotificationChannel(PKG, 1000, update, true, false);
+ assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
+ }
+
+ @Test
+ public void testGetBlockedAppCount_noApps() {
+ assertEquals(0, mHelper.getBlockedAppCount(0));
+ }
+
+ @Test
+ public void testGetBlockedAppCount_noAppsForUserId() {
+ mHelper.setEnabled(PKG, 100, false);
+ assertEquals(0, mHelper.getBlockedAppCount(9));
+ }
+
+ @Test
+ public void testGetBlockedAppCount_appsForUserId() {
+ mHelper.setEnabled(PKG, 1020, false);
+ mHelper.setEnabled(PKG, 1030, false);
+ mHelper.setEnabled(PKG, 1060, false);
+ mHelper.setEnabled(PKG, 1000, true);
+ assertEquals(3, mHelper.getBlockedAppCount(0));
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 98c6ec42207f..7e0fcc908015 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -15,35 +15,17 @@
*/
package com.android.server.notification;
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MAX;
-import static android.app.NotificationManager.IMPORTANCE_NONE;
-import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.fail;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
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;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.ContentProvider;
import android.content.Context;
@@ -52,46 +34,26 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
-import android.content.res.Resources;
-import android.graphics.Color;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Build;
import android.os.UserHandle;
-import android.provider.Settings;
import android.provider.Settings.Secure;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableContentResolver;
-import android.util.ArrayMap;
-import android.util.Xml;
-import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
-import org.json.JSONArray;
-import org.json.JSONObject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ThreadLocalRandom;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -118,6 +80,7 @@ public class RankingHelperTest extends UiServiceTestCase {
@Mock IContentProvider mTestIContentProvider;
@Mock Context mContext;
@Mock ZenModeHelper mMockZenModeHelper;
+ @Mock RankingConfig mConfig;
private NotificationManager.Policy mTestNotificationPolicy;
private Notification mNotiGroupGSortA;
@@ -179,9 +142,8 @@ public class RankingHelperTest extends UiServiceTestCase {
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
+ mHelper = new RankingHelper(getContext(), mHandler, mConfig, mMockZenModeHelper,
mUsageStats, new String[] {ImportanceExtractor.class.getName()});
- resetZenModeHelper();
mNotiGroupGSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("A")
@@ -240,74 +202,6 @@ public class RankingHelperTest extends UiServiceTestCase {
IMPORTANCE_LOW);
}
- private ByteArrayOutputStream writeXmlAndPurge(String pkg, int uid, boolean forBackup,
- String... channelIds)
- throws Exception {
- XmlSerializer serializer = new FastXmlSerializer();
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
- serializer.startDocument(null, true);
- mHelper.writeXml(serializer, forBackup);
- serializer.endDocument();
- serializer.flush();
- for (String channelId : channelIds) {
- mHelper.permanentlyDeleteNotificationChannel(pkg, uid, channelId);
- }
- return baos;
- }
-
- private void loadStreamXml(ByteArrayOutputStream stream, boolean forRestore) throws Exception {
- loadByteArrayXml(stream.toByteArray(), forRestore);
- }
-
- private void loadByteArrayXml(byte[] byteArray, boolean forRestore) throws Exception {
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
- parser.nextTag();
- mHelper.readXml(parser, forRestore);
- }
-
- private void compareChannels(NotificationChannel expected, NotificationChannel actual) {
- assertEquals(expected.getId(), actual.getId());
- assertEquals(expected.getName(), actual.getName());
- assertEquals(expected.getDescription(), actual.getDescription());
- assertEquals(expected.shouldVibrate(), actual.shouldVibrate());
- assertEquals(expected.shouldShowLights(), actual.shouldShowLights());
- assertEquals(expected.getImportance(), actual.getImportance());
- assertEquals(expected.getLockscreenVisibility(), actual.getLockscreenVisibility());
- assertEquals(expected.getSound(), actual.getSound());
- assertEquals(expected.canBypassDnd(), actual.canBypassDnd());
- assertTrue(Arrays.equals(expected.getVibrationPattern(), actual.getVibrationPattern()));
- assertEquals(expected.getGroup(), actual.getGroup());
- assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
- assertEquals(expected.getLightColor(), actual.getLightColor());
- }
-
- private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
- assertEquals(expected.getId(), actual.getId());
- assertEquals(expected.getName(), actual.getName());
- assertEquals(expected.getDescription(), actual.getDescription());
- assertEquals(expected.isBlocked(), actual.isBlocked());
- }
-
- private NotificationChannel getChannel() {
- return new NotificationChannel("id", "name", IMPORTANCE_LOW);
- }
-
- private NotificationChannel findChannel(List<NotificationChannel> channels, String id) {
- for (NotificationChannel channel : channels) {
- if (channel.getId().equals(id)) {
- return channel;
- }
- }
- return null;
- }
-
- private void resetZenModeHelper() {
- reset(mMockZenModeHelper);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- }
-
@Test
public void testFindAfterRankingWithASplitGroup() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(3);
@@ -357,1496 +251,4 @@ public class RankingHelperTest extends UiServiceTestCase {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
mHelper.sort(notificationList);
}
-
- @Test
- public void testChannelXml() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
- ncg.setBlocked(true);
- ncg.setDescription("group desc");
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel2.setDescription("descriptions for all");
- channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel2.enableLights(true);
- channel2.setBypassDnd(true);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel2.enableVibration(true);
- channel2.setGroup(ncg.getId());
- channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
- channel2.setLightColor(Color.BLUE);
-
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-
- mHelper.setShowBadge(PKG, UID, true);
- mHelper.setAppImportanceLocked(PKG, UID);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false, channel1.getId(),
- channel2.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
-
- loadStreamXml(baos, false);
-
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
- assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
- compareChannels(channel2,
- mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-
- List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
- boolean foundNcg = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (ncg.getId().equals(actual.getId())) {
- foundNcg = true;
- compareGroups(ncg, actual);
- } else if (ncg2.getId().equals(actual.getId())) {
- compareGroups(ncg2, actual);
- }
- }
- assertTrue(foundNcg);
-
- boolean foundChannel2Group = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
- foundChannel2Group = true;
- break;
- }
- }
- assertTrue(foundChannel2Group);
- }
-
- @Test
- public void testChannelXmlForBackup() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
- NotificationChannel channel1 =
- 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);
- channel2.setBypassDnd(true);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel2.enableVibration(false);
- channel2.setGroup(ncg.getId());
- channel2.setLightColor(Color.BLUE);
- NotificationChannel channel3 = new NotificationChannel("id3", "NAM3", IMPORTANCE_HIGH);
- channel3.enableVibration(true);
-
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, false, false);
- mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
-
- mHelper.setShowBadge(PKG, UID, true);
-
- mHelper.setImportance(UPDATED_PKG, UID2, IMPORTANCE_NONE);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
- channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG, UPDATED_PKG},
- new int[]{UID, UID2});
-
- mHelper.setShowBadge(UPDATED_PKG, UID2, true);
-
- loadStreamXml(baos, true);
-
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(UPDATED_PKG, UID2));
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(channel1, mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
- compareChannels(channel2,
- mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- compareChannels(channel3,
- mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
-
- List<NotificationChannelGroup> actualGroups =
- mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
- boolean foundNcg = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (ncg.getId().equals(actual.getId())) {
- foundNcg = true;
- compareGroups(ncg, actual);
- } else if (ncg2.getId().equals(actual.getId())) {
- compareGroups(ncg2, actual);
- }
- }
- assertTrue(foundNcg);
-
- boolean foundChannel2Group = false;
- for (NotificationChannelGroup actual : actualGroups) {
- if (channel2.getGroup().equals(actual.getChannels().get(0).getGroup())) {
- foundChannel2Group = true;
- break;
- }
- }
- assertTrue(foundChannel2Group);
- }
-
- @Test
- public void testBackupXml_backupCanonicalizedSoundUri() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(SOUND_URI, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- // Testing that in restore we are given the canonical version
- loadStreamXml(baos, true);
- verify(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
- }
-
- @Test
- public void testRestoreXml_withExistentCanonicalizedSoundUri() throws Exception {
- Uri localUri = Uri.parse("content://" + TEST_AUTHORITY + "/local/url");
- Uri canonicalBasedOnLocal = localUri.buildUpon()
- .appendQueryParameter("title", "Test")
- .appendQueryParameter("canonical", "1")
- .build();
- when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(canonicalBasedOnLocal);
- when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(localUri);
- when(mTestIContentProvider.uncanonicalize(any(), eq(canonicalBasedOnLocal)))
- .thenReturn(localUri);
-
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(SOUND_URI, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- loadStreamXml(baos, true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false);
- assertEquals(localUri, actualChannel.getSound());
- }
-
- @Test
- public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
- Thread.sleep(3000);
- when(mTestIContentProvider.canonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(null);
- when(mTestIContentProvider.uncanonicalize(any(), eq(CANONICAL_SOUND_URI)))
- .thenReturn(null);
-
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(SOUND_URI, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- loadStreamXml(baos, true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false);
- assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
- }
-
-
- /**
- * Although we don't make backups with uncanonicalized uris anymore, we used to, so we have to
- * handle its restore properly.
- */
- @Test
- public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
- // Not a local uncanonicalized uri, simulating that it fails to exist locally
- when(mTestIContentProvider.canonicalize(any(), eq(SOUND_URI))).thenReturn(null);
- String id = "id";
- String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
- + "<package name=\"com.android.server.notification\" show_badge=\"true\">\n"
- + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" "
- + "sound=\"" + SOUND_URI + "\" "
- + "usage=\"6\" content_type=\"0\" flags=\"1\" show_badge=\"true\" />\n"
- + "<channel id=\"miscellaneous\" name=\"Uncategorized\" usage=\"5\" "
- + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n"
- + "</package>\n"
- + "</ranking>\n";
-
- loadByteArrayXml(backupWithUncanonicalizedSoundUri.getBytes(), true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(PKG, UID, id, false);
- assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI, actualChannel.getSound());
- }
-
- @Test
- public void testBackupRestoreXml_withNullSoundUri() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id", "name", IMPORTANCE_LOW);
- channel.setSound(null, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel.getId());
-
- loadStreamXml(baos, true);
-
- NotificationChannel actualChannel = mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false);
- assertEquals(null, actualChannel.getSound());
- }
-
- @Test
- public void testChannelXml_backup() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("1", "bye");
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("2", "hello");
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- NotificationChannel channel3 =
- new NotificationChannel("id3", "name3", IMPORTANCE_LOW);
- channel3.setGroup(ncg.getId());
-
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel1.getId());
- mHelper.deleteNotificationChannelGroup(PKG, UID, ncg.getId());
- assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, true, channel1.getId(),
- channel2.getId(), channel3.getId(), NotificationChannel.DEFAULT_CHANNEL_ID);
- mHelper.onPackagesChanged(true, UserHandle.myUserId(), new String[]{PKG}, new int[]{UID});
-
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(baos.toByteArray())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, true);
-
- assertNull(mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false));
- assertNull(mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
- assertNull(mHelper.getNotificationChannelGroup(ncg.getId(), PKG, UID));
- //assertEquals(ncg2, mHelper.getNotificationChannelGroup(ncg2.getId(), PKG, UID));
- assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
- }
-
- @Test
- public void testChannelXml_defaultChannelLegacyApp_noUserSettings() throws Exception {
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID);
-
- loadStreamXml(baos, false);
-
- final NotificationChannel updated = mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, updated.getImportance());
- assertFalse(updated.canBypassDnd());
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE, updated.getLockscreenVisibility());
- assertEquals(0, updated.getUserLockedFields());
- }
-
- @Test
- public void testChannelXml_defaultChannelUpdatedApp_userSettings() throws Exception {
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- defaultChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
-
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID);
-
- loadStreamXml(baos, false);
-
- assertEquals(NotificationManager.IMPORTANCE_LOW, mHelper.getNotificationChannel(
- PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false).getImportance());
- }
-
- @Test
- public void testChannelXml_upgradeCreateDefaultChannel() throws Exception {
- final String preupgradeXml = "<ranking version=\"1\">\n"
- + "<package name=\"" + PKG
- + "\" importance=\"" + NotificationManager.IMPORTANCE_HIGH
- + "\" priority=\"" + Notification.PRIORITY_MAX + "\" visibility=\""
- + Notification.VISIBILITY_SECRET + "\"" +" uid=\"" + UID + "\" />\n"
- + "<package name=\"" + UPDATED_PKG + "\" uid=\"" + UID2 + "\" visibility=\""
- + Notification.VISIBILITY_PRIVATE + "\" />\n"
- + "</ranking>";
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(new BufferedInputStream(new ByteArrayInputStream(preupgradeXml.getBytes())),
- null);
- parser.nextTag();
- mHelper.readXml(parser, false);
-
- final NotificationChannel updated1 =
- mHelper.getNotificationChannel(PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- assertEquals(NotificationManager.IMPORTANCE_HIGH, updated1.getImportance());
- assertTrue(updated1.canBypassDnd());
- assertEquals(Notification.VISIBILITY_SECRET, updated1.getLockscreenVisibility());
- assertEquals(NotificationChannel.USER_LOCKED_IMPORTANCE
- | NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY,
- updated1.getUserLockedFields());
-
- // No Default Channel created for updated packages
- assertEquals(null, mHelper.getNotificationChannel(UPDATED_PKG, UID2,
- NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @Test
- public void testChannelXml_upgradeDeletesDefaultChannel() throws Exception {
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(
- PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
- assertTrue(defaultChannel != null);
- ByteArrayOutputStream baos =
- writeXmlAndPurge(PKG, UID, false, NotificationChannel.DEFAULT_CHANNEL_ID);
- // Load package at higher sdk.
- final ApplicationInfo upgraded = new ApplicationInfo();
- upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
- loadStreamXml(baos, false);
-
- // Default Channel should be gone.
- assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @Test
- public void testDeletesDefaultChannelAfterChannelIsCreated() throws Exception {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
-
- // Load package at higher sdk.
- final ApplicationInfo upgraded = new ApplicationInfo();
- upgraded.targetSdkVersion = Build.VERSION_CODES.N_MR1 + 1;
- when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(upgraded);
- loadStreamXml(baos, false);
-
- // Default Channel should be gone.
- assertEquals(null, mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @Test
- public void testLoadingOldChannelsDoesNotDeleteNewlyCreatedChannels() throws Exception {
- ByteArrayOutputStream baos = writeXmlAndPurge(PKG, UID, false,
- NotificationChannel.DEFAULT_CHANNEL_ID, "bananas");
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
-
- loadStreamXml(baos, false);
-
- // Should still have the newly created channel that wasn't in the xml.
- assertTrue(mHelper.getNotificationChannel(PKG, UID, "bananas", false) != null);
- }
-
- @Test
- public void testCreateChannel_blocked() throws Exception {
- mHelper.setImportance(PKG, UID, IMPORTANCE_NONE);
-
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_LOW), true, false);
- }
-
- @Test
- public void testCreateChannel_badImportance() throws Exception {
- try {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE - 1),
- true, false);
- fail("Was allowed to create a channel with invalid importance");
- } catch (IllegalArgumentException e) {
- // yay
- }
- try {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_UNSPECIFIED),
- true, false);
- fail("Was allowed to create a channel with invalid importance");
- } catch (IllegalArgumentException e) {
- // yay
- }
- try {
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX + 1),
- true, false);
- fail("Was allowed to create a channel with invalid importance");
- } catch (IllegalArgumentException e) {
- // yay
- }
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_NONE), true, false);
- mHelper.createNotificationChannel(PKG, UID,
- new NotificationChannel("bananas", "bananas", IMPORTANCE_MAX), true, false);
- }
-
-
- @Test
- public void testUpdate() throws Exception {
- // no fields locked by user
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false, false);
-
- // same id, try to update all fields
- final NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", NotificationManager.IMPORTANCE_HIGH);
- channel2.setSound(new Uri.Builder().scheme("test2").build(), mAudioAttributes);
- channel2.enableLights(false);
- channel2.setBypassDnd(false);
- channel2.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
-
- mHelper.updateNotificationChannel(PKG, UID, channel2, true);
-
- // all fields should be changed
- assertEquals(channel2, mHelper.getNotificationChannel(PKG, UID, channel.getId(), false));
-
- verify(mHandler, times(1)).requestSort();
- }
-
- @Test
- public void testUpdate_preUpgrade_updatesAppFields() throws Exception {
- mHelper.setImportance(PKG, UID, IMPORTANCE_UNSPECIFIED);
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
- mHelper.getPackageVisibility(PKG, UID));
- assertFalse(mHelper.getIsAppImportanceLocked(PKG, UID));
-
- NotificationChannel defaultChannel = mHelper.getNotificationChannel(
- PKG, UID, NotificationChannel.DEFAULT_CHANNEL_ID, false);
-
- defaultChannel.setShowBadge(false);
- defaultChannel.setImportance(IMPORTANCE_NONE);
- defaultChannel.setBypassDnd(true);
- defaultChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
- mHelper.setAppImportanceLocked(PKG, UID);
- mHelper.updateNotificationChannel(PKG, UID, defaultChannel, true);
-
- // ensure app level fields are changed
- assertFalse(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_MAX, mHelper.getPackagePriority(PKG, UID));
- assertEquals(Notification.VISIBILITY_SECRET, mHelper.getPackageVisibility(PKG, UID));
- assertEquals(IMPORTANCE_NONE, mHelper.getImportance(PKG, UID));
- assertTrue(mHelper.getIsAppImportanceLocked(PKG, UID));
- }
-
- @Test
- public void testUpdate_postUpgrade_noUpdateAppFields() throws Exception {
- final NotificationChannel channel = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-
- mHelper.createNotificationChannel(PKG, UID, channel, false, false);
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
- mHelper.getPackageVisibility(PKG, UID));
-
- channel.setShowBadge(false);
- channel.setImportance(IMPORTANCE_NONE);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
-
- mHelper.updateNotificationChannel(PKG, UID, channel, true);
-
- // ensure app level fields are not changed
- assertTrue(mHelper.canShowBadge(PKG, UID));
- assertEquals(Notification.PRIORITY_DEFAULT, mHelper.getPackagePriority(PKG, UID));
- assertEquals(NotificationManager.VISIBILITY_NO_OVERRIDE,
- mHelper.getPackageVisibility(PKG, UID));
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
- }
-
- @Test
- public void testGetNotificationChannel_ReturnsNullForUnknownChannel() throws Exception {
- assertEquals(null, mHelper.getNotificationChannel(PKG, UID, "garbage", false));
- }
-
- @Test
- public void testCreateChannel_CannotChangeHiddenFields() throws Exception {
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.setShowBadge(true);
- int lockMask = 0;
- for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
- lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
- }
- channel.lockFields(lockMask);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- NotificationChannel savedChannel =
- mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-
- assertEquals(channel.getName(), savedChannel.getName());
- assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
- assertFalse(savedChannel.canBypassDnd());
- assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
- assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
-
- verify(mHandler, never()).requestSort();
- }
-
- @Test
- public void testCreateChannel_CannotChangeHiddenFieldsAssistant() throws Exception {
- final NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.setShowBadge(true);
- int lockMask = 0;
- for (int i = 0; i < NotificationChannel.LOCKABLE_FIELDS.length; i++) {
- lockMask |= NotificationChannel.LOCKABLE_FIELDS[i];
- }
- channel.lockFields(lockMask);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- NotificationChannel savedChannel =
- mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
-
- assertEquals(channel.getName(), savedChannel.getName());
- assertEquals(channel.shouldShowLights(), savedChannel.shouldShowLights());
- assertFalse(savedChannel.canBypassDnd());
- assertFalse(Notification.VISIBILITY_SECRET == savedChannel.getLockscreenVisibility());
- assertEquals(channel.canShowBadge(), savedChannel.canShowBadge());
- }
-
- @Test
- public void testClearLockedFields() throws Exception {
- final NotificationChannel channel = getChannel();
- mHelper.clearLockedFields(channel);
- assertEquals(0, channel.getUserLockedFields());
-
- channel.lockFields(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_IMPORTANCE);
- mHelper.clearLockedFields(channel);
- assertEquals(0, channel.getUserLockedFields());
- }
-
- @Test
- public void testLockFields_soundAndVibration() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
- final NotificationChannel update1 = getChannel();
- update1.setSound(new Uri.Builder().scheme("test").build(),
- new AudioAttributes.Builder().build());
- update1.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_SOUND,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- NotificationChannel update2 = getChannel();
- update2.enableVibration(true);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_SOUND
- | NotificationChannel.USER_LOCKED_VIBRATION,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testLockFields_vibrationAndLights() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
- final NotificationChannel update1 = getChannel();
- update1.setVibrationPattern(new long[]{7945, 46 ,246});
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_VIBRATION,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update2 = getChannel();
- update2.enableLights(true);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_VIBRATION
- | NotificationChannel.USER_LOCKED_LIGHTS,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testLockFields_lightsAndImportance() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
-
- final NotificationChannel update1 = getChannel();
- update1.setLightColor(Color.GREEN);
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_LIGHTS,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update2 = getChannel();
- update2.setImportance(IMPORTANCE_DEFAULT);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_LIGHTS
- | NotificationChannel.USER_LOCKED_IMPORTANCE,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testLockFields_visibilityAndDndAndBadge() throws Exception {
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
- assertEquals(0,
- mHelper.getNotificationChannel(PKG, UID, getChannel().getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update1 = getChannel();
- update1.setBypassDnd(true);
- mHelper.updateNotificationChannel(PKG, UID, update1, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY,
- mHelper.getNotificationChannel(PKG, UID, update1.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update2 = getChannel();
- update2.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- mHelper.updateNotificationChannel(PKG, UID, update2, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY,
- mHelper.getNotificationChannel(PKG, UID, update2.getId(), false)
- .getUserLockedFields());
-
- final NotificationChannel update3 = getChannel();
- update3.setShowBadge(false);
- mHelper.updateNotificationChannel(PKG, UID, update3, true);
- assertEquals(NotificationChannel.USER_LOCKED_PRIORITY
- | NotificationChannel.USER_LOCKED_VISIBILITY
- | NotificationChannel.USER_LOCKED_SHOW_BADGE,
- mHelper.getNotificationChannel(PKG, UID, update3.getId(), false)
- .getUserLockedFields());
- }
-
- @Test
- public void testDeleteNonExistentChannel() throws Exception {
- mHelper.deleteNotificationChannelGroup(PKG, UID, "does not exist");
- }
-
- @Test
- public void testGetDeletedChannel() throws Exception {
- NotificationChannel channel = getChannel();
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);
- channel.enableVibration(true);
- channel.setVibrationPattern(new long[]{100, 67, 145, 156});
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
- // Does not return deleted channel
- NotificationChannel response =
- mHelper.getNotificationChannel(PKG, UID, channel.getId(), false);
- assertNull(response);
-
- // Returns deleted channel
- response = mHelper.getNotificationChannel(PKG, UID, channel.getId(), true);
- compareChannels(channel, response);
- assertTrue(response.isDeleted());
- }
-
- @Test
- public void testGetDeletedChannels() throws Exception {
- Map<String, NotificationChannel> channelMap = new HashMap<>();
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes);
- channel.enableLights(true);
- channel.setBypassDnd(true);
- channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
- channel.enableVibration(true);
- channel.setVibrationPattern(new long[]{100, 67, 145, 156});
- channelMap.put(channel.getId(), channel);
- NotificationChannel channel2 =
- new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- channelMap.put(channel2.getId(), channel2);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
- // Returns only non-deleted channels
- List<NotificationChannel> channels =
- mHelper.getNotificationChannels(PKG, UID, false).getList();
- assertEquals(2, channels.size()); // Default channel + non-deleted channel
- for (NotificationChannel nc : channels) {
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
- compareChannels(channel2, nc);
- }
- }
-
- // Returns deleted channels too
- channels = mHelper.getNotificationChannels(PKG, UID, true).getList();
- assertEquals(3, channels.size()); // Includes default channel
- for (NotificationChannel nc : channels) {
- if (!NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
- compareChannels(channelMap.get(nc.getId()), nc);
- }
- }
- }
-
- @Test
- public void testGetDeletedChannelCount() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- NotificationChannel channel2 =
- new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel3 =
- new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
- mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
-
- assertEquals(2, mHelper.getDeletedChannelCount(PKG, UID));
- assertEquals(0, mHelper.getDeletedChannelCount("pkg2", UID2));
- }
-
- @Test
- public void testGetBlockedChannelCount() throws Exception {
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- NotificationChannel channel2 =
- new NotificationChannel("id4", "a", NotificationManager.IMPORTANCE_NONE);
- NotificationChannel channel3 =
- new NotificationChannel("id5", "a", NotificationManager.IMPORTANCE_NONE);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- mHelper.deleteNotificationChannel(PKG, UID, channel3.getId());
-
- assertEquals(1, mHelper.getBlockedChannelCount(PKG, UID));
- assertEquals(0, mHelper.getBlockedChannelCount("pkg2", UID2));
- }
-
- @Test
- public void testCreateAndDeleteCanChannelsBypassDnd() throws Exception {
- // create notification channel that can't bypass dnd
- // expected result: areChannelsBypassingDnd = false
- // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
- NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // create notification channel that can bypass dnd
- // expected result: areChannelsBypassingDnd = true
- NotificationChannel channel2 = new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel2.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG, UID, channel2, true, true);
- assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // delete channels
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
- assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
-
- mHelper.deleteNotificationChannel(PKG, UID, channel2.getId());
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testUpdateCanChannelsBypassDnd() throws Exception {
- // create notification channel that can't bypass dnd
- // expected result: areChannelsBypassingDnd = false
- // setNotificationPolicy isn't called since areChannelsBypassingDnd was already false
- NotificationChannel channel = new NotificationChannel("id1", "name1", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // update channel so it CAN bypass dnd:
- // expected result: areChannelsBypassingDnd = true
- channel.setBypassDnd(true);
- mHelper.updateNotificationChannel(PKG, UID, channel, true);
- assertTrue(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
-
- // update channel so it can't bypass dnd:
- // expected result: areChannelsBypassingDnd = false
- channel.setBypassDnd(false);
- mHelper.updateNotificationChannel(PKG, UID, channel, true);
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testSetupNewZenModeHelper_canBypass() {
- // start notification policy off with mAreChannelsBypassingDnd = true, but
- // RankingHelper should change to false
- mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
- NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mUsageStats, new String[] {ImportanceExtractor.class.getName()});
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testSetupNewZenModeHelper_cannotBypass() {
- // start notification policy off with mAreChannelsBypassingDnd = false
- mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0);
- when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy);
- mHelper = new RankingHelper(getContext(), mPm, mHandler, mMockZenModeHelper,
- mUsageStats, new String[] {ImportanceExtractor.class.getName()});
- assertFalse(mHelper.areChannelsBypassingDnd());
- verify(mMockZenModeHelper, never()).setNotificationPolicy(any());
- resetZenModeHelper();
- }
-
- @Test
- public void testCreateDeletedChannel() throws Exception {
- long[] vibration = new long[]{100, 67, 145, 156};
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setVibrationPattern(vibration);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- mHelper.deleteNotificationChannel(PKG, UID, channel.getId());
-
- NotificationChannel newChannel = new NotificationChannel(
- channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
- newChannel.setVibrationPattern(new long[]{100});
-
- mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
-
- // No long deleted, using old settings
- compareChannels(channel,
- mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
- }
-
- @Test
- public void testOnlyHasDefaultChannel() throws Exception {
- assertTrue(mHelper.onlyHasDefaultChannel(PKG, UID));
- assertFalse(mHelper.onlyHasDefaultChannel(UPDATED_PKG, UID2));
-
- mHelper.createNotificationChannel(PKG, UID, getChannel(), true, false);
- assertFalse(mHelper.onlyHasDefaultChannel(PKG, UID));
- }
-
- @Test
- public void testCreateChannel_defaultChannelId() throws Exception {
- try {
- mHelper.createNotificationChannel(PKG, UID, new NotificationChannel(
- NotificationChannel.DEFAULT_CHANNEL_ID, "ha", IMPORTANCE_HIGH), true, false);
- fail("Allowed to create default channel");
- } catch (IllegalArgumentException e) {
- // pass
- }
- }
-
- @Test
- public void testCreateChannel_alreadyExists() throws Exception {
- long[] vibration = new long[]{100, 67, 145, 156};
- NotificationChannel channel =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
- channel.setVibrationPattern(vibration);
-
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
-
- NotificationChannel newChannel = new NotificationChannel(
- channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
- newChannel.setVibrationPattern(new long[]{100});
-
- mHelper.createNotificationChannel(PKG, UID, newChannel, true, false);
-
- // Old settings not overridden
- compareChannels(channel,
- mHelper.getNotificationChannel(PKG, UID, newChannel.getId(), false));
- }
-
- @Test
- public void testCreateChannel_noOverrideSound() throws Exception {
- Uri sound = new Uri.Builder().scheme("test").build();
- final NotificationChannel channel = new NotificationChannel("id2", "name2",
- NotificationManager.IMPORTANCE_DEFAULT);
- channel.setSound(sound, mAudioAttributes);
- mHelper.createNotificationChannel(PKG, UID, channel, true, false);
- assertEquals(sound, mHelper.getNotificationChannel(
- PKG, UID, channel.getId(), false).getSound());
- }
-
- @Test
- public void testPermanentlyDeleteChannels() throws Exception {
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
-
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.createNotificationChannel(PKG, UID, channel2, false, false);
-
- mHelper.permanentlyDeleteNotificationChannels(PKG, UID);
-
- // Only default channel remains
- assertEquals(1, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
- }
-
- @Test
- public void testDeleteGroup() throws Exception {
- NotificationChannelGroup notDeleted = new NotificationChannelGroup("not", "deleted");
- NotificationChannelGroup deleted = new NotificationChannelGroup("totally", "deleted");
- NotificationChannel nonGroupedNonDeletedChannel =
- new NotificationChannel("no group", "so not deleted", IMPORTANCE_HIGH);
- NotificationChannel groupedButNotDeleted =
- new NotificationChannel("not deleted", "belongs to notDeleted", IMPORTANCE_DEFAULT);
- groupedButNotDeleted.setGroup("not");
- NotificationChannel groupedAndDeleted =
- new NotificationChannel("deleted", "belongs to deleted", IMPORTANCE_DEFAULT);
- groupedAndDeleted.setGroup("totally");
-
- mHelper.createNotificationChannelGroup(PKG, UID, notDeleted, true);
- mHelper.createNotificationChannelGroup(PKG, UID, deleted, true);
- mHelper.createNotificationChannel(PKG, UID, nonGroupedNonDeletedChannel, true, false);
- mHelper.createNotificationChannel(PKG, UID, groupedAndDeleted, true, false);
- mHelper.createNotificationChannel(PKG, UID, groupedButNotDeleted, true, false);
-
- mHelper.deleteNotificationChannelGroup(PKG, UID, deleted.getId());
-
- assertNull(mHelper.getNotificationChannelGroup(deleted.getId(), PKG, UID));
- assertNotNull(mHelper.getNotificationChannelGroup(notDeleted.getId(), PKG, UID));
-
- assertNull(mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), false));
- compareChannels(groupedAndDeleted,
- mHelper.getNotificationChannel(PKG, UID, groupedAndDeleted.getId(), true));
-
- compareChannels(groupedButNotDeleted,
- mHelper.getNotificationChannel(PKG, UID, groupedButNotDeleted.getId(), false));
- compareChannels(nonGroupedNonDeletedChannel, mHelper.getNotificationChannel(
- PKG, UID, nonGroupedNonDeletedChannel.getId(), false));
-
- // notDeleted
- assertEquals(1, mHelper.getNotificationChannelGroups(PKG, UID).size());
-
- verify(mHandler, never()).requestSort();
- }
-
- @Test
- public void testOnUserRemoved() throws Exception {
- int[] user0Uids = {98, 235, 16, 3782};
- int[] user1Uids = new int[user0Uids.length];
- for (int i = 0; i < user0Uids.length; i++) {
- user1Uids[i] = UserHandle.PER_USER_RANGE + user0Uids[i];
-
- final ApplicationInfo legacy = new ApplicationInfo();
- legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- when(mPm.getApplicationInfoAsUser(eq(PKG), anyInt(), anyInt())).thenReturn(legacy);
-
- // create records with the default channel for all user 0 and user 1 uids
- mHelper.getImportance(PKG, user0Uids[i]);
- mHelper.getImportance(PKG, user1Uids[i]);
- }
-
- mHelper.onUserRemoved(1);
-
- // user 0 records remain
- for (int i = 0; i < user0Uids.length; i++) {
- assertEquals(1,
- mHelper.getNotificationChannels(PKG, user0Uids[i], false).getList().size());
- }
- // user 1 records are gone
- for (int i = 0; i < user1Uids.length; i++) {
- assertEquals(0,
- mHelper.getNotificationChannels(PKG, user1Uids[i], false).getList().size());
- }
- }
-
- @Test
- public void testOnPackageChanged_packageRemoval() throws Exception {
- // Deleted
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
- assertEquals(0, mHelper.getNotificationChannels(PKG, UID, true).getList().size());
-
- // Not deleted
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
- mHelper.onPackagesChanged(false, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
- assertEquals(2, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
- }
-
- @Test
- public void testOnPackageChanged_packageRemoval_importance() throws Exception {
- mHelper.setImportance(PKG, UID, NotificationManager.IMPORTANCE_HIGH);
-
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
- }
-
- @Test
- public void testOnPackageChanged_packageRemoval_groups() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-
- mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
-
- assertEquals(0,
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
- }
-
- @Test
- public void testOnPackageChange_downgradeTargetSdk() throws Exception {
- // create channel as api 26
- mHelper.createNotificationChannel(UPDATED_PKG, UID2, getChannel(), true, false);
-
- // install new app version targeting 25
- final ApplicationInfo legacy = new ApplicationInfo();
- legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1;
- when(mPm.getApplicationInfoAsUser(eq(UPDATED_PKG), anyInt(), anyInt())).thenReturn(legacy);
- mHelper.onPackagesChanged(
- false, UserHandle.USER_SYSTEM, new String[]{UPDATED_PKG}, new int[]{UID2});
-
- // make sure the default channel was readded
- //assertEquals(2, mHelper.getNotificationChannels(UPDATED_PKG, UID2, false).getList().size());
- assertNotNull(mHelper.getNotificationChannel(
- UPDATED_PKG, UID2, NotificationChannel.DEFAULT_CHANNEL_ID, false));
- }
-
- @Test
- public void testRecordDefaults() throws Exception {
- assertEquals(NotificationManager.IMPORTANCE_UNSPECIFIED, mHelper.getImportance(PKG, UID));
- assertEquals(true, mHelper.canShowBadge(PKG, UID));
- assertEquals(1, mHelper.getNotificationChannels(PKG, UID, false).getList().size());
- }
-
- @Test
- public void testCreateGroup() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG, UID).iterator().next());
- verify(mHandler, never()).requestSort();
- }
-
- @Test
- public void testCannotCreateChannel_badGroup() throws Exception {
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1.setGroup("garbage");
- try {
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- fail("Created a channel with a bad group");
- } catch (IllegalArgumentException e) {
- }
- }
-
- @Test
- public void testCannotCreateChannel_goodGroup() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
-
- assertEquals(ncg.getId(),
- mHelper.getNotificationChannel(PKG, UID, channel1.getId(), false).getGroup());
- }
-
- @Test
- public void testGetChannelGroups() throws Exception {
- NotificationChannelGroup unused = new NotificationChannelGroup("unused", "s");
- mHelper.createNotificationChannelGroup(PKG, UID, unused, true);
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
- NotificationChannelGroup ncg2 = new NotificationChannelGroup("group2", "name2");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg2, true);
-
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- NotificationChannel channel1a =
- new NotificationChannel("id1a", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1a.setGroup(ncg.getId());
- mHelper.createNotificationChannel(PKG, UID, channel1a, true, false);
-
- NotificationChannel channel2 =
- new NotificationChannel("id2", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel2.setGroup(ncg2.getId());
- mHelper.createNotificationChannel(PKG, UID, channel2, true, false);
-
- NotificationChannel channel3 =
- new NotificationChannel("id3", "name1", NotificationManager.IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, channel3, true, false);
-
- List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
- assertEquals(3, actual.size());
- for (NotificationChannelGroup group : actual) {
- if (group.getId() == null) {
- assertEquals(2, group.getChannels().size()); // misc channel too
- assertTrue(channel3.getId().equals(group.getChannels().get(0).getId())
- || channel3.getId().equals(group.getChannels().get(1).getId()));
- } else if (group.getId().equals(ncg.getId())) {
- assertEquals(2, group.getChannels().size());
- if (group.getChannels().get(0).getId().equals(channel1.getId())) {
- assertTrue(group.getChannels().get(1).getId().equals(channel1a.getId()));
- } else if (group.getChannels().get(0).getId().equals(channel1a.getId())) {
- assertTrue(group.getChannels().get(1).getId().equals(channel1.getId()));
- } else {
- fail("expected channel not found");
- }
- } else if (group.getId().equals(ncg2.getId())) {
- assertEquals(1, group.getChannels().size());
- assertEquals(channel2.getId(), group.getChannels().get(0).getId());
- }
- }
- }
-
- @Test
- public void testGetChannelGroups_noSideEffects() throws Exception {
- NotificationChannelGroup ncg = new NotificationChannelGroup("group1", "name1");
- mHelper.createNotificationChannelGroup(PKG, UID, ncg, true);
-
- NotificationChannel channel1 =
- new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
- channel1.setGroup(ncg.getId());
- mHelper.createNotificationChannel(PKG, UID, channel1, true, false);
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-
- channel1.setImportance(IMPORTANCE_LOW);
- mHelper.updateNotificationChannel(PKG, UID, channel1, true);
-
- List<NotificationChannelGroup> actual =
- mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
-
- assertEquals(2, actual.size());
- for (NotificationChannelGroup group : actual) {
- if (Objects.equals(group.getId(), ncg.getId())) {
- assertEquals(1, group.getChannels().size());
- }
- }
- }
-
- @Test
- public void testCreateChannel_updateName() throws Exception {
- NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
- NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertEquals("hello", actual.getName());
-
- nc = new NotificationChannel("id", "goodbye", IMPORTANCE_HIGH);
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-
- actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertEquals("goodbye", actual.getName());
- assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
-
- verify(mHandler, times(1)).requestSort();
- }
-
- @Test
- public void testCreateChannel_addToGroup() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- NotificationChannel nc = new NotificationChannel("id", "hello", IMPORTANCE_DEFAULT);
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
- NotificationChannel actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertNull(actual.getGroup());
-
- nc = new NotificationChannel("id", "hello", IMPORTANCE_HIGH);
- nc.setGroup(group.getId());
- mHelper.createNotificationChannel(PKG, UID, nc, true, false);
-
- actual = mHelper.getNotificationChannel(PKG, UID, "id", false);
- assertNotNull(actual.getGroup());
- assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
-
- verify(mHandler, times(1)).requestSort();
- }
-
- @Test
- public void testDumpChannelsJson() throws Exception {
- final ApplicationInfo upgrade = new ApplicationInfo();
- upgrade.targetSdkVersion = Build.VERSION_CODES.O;
- try {
- when(mPm.getApplicationInfoAsUser(
- anyString(), anyInt(), anyInt())).thenReturn(upgrade);
- } catch (PackageManager.NameNotFoundException e) {
- }
- ArrayMap<String, Integer> expectedChannels = new ArrayMap<>();
- int numPackages = ThreadLocalRandom.current().nextInt(1, 5);
- for (int i = 0; i < numPackages; i++) {
- String pkgName = "pkg" + i;
- int numChannels = ThreadLocalRandom.current().nextInt(1, 10);
- for (int j = 0; j < numChannels; j++) {
- mHelper.createNotificationChannel(pkgName, UID,
- new NotificationChannel("" + j, "a", IMPORTANCE_HIGH), true, false);
- }
- expectedChannels.put(pkgName, numChannels);
- }
-
- // delete the first channel of the first package
- String pkg = expectedChannels.keyAt(0);
- mHelper.deleteNotificationChannel("pkg" + 0, UID, "0");
- // dump should not include deleted channels
- int count = expectedChannels.get(pkg);
- expectedChannels.put(pkg, count - 1);
-
- JSONArray actual = mHelper.dumpChannelsJson(new NotificationManagerService.DumpFilter());
- assertEquals(numPackages, actual.length());
- for (int i = 0; i < numPackages; i++) {
- JSONObject object = actual.getJSONObject(i);
- assertTrue(expectedChannels.containsKey(object.get("packageName")));
- assertEquals(expectedChannels.get(object.get("packageName")).intValue(),
- object.getInt("channelCount"));
- }
- }
-
- @Test
- public void testBadgingOverrideTrue() throws Exception {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 1,
- USER.getIdentifier());
- mHelper.updateBadgingEnabled(); // would be called by settings observer
- assertTrue(mHelper.badgingEnabled(USER));
- }
-
- @Test
- public void testBadgingOverrideFalse() throws Exception {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 0,
- USER.getIdentifier());
- mHelper.updateBadgingEnabled(); // would be called by settings observer
- assertFalse(mHelper.badgingEnabled(USER));
- }
-
- @Test
- public void testBadgingForUserAll() throws Exception {
- try {
- mHelper.badgingEnabled(UserHandle.ALL);
- } catch (Exception e) {
- fail("just don't throw");
- }
- }
-
- @Test
- public void testBadgingOverrideUserIsolation() throws Exception {
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 0,
- USER.getIdentifier());
- Secure.putIntForUser(getContext().getContentResolver(),
- Secure.NOTIFICATION_BADGING, 1,
- USER2.getIdentifier());
- mHelper.updateBadgingEnabled(); // would be called by settings observer
- assertFalse(mHelper.badgingEnabled(USER));
- assertTrue(mHelper.badgingEnabled(USER2));
- }
-
- @Test
- public void testOnLocaleChanged_updatesDefaultChannels() throws Exception {
- String newLabel = "bananas!";
- final NotificationChannel defaultChannel = mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false);
- assertFalse(newLabel.equals(defaultChannel.getName()));
-
- Resources res = mock(Resources.class);
- when(mContext.getResources()).thenReturn(res);
- when(res.getString(com.android.internal.R.string.default_notification_channel_label))
- .thenReturn(newLabel);
-
- mHelper.onLocaleChanged(mContext, USER.getIdentifier());
-
- assertEquals(newLabel, mHelper.getNotificationChannel(PKG, UID,
- NotificationChannel.DEFAULT_CHANNEL_ID, false).getName());
- }
-
- @Test
- public void testIsGroupBlocked_noGroup() throws Exception {
- assertFalse(mHelper.isGroupBlocked(PKG, UID, null));
-
- assertFalse(mHelper.isGroupBlocked(PKG, UID, "non existent group"));
- }
-
- @Test
- public void testIsGroupBlocked_notBlocked() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
-
- assertFalse(mHelper.isGroupBlocked(PKG, UID, group.getId()));
- }
-
- @Test
- public void testIsGroupBlocked_blocked() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- group.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG, UID, group, false);
-
- assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
- }
-
- @Test
- public void testIsGroup_appCannotResetBlock() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- NotificationChannelGroup group2 = group.clone();
- group2.setBlocked(true);
- mHelper.createNotificationChannelGroup(PKG, UID, group2, false);
- assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
-
- NotificationChannelGroup group3 = group.clone();
- group3.setBlocked(false);
- mHelper.createNotificationChannelGroup(PKG, UID, group3, true);
- assertTrue(mHelper.isGroupBlocked(PKG, UID, group.getId()));
- }
-
- @Test
- public void testGetNotificationChannelGroupWithChannels() throws Exception {
- NotificationChannelGroup group = new NotificationChannelGroup("group", "");
- NotificationChannelGroup other = new NotificationChannelGroup("something else", "");
- mHelper.createNotificationChannelGroup(PKG, UID, group, true);
- mHelper.createNotificationChannelGroup(PKG, UID, other, true);
-
- NotificationChannel a = new NotificationChannel("a", "a", IMPORTANCE_DEFAULT);
- a.setGroup(group.getId());
- NotificationChannel b = new NotificationChannel("b", "b", IMPORTANCE_DEFAULT);
- b.setGroup(other.getId());
- NotificationChannel c = new NotificationChannel("c", "c", IMPORTANCE_DEFAULT);
- c.setGroup(group.getId());
- NotificationChannel d = new NotificationChannel("d", "d", IMPORTANCE_DEFAULT);
-
- mHelper.createNotificationChannel(PKG, UID, a, true, false);
- mHelper.createNotificationChannel(PKG, UID, b, true, false);
- mHelper.createNotificationChannel(PKG, UID, c, true, false);
- mHelper.createNotificationChannel(PKG, UID, d, true, false);
- mHelper.deleteNotificationChannel(PKG, UID, c.getId());
-
- NotificationChannelGroup retrieved = mHelper.getNotificationChannelGroupWithChannels(
- PKG, UID, group.getId(), true);
- assertEquals(2, retrieved.getChannels().size());
- compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
- compareChannels(c, findChannel(retrieved.getChannels(), c.getId()));
-
- retrieved = mHelper.getNotificationChannelGroupWithChannels(
- PKG, UID, group.getId(), false);
- assertEquals(1, retrieved.getChannels().size());
- compareChannels(a, findChannel(retrieved.getChannels(), a.getId()));
- }
-
- @Test
- public void testAndroidPkgCannotBypassDnd_creation() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- test.setBypassDnd(true);
-
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
-
- assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
- .canBypassDnd());
- }
-
- @Test
- public void testDndPkgCanBypassDnd_creation() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- test.setBypassDnd(true);
-
- mHelper.createNotificationChannel(PKG, UID, test, true, true);
-
- assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
- }
-
- @Test
- public void testNormalPkgCannotBypassDnd_creation() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- test.setBypassDnd(true);
-
- mHelper.createNotificationChannel(PKG, 1000, test, true, false);
-
- assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
- }
-
- @Test
- public void testAndroidPkgCannotBypassDnd_update() throws Exception {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, test, true, false);
-
- NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- update.setBypassDnd(true);
- mHelper.createNotificationChannel(SYSTEM_PKG, SYSTEM_UID, update, true, false);
-
- assertFalse(mHelper.getNotificationChannel(SYSTEM_PKG, SYSTEM_UID, "A", false)
- .canBypassDnd());
- }
-
- @Test
- public void testDndPkgCanBypassDnd_update() throws Exception {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, UID, test, true, true);
-
- NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- update.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG, UID, update, true, true);
-
- assertTrue(mHelper.getNotificationChannel(PKG, UID, "A", false).canBypassDnd());
- }
-
- @Test
- public void testNormalPkgCannotBypassDnd_update() {
- NotificationChannel test = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- mHelper.createNotificationChannel(PKG, 1000, test, true, false);
- NotificationChannel update = new NotificationChannel("A", "a", IMPORTANCE_LOW);
- update.setBypassDnd(true);
- mHelper.createNotificationChannel(PKG, 1000, update, true, false);
- assertFalse(mHelper.getNotificationChannel(PKG, 1000, "A", false).canBypassDnd());
- }
-
- @Test
- public void testGetBlockedAppCount_noApps() {
- assertEquals(0, mHelper.getBlockedAppCount(0));
- }
-
- @Test
- public void testGetBlockedAppCount_noAppsForUserId() {
- mHelper.setEnabled(PKG, 100, false);
- assertEquals(0, mHelper.getBlockedAppCount(9));
- }
-
- @Test
- public void testGetBlockedAppCount_appsForUserId() {
- mHelper.setEnabled(PKG, 1020, false);
- mHelper.setEnabled(PKG, 1030, false);
- mHelper.setEnabled(PKG, 1060, false);
- mHelper.setEnabled(PKG, 1000, true);
- assertEquals(3, mHelper.getBlockedAppCount(0));
- }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 5239fe5ef6f4..35f64a183d96 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -974,7 +974,8 @@ public class UsageStatsService extends SystemService implements
final long token = Binder.clearCallingIdentity();
try {
final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
- PackageManager.MATCH_ANY_USER, userId);
+ PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
// Caller cannot set their own standby state
if (packageUid == callingUid) {
throw new IllegalArgumentException("Cannot set your own standby bucket");
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 3e97c8f40bf8..468c8fa9e30c 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -854,6 +854,8 @@ public abstract class Connection extends Conferenceable {
private final OutputStreamWriter mPipeToInCall;
private final ParcelFileDescriptor mFdFromInCall;
private final ParcelFileDescriptor mFdToInCall;
+
+ private final FileInputStream mFromInCallFileInputStream;
private char[] mReadBuffer = new char[READ_BUFFER_SIZE];
/**
@@ -862,11 +864,11 @@ public abstract class Connection extends Conferenceable {
public RttTextStream(ParcelFileDescriptor toInCall, ParcelFileDescriptor fromInCall) {
mFdFromInCall = fromInCall;
mFdToInCall = toInCall;
+ mFromInCallFileInputStream = new FileInputStream(fromInCall.getFileDescriptor());
// Wrap the FileInputStream in a Channel so that it's interruptible.
mPipeFromInCall = new InputStreamReader(
- Channels.newInputStream(Channels.newChannel(
- new FileInputStream(fromInCall.getFileDescriptor()))));
+ Channels.newInputStream(Channels.newChannel(mFromInCallFileInputStream)));
mPipeToInCall = new OutputStreamWriter(
new FileOutputStream(toInCall.getFileDescriptor()));
}
@@ -914,7 +916,7 @@ public abstract class Connection extends Conferenceable {
* not entered any new text yet.
*/
public String readImmediately() throws IOException {
- if (mPipeFromInCall.ready()) {
+ if (mFromInCallFileInputStream.available() > 0) {
return read();
} else {
return null;
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 890a6ea7c88e..2a41829dcc2f 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -175,7 +175,10 @@ public abstract class CellIdentity implements Parcelable {
}
CellIdentity o = (CellIdentity) other;
- return TextUtils.equals(mAlphaLong, o.mAlphaLong)
+ return mType == o.mType
+ && TextUtils.equals(mMccStr, o.mMccStr)
+ && TextUtils.equals(mMncStr, o.mMncStr)
+ && TextUtils.equals(mAlphaLong, o.mAlphaLong)
&& TextUtils.equals(mAlphaShort, o.mAlphaShort);
}
@@ -233,4 +236,4 @@ public abstract class CellIdentity implements Parcelable {
protected void log(String s) {
Rlog.w(mTag, s);
}
-} \ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 18ab6d4d2bd6..b99fe466ec46 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -16,9 +16,7 @@
package android.telephony;
-import android.annotation.Nullable;
import android.os.Parcel;
-import android.text.TextUtils;
import java.util.Objects;
@@ -35,6 +33,8 @@ public final class CellIdentityTdscdma extends CellIdentity {
private final int mCid;
// 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown.
private final int mCpid;
+ // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
+ private final int mUarfcn;
/**
* @hide
@@ -44,6 +44,7 @@ public final class CellIdentityTdscdma extends CellIdentity {
mLac = Integer.MAX_VALUE;
mCid = Integer.MAX_VALUE;
mCpid = Integer.MAX_VALUE;
+ mUarfcn = Integer.MAX_VALUE;
}
/**
@@ -52,28 +53,12 @@ public final class CellIdentityTdscdma extends CellIdentity {
* @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
* @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
* @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
*
* @hide
*/
- public CellIdentityTdscdma(int mcc, int mnc, int lac, int cid, int cpid) {
- this(String.valueOf(mcc), String.valueOf(mnc), lac, cid, cpid, null, null);
- }
-
- /**
- * @param mcc 3-digit Mobile Country Code in string format
- * @param mnc 2 or 3-digit Mobile Network Code in string format
- * @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
- * @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
- * @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
- *
- * FIXME: This is a temporary constructor to facilitate migration.
- * @hide
- */
- public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid) {
- super(TAG, TYPE_TDSCDMA, mcc, mnc, null, null);
- mLac = lac;
- mCid = cid;
- mCpid = cpid;
+ public CellIdentityTdscdma(int mcc, int mnc, int lac, int cid, int cpid, int uarfcn) {
+ this(String.valueOf(mcc), String.valueOf(mnc), lac, cid, cpid, uarfcn, null, null);
}
/**
@@ -82,22 +67,24 @@ public final class CellIdentityTdscdma extends CellIdentity {
* @param lac 16-bit Location Area Code, 0..65535, INT_MAX if unknown
* @param cid 28-bit UMTS Cell Identity described in TS 25.331, 0..268435455, INT_MAX if unknown
* @param cpid 8-bit Cell Parameters ID described in TS 25.331, 0..127, INT_MAX if unknown
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
* @param alphas short alpha Operator Name String or Enhanced Operator Name String
*
* @hide
*/
- public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid,
+ public CellIdentityTdscdma(String mcc, String mnc, int lac, int cid, int cpid, int uarfcn,
String alphal, String alphas) {
super(TAG, TYPE_TDSCDMA, mcc, mnc, alphal, alphas);
mLac = lac;
mCid = cid;
mCpid = cpid;
+ mUarfcn = uarfcn;
}
private CellIdentityTdscdma(CellIdentityTdscdma cid) {
this(cid.mMccStr, cid.mMncStr, cid.mLac, cid.mCid,
- cid.mCpid, cid.mAlphaLong, cid.mAlphaShort);
+ cid.mCpid, cid.mUarfcn, cid.mAlphaLong, cid.mAlphaShort);
}
CellIdentityTdscdma copy() {
@@ -141,9 +128,10 @@ public final class CellIdentityTdscdma extends CellIdentity {
return mCpid;
}
+ /** @hide */
@Override
- public int hashCode() {
- return Objects.hash(mLac, mCid, mCpid, super.hashCode());
+ public int getChannelNumber() {
+ return mUarfcn;
}
@Override
@@ -157,24 +145,29 @@ public final class CellIdentityTdscdma extends CellIdentity {
}
CellIdentityTdscdma o = (CellIdentityTdscdma) other;
- return TextUtils.equals(mMccStr, o.mMccStr)
- && TextUtils.equals(mMncStr, o.mMncStr)
- && mLac == o.mLac
+ return mLac == o.mLac
&& mCid == o.mCid
&& mCpid == o.mCpid
+ && mUarfcn == o.mUarfcn
&& super.equals(other);
}
@Override
+ public int hashCode() {
+ return Objects.hash(mLac, mCid, mCpid, mUarfcn, super.hashCode());
+ }
+
+ @Override
public String toString() {
return new StringBuilder(TAG)
.append(":{ mMcc=").append(mMccStr)
.append(" mMnc=").append(mMncStr)
+ .append(" mAlphaLong=").append(mAlphaLong)
+ .append(" mAlphaShort=").append(mAlphaShort)
.append(" mLac=").append(mLac)
.append(" mCid=").append(mCid)
.append(" mCpid=").append(mCpid)
- .append(" mAlphaLong=").append(mAlphaLong)
- .append(" mAlphaShort=").append(mAlphaShort)
+ .append(" mUarfcn=").append(mUarfcn)
.append("}").toString();
}
@@ -186,6 +179,7 @@ public final class CellIdentityTdscdma extends CellIdentity {
dest.writeInt(mLac);
dest.writeInt(mCid);
dest.writeInt(mCpid);
+ dest.writeInt(mUarfcn);
}
/** Construct from Parcel, type has already been processed */
@@ -194,7 +188,7 @@ public final class CellIdentityTdscdma extends CellIdentity {
mLac = in.readInt();
mCid = in.readInt();
mCpid = in.readInt();
-
+ mUarfcn = in.readInt();
if (DBG) log(toString());
}
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 984483edd8da..43f9406be79e 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -35,7 +35,7 @@ public final class CellIdentityWcdma extends CellIdentity {
private final int mCid;
// 9-bit UMTS Primary Scrambling Code described in TS 25.331, 0..511
private final int mPsc;
- // 16-bit UMTS Absolute RF Channel Number
+ // 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.4
private final int mUarfcn;
/**
@@ -70,7 +70,7 @@ public final class CellIdentityWcdma extends CellIdentity {
* @param lac 16-bit Location Area Code, 0..65535
* @param cid 28-bit UMTS Cell Identity
* @param psc 9-bit UMTS Primary Scrambling Code
- * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
*
* @hide
*/
@@ -83,7 +83,7 @@ public final class CellIdentityWcdma extends CellIdentity {
* @param lac 16-bit Location Area Code, 0..65535
* @param cid 28-bit UMTS Cell Identity
* @param psc 9-bit UMTS Primary Scrambling Code
- * @param uarfcn 16-bit UMTS Absolute RF Channel Number
+ * @param uarfcn 16-bit UMTS Absolute RF Channel Number described in TS 25.101 sec. 5.4.3
* @param mccStr 3-digit Mobile Country Code in string format
* @param mncStr 2 or 3-digit Mobile Network Code in string format
* @param alphal long alpha Operator Name String or Enhanced Operator Name String
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index 9232ed7167cc..3aab3fc9e199 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -19,6 +19,7 @@ package android.telephony;
import android.annotation.IntDef;
import android.os.Parcel;
import android.os.Parcelable;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -36,6 +37,8 @@ public abstract class CellInfo implements Parcelable {
protected static final int TYPE_LTE = 3;
/** @hide */
protected static final int TYPE_WCDMA = 4;
+ /** @hide */
+ protected static final int TYPE_TDCDMA = 5;
// Type to distinguish where time stamp gets recorded.
@@ -260,6 +263,7 @@ public abstract class CellInfo implements Parcelable {
case TYPE_CDMA: return CellInfoCdma.createFromParcelBody(in);
case TYPE_LTE: return CellInfoLte.createFromParcelBody(in);
case TYPE_WCDMA: return CellInfoWcdma.createFromParcelBody(in);
+ case TYPE_TDCDMA: return CellInfoTdscdma.createFromParcelBody(in);
default: throw new RuntimeException("Bad CellInfo Parcel");
}
}
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 6f2f1f677859..6403bc5a16a1 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -21,7 +21,7 @@ import android.os.Parcelable;
import android.telephony.Rlog;
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a CDMA cell that provides identity and measurement info.
*/
public final class CellInfoCdma extends CellInfo implements Parcelable {
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 1bedddb6b794..a3a9b315241e 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -21,7 +21,7 @@ import android.os.Parcelable;
import android.telephony.Rlog;
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a GSM cell that provides identity and measurement info.
*/
public final class CellInfoGsm extends CellInfo implements Parcelable {
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index 287c9f044a07..b892e89a8517 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -21,7 +21,7 @@ import android.os.Parcelable;
import android.telephony.Rlog;
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing an LTE cell that provides identity and measurement info.
*/
public final class CellInfoLte extends CellInfo implements Parcelable {
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
new file mode 100644
index 000000000000..7084c51f1b8a
--- /dev/null
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A {@link CellInfo} representing a TD-SCDMA cell that provides identity and measurement info.
+ *
+ * @hide
+ */
+public final class CellInfoTdscdma extends CellInfo implements Parcelable {
+
+ private static final String LOG_TAG = "CellInfoTdscdma";
+ private static final boolean DBG = false;
+
+ private CellIdentityTdscdma mCellIdentityTdscdma;
+ private CellSignalStrengthTdscdma mCellSignalStrengthTdscdma;
+
+ /** @hide */
+ public CellInfoTdscdma() {
+ super();
+ mCellIdentityTdscdma = new CellIdentityTdscdma();
+ mCellSignalStrengthTdscdma = new CellSignalStrengthTdscdma();
+ }
+
+ /** @hide */
+ public CellInfoTdscdma(CellInfoTdscdma ci) {
+ super(ci);
+ this.mCellIdentityTdscdma = ci.mCellIdentityTdscdma.copy();
+ this.mCellSignalStrengthTdscdma = ci.mCellSignalStrengthTdscdma.copy();
+ }
+
+ public CellIdentityTdscdma getCellIdentity() {
+ return mCellIdentityTdscdma;
+ }
+ /** @hide */
+ public void setCellIdentity(CellIdentityTdscdma cid) {
+ mCellIdentityTdscdma = cid;
+ }
+
+ public CellSignalStrengthTdscdma getCellSignalStrength() {
+ return mCellSignalStrengthTdscdma;
+ }
+ /** @hide */
+ public void setCellSignalStrength(CellSignalStrengthTdscdma css) {
+ mCellSignalStrengthTdscdma = css;
+ }
+
+ /**
+ * @return hash code
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), mCellIdentityTdscdma, mCellSignalStrengthTdscdma);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!super.equals(other)) {
+ return false;
+ }
+ try {
+ CellInfoTdscdma o = (CellInfoTdscdma) other;
+ return mCellIdentityTdscdma.equals(o.mCellIdentityTdscdma)
+ && mCellSignalStrengthTdscdma.equals(o.mCellSignalStrengthTdscdma);
+ } catch (ClassCastException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+
+ sb.append("CellInfoTdscdma:{");
+ sb.append(super.toString());
+ sb.append(" ").append(mCellIdentityTdscdma);
+ sb.append(" ").append(mCellSignalStrengthTdscdma);
+ sb.append("}");
+
+ return sb.toString();
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags, TYPE_TDCDMA);
+ mCellIdentityTdscdma.writeToParcel(dest, flags);
+ mCellSignalStrengthTdscdma.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Construct a CellInfoTdscdma object from the given parcel
+ * where the token is already been processed.
+ */
+ private CellInfoTdscdma(Parcel in) {
+ super(in);
+ mCellIdentityTdscdma = CellIdentityTdscdma.CREATOR.createFromParcel(in);
+ mCellSignalStrengthTdscdma = CellSignalStrengthTdscdma.CREATOR.createFromParcel(in);
+ }
+
+ /** Implement the Parcelable interface */
+ public static final Creator<CellInfoTdscdma> CREATOR = new Creator<CellInfoTdscdma>() {
+ @Override
+ public CellInfoTdscdma createFromParcel(Parcel in) {
+ in.readInt(); // Skip past token, we know what it is
+ return createFromParcelBody(in);
+ }
+
+ @Override
+ public CellInfoTdscdma[] newArray(int size) {
+ return new CellInfoTdscdma[size];
+ }
+ };
+
+ /** @hide */
+ protected static CellInfoTdscdma createFromParcelBody(Parcel in) {
+ return new CellInfoTdscdma(in);
+ }
+
+ /**
+ * log
+ */
+ private static void log(String s) {
+ Rlog.w(LOG_TAG, s);
+ }
+}
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index 06157022de34..005f3d341ec1 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -20,8 +20,10 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Rlog;
+import java.util.Objects;
+
/**
- * Immutable cell information from a point in time.
+ * A {@link CellInfo} representing a WCDMA cell that provides identity and measurement info.
*/
public final class CellInfoWcdma extends CellInfo implements Parcelable {
@@ -66,7 +68,7 @@ public final class CellInfoWcdma extends CellInfo implements Parcelable {
*/
@Override
public int hashCode() {
- return super.hashCode() + mCellIdentityWcdma.hashCode() + mCellSignalStrengthWcdma.hashCode();
+ return Objects.hash(super.hashCode(), mCellIdentityWcdma, mCellSignalStrengthWcdma);
}
@Override
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 183f96ddab0b..aa6b207d2f31 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -104,7 +104,10 @@ public final class CellSignalStrengthCdma extends CellSignalStrength implements
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index 8687cd1c454b..cff159b991c0 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -82,7 +82,10 @@ public final class CellSignalStrengthGsm extends CellSignalStrength implements P
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 2b6928e2e4f1..2f059f412b64 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -86,7 +86,10 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
new file mode 100644
index 000000000000..41859a3e96d9
--- /dev/null
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * Tdscdma signal strength related information.
+ *
+ * @hide
+ */
+public final class CellSignalStrengthTdscdma extends CellSignalStrength implements Parcelable {
+
+ private static final String LOG_TAG = "CellSignalStrengthTdscdma";
+ private static final boolean DBG = false;
+
+ private static final int TDSCDMA_SIGNAL_STRENGTH_GREAT = 12;
+ private static final int TDSCDMA_SIGNAL_STRENGTH_GOOD = 8;
+ private static final int TDSCDMA_SIGNAL_STRENGTH_MODERATE = 5;
+
+ private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
+ // or Integer.MAX_VALUE if unknown
+ private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+ // Integer.MAX_VALUE if unknown
+ private int mRscp; // Pilot power (0-96, 255) as defined in TS 27.007 8.69 or Integer.MAX_VALUE
+ // if unknown
+
+ /** @hide */
+ public CellSignalStrengthTdscdma() {
+ setDefaultValues();
+ }
+
+ /** @hide */
+ public CellSignalStrengthTdscdma(int ss, int ber, int rscp) {
+ mSignalStrength = ss;
+ mBitErrorRate = ber;
+ mRscp = rscp;
+ }
+
+ /** @hide */
+ public CellSignalStrengthTdscdma(CellSignalStrengthTdscdma s) {
+ copyFrom(s);
+ }
+
+ /** @hide */
+ protected void copyFrom(CellSignalStrengthTdscdma s) {
+ mSignalStrength = s.mSignalStrength;
+ mBitErrorRate = s.mBitErrorRate;
+ mRscp = s.mRscp;
+ }
+
+ /** @hide */
+ @Override
+ public CellSignalStrengthTdscdma copy() {
+ return new CellSignalStrengthTdscdma(this);
+ }
+
+ /** @hide */
+ @Override
+ public void setDefaultValues() {
+ mSignalStrength = Integer.MAX_VALUE;
+ mBitErrorRate = Integer.MAX_VALUE;
+ mRscp = Integer.MAX_VALUE;
+ }
+
+ /**
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
+ */
+ @Override
+ public int getLevel() {
+ int level;
+
+ // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+ // asu = 0 (-113dB or less) is very weak
+ // signal, its better to show 0 bars to the user in such cases.
+ // asu = 99 is a special case, where the signal strength is unknown.
+ int asu = mSignalStrength;
+ if (asu <= 2 || asu == 99) {
+ level = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_GREAT) {
+ level = SIGNAL_STRENGTH_GREAT;
+ } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_GOOD) {
+ level = SIGNAL_STRENGTH_GOOD;
+ } else if (asu >= TDSCDMA_SIGNAL_STRENGTH_MODERATE) {
+ level = SIGNAL_STRENGTH_MODERATE;
+ } else {
+ level = SIGNAL_STRENGTH_POOR;
+ }
+ if (DBG) log("getLevel=" + level);
+ return level;
+ }
+
+ /**
+ * Get the signal strength as dBm
+ */
+ @Override
+ public int getDbm() {
+ int dBm;
+
+ int level = mSignalStrength;
+ int asu = (level == 99 ? Integer.MAX_VALUE : level);
+ if (asu != Integer.MAX_VALUE) {
+ dBm = -113 + (2 * asu);
+ } else {
+ dBm = Integer.MAX_VALUE;
+ }
+ if (DBG) log("getDbm=" + dBm);
+ return dBm;
+ }
+
+ /**
+ * Get the signal level as an asu value between 0..31, 99 is unknown
+ * Asu is calculated based on 3GPP RSRP. Refer to 3GPP 27.007 (Ver 10.3.0) Sec 8.69
+ */
+ @Override
+ public int getAsuLevel() {
+ // ASU ranges from 0 to 31 - TS 27.007 Sec 8.5
+ // asu = 0 (-113dB or less) is very weak
+ // signal, its better to show 0 bars to the user in such cases.
+ // asu = 99 is a special case, where the signal strength is unknown.
+ int level = mSignalStrength;
+ if (DBG) log("getAsuLevel=" + level);
+ return level;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mSignalStrength, mBitErrorRate);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ CellSignalStrengthTdscdma s;
+
+ try {
+ s = (CellSignalStrengthTdscdma) o;
+ } catch (ClassCastException ex) {
+ return false;
+ }
+
+ if (o == null) {
+ return false;
+ }
+
+ return mSignalStrength == s.mSignalStrength
+ && mBitErrorRate == s.mBitErrorRate
+ && mRscp == s.mRscp;
+ }
+
+ /**
+ * @return string representation.
+ */
+ @Override
+ public String toString() {
+ return "CellSignalStrengthTdscdma:"
+ + " ss=" + mSignalStrength
+ + " ber=" + mBitErrorRate
+ + " rscp=" + mRscp;
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (DBG) log("writeToParcel(Parcel, int): " + toString());
+ dest.writeInt(mSignalStrength);
+ dest.writeInt(mBitErrorRate);
+ dest.writeInt(mRscp);
+ }
+
+ /**
+ * Construct a SignalStrength object from the given parcel
+ * where the token is already been processed.
+ */
+ private CellSignalStrengthTdscdma(Parcel in) {
+ mSignalStrength = in.readInt();
+ mBitErrorRate = in.readInt();
+ mRscp = in.readInt();
+ if (DBG) log("CellSignalStrengthTdscdma(Parcel): " + toString());
+ }
+
+ /** Implement the Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Implement the Parcelable interface */
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<CellSignalStrengthTdscdma> CREATOR =
+ new Parcelable.Creator<CellSignalStrengthTdscdma>() {
+ @Override
+ public CellSignalStrengthTdscdma createFromParcel(Parcel in) {
+ return new CellSignalStrengthTdscdma(in);
+ }
+
+ @Override
+ public CellSignalStrengthTdscdma[] newArray(int size) {
+ return new CellSignalStrengthTdscdma[size];
+ }
+ };
+
+ /**
+ * log
+ */
+ private static void log(String s) {
+ Rlog.w(LOG_TAG, s);
+ }
+}
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index dd32a960db91..21cf0be96931 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -35,7 +35,13 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
private static final int WCDMA_SIGNAL_STRENGTH_MODERATE = 5;
private int mSignalStrength; // in ASU; Valid values are (0-31, 99) as defined in TS 27.007 8.5
- private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5
+ // or Integer.MAX_VALUE if unknown
+ private int mBitErrorRate; // bit error rate (0-7, 99) as defined in TS 27.007 8.5 or
+ // Integer.MAX_VALUE if unknown
+ private int mRscp; // bit error rate (0-96, 255) as defined in TS 27.007 8.69 or
+ // Integer.MAX_VALUE if unknown
+ private int mEcNo; // signal to noise radio (0-49, 255) as defined in TS 27.007 8.69 or
+ // Integer.MAX_VALUE if unknown
/** @hide */
public CellSignalStrengthWcdma() {
@@ -43,9 +49,11 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
}
/** @hide */
- public CellSignalStrengthWcdma(int ss, int ber) {
+ public CellSignalStrengthWcdma(int ss, int ber, int rscp, int ecno) {
mSignalStrength = ss;
mBitErrorRate = ber;
+ mRscp = rscp;
+ mEcNo = ecno;
}
/** @hide */
@@ -57,6 +65,8 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
protected void copyFrom(CellSignalStrengthWcdma s) {
mSignalStrength = s.mSignalStrength;
mBitErrorRate = s.mBitErrorRate;
+ mRscp = s.mRscp;
+ mEcNo = s.mEcNo;
}
/** @hide */
@@ -70,10 +80,15 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
public void setDefaultValues() {
mSignalStrength = Integer.MAX_VALUE;
mBitErrorRate = Integer.MAX_VALUE;
+ mRscp = Integer.MAX_VALUE;
+ mEcNo = Integer.MAX_VALUE;
}
/**
- * Get signal level as an int from 0..4
+ * Retrieve an abstract level value for the overall signal strength.
+ *
+ * @return a single integer from 0 to 4 representing the general signal quality.
+ * 0 represents very poor signal strength while 4 represents a very strong signal strength.
*/
@Override
public int getLevel() {
@@ -145,7 +160,10 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
return false;
}
- return mSignalStrength == s.mSignalStrength && mBitErrorRate == s.mBitErrorRate;
+ return mSignalStrength == s.mSignalStrength
+ && mBitErrorRate == s.mBitErrorRate
+ && mRscp == s.mRscp
+ && mEcNo == s.mEcNo;
}
/**
@@ -155,7 +173,9 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
public String toString() {
return "CellSignalStrengthWcdma:"
+ " ss=" + mSignalStrength
- + " ber=" + mBitErrorRate;
+ + " ber=" + mBitErrorRate
+ + " rscp=" + mRscp
+ + " ecno=" + mEcNo;
}
/** Implement the Parcelable interface */
@@ -164,6 +184,8 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
if (DBG) log("writeToParcel(Parcel, int): " + toString());
dest.writeInt(mSignalStrength);
dest.writeInt(mBitErrorRate);
+ dest.writeInt(mRscp);
+ dest.writeInt(mEcNo);
}
/**
@@ -173,6 +195,8 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements
private CellSignalStrengthWcdma(Parcel in) {
mSignalStrength = in.readInt();
mBitErrorRate = in.readInt();
+ mRscp = in.readInt();
+ mEcNo = in.readInt();
if (DBG) log("CellSignalStrengthWcdma(Parcel): " + toString());
}
diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java
index bba779d0c175..e881549489a3 100644
--- a/telephony/java/android/telephony/NetworkRegistrationState.java
+++ b/telephony/java/android/telephony/NetworkRegistrationState.java
@@ -85,12 +85,12 @@ public class NetworkRegistrationState implements Parcelable {
public static final int SERVICE_TYPE_VIDEO = 4;
public static final int SERVICE_TYPE_EMERGENCY = 5;
- /** {@link AccessNetworkConstants.TransportType}*/
- private final int mTransportType;
-
@Domain
private final int mDomain;
+ /** {@link AccessNetworkConstants.TransportType}*/
+ private final int mTransportType;
+
@RegState
private final int mRegState;
@@ -112,19 +112,19 @@ public class NetworkRegistrationState implements Parcelable {
private DataSpecificRegistrationStates mDataSpecificStates;
/**
- * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
* @param domain Network domain. Must be DOMAIN_CS or DOMAIN_PS.
+ * @param transportType Transport type. Must be {@link AccessNetworkConstants.TransportType}
* @param regState Network registration state.
* @param accessNetworkTechnology See TelephonyManager NETWORK_TYPE_XXXX.
* @param reasonForDenial Reason for denial if the registration state is DENIED.
* @param availableServices The supported service.
* @param cellIdentity The identity representing a unique cell
*/
- public NetworkRegistrationState(int transportType, int domain, int regState,
+ public NetworkRegistrationState(int domain, int transportType, int regState,
int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
int[] availableServices, @Nullable CellIdentity cellIdentity) {
- mTransportType = transportType;
mDomain = domain;
+ mTransportType = transportType;
mRegState = regState;
mAccessNetworkTechnology = accessNetworkTechnology;
mReasonForDenial = reasonForDenial;
@@ -137,11 +137,11 @@ public class NetworkRegistrationState implements Parcelable {
* Constructor for voice network registration states.
* @hide
*/
- public NetworkRegistrationState(int transportType, int domain, int regState,
+ public NetworkRegistrationState(int domain, int transportType, int regState,
int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
int[] availableServices, @Nullable CellIdentity cellIdentity, boolean cssSupported,
int roamingIndicator, int systemIsInPrl, int defaultRoamingIndicator) {
- this(transportType, domain, regState, accessNetworkTechnology,
+ this(domain, transportType, regState, accessNetworkTechnology,
reasonForDenial, emergencyOnly, availableServices, cellIdentity);
mVoiceSpecificStates = new VoiceSpecificRegistrationStates(cssSupported, roamingIndicator,
@@ -152,18 +152,18 @@ public class NetworkRegistrationState implements Parcelable {
* Constructor for data network registration states.
* @hide
*/
- public NetworkRegistrationState(int transportType, int domain, int regState,
+ public NetworkRegistrationState(int domain, int transportType, int regState,
int accessNetworkTechnology, int reasonForDenial, boolean emergencyOnly,
int[] availableServices, @Nullable CellIdentity cellIdentity, int maxDataCalls) {
- this(transportType, domain, regState, accessNetworkTechnology,
+ this(domain, transportType, regState, accessNetworkTechnology,
reasonForDenial, emergencyOnly, availableServices, cellIdentity);
mDataSpecificStates = new DataSpecificRegistrationStates(maxDataCalls);
}
protected NetworkRegistrationState(Parcel source) {
- mTransportType = source.readInt();
mDomain = source.readInt();
+ mTransportType = source.readInt();
mRegState = source.readInt();
mAccessNetworkTechnology = source.readInt();
mReasonForDenial = source.readInt();
@@ -260,8 +260,8 @@ public class NetworkRegistrationState implements Parcelable {
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationState{")
- .append("transportType=").append(mTransportType)
.append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS")
+ .append("transportType=").append(mTransportType)
.append(" regState=").append(regStateToString(mRegState))
.append(" accessNetworkTechnology=")
.append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
@@ -290,8 +290,8 @@ public class NetworkRegistrationState implements Parcelable {
}
NetworkRegistrationState other = (NetworkRegistrationState) o;
- return mTransportType == other.mTransportType
- && mDomain == other.mDomain
+ return mDomain == other.mDomain
+ && mTransportType == other.mTransportType
&& mRegState == other.mRegState
&& mAccessNetworkTechnology == other.mAccessNetworkTechnology
&& mReasonForDenial == other.mReasonForDenial
@@ -305,8 +305,8 @@ public class NetworkRegistrationState implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mTransportType);
dest.writeInt(mDomain);
+ dest.writeInt(mTransportType);
dest.writeInt(mRegState);
dest.writeInt(mAccessNetworkTechnology);
dest.writeInt(mReasonForDenial);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index ae999c31a42a..9e8529e20a7b 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1569,13 +1569,14 @@ public class ServiceState implements Parcelable {
/**
* Get the network registration states with given transport type and domain.
*
+ * @param domain The network domain. Must be {@link NetworkRegistrationState#DOMAIN_CS} or
+ * {@link NetworkRegistrationState#DOMAIN_PS}.
* @param transportType The transport type. See {@link AccessNetworkConstants.TransportType}
- * @param domain The network domain. Must be DOMAIN_CS or DOMAIN_PS.
* @return The matching NetworkRegistrationState.
* @hide
*/
@SystemApi
- public NetworkRegistrationState getNetworkRegistrationStates(int transportType, int domain) {
+ public NetworkRegistrationState getNetworkRegistrationStates(int domain, int transportType) {
synchronized (mNetworkRegistrationStates) {
for (NetworkRegistrationState networkRegistrationState : mNetworkRegistrationStates) {
if (networkRegistrationState.getTransportType() == transportType
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 936505ca407d..d76e39b83801 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -33,9 +33,9 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.DisplayMetrics;
+import android.util.Log;
import java.util.Arrays;
-import java.util.ArrayList;
import java.util.List;
/**
@@ -105,12 +105,12 @@ public class SubscriptionInfo implements Parcelable {
/**
* Mobile Country Code
*/
- private int mMcc;
+ private String mMcc;
/**
* Mobile Network Code
*/
- private int mMnc;
+ private String mMnc;
/**
* ISO Country code for the subscription's provider
@@ -138,11 +138,11 @@ public class SubscriptionInfo implements Parcelable {
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
- CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
- Bitmap icon, int mcc, int mnc, String countryIso) {
+ CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
+ Bitmap icon, String mcc, String mnc, String countryIso) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
- roaming, icon, mcc, mnc, countryIso, false /* isEmbedded */,
- null /* accessRules */, null /* accessRules */);
+ roaming, icon, mcc, mnc, countryIso, false /* isEmbedded */,
+ null /* accessRules */, null /* accessRules */);
}
/**
@@ -150,7 +150,7 @@ public class SubscriptionInfo implements Parcelable {
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
- Bitmap icon, int mcc, int mnc, String countryIso, boolean isEmbedded,
+ Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, null /* cardId */);
@@ -161,7 +161,7 @@ public class SubscriptionInfo implements Parcelable {
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
- Bitmap icon, int mcc, int mnc, String countryIso, boolean isEmbedded,
+ Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardId) {
this.mId = id;
this.mIccId = iccId;
@@ -316,15 +316,43 @@ public class SubscriptionInfo implements Parcelable {
/**
* @return the MCC.
+ * @deprecated Use {@link #getMccString()} instead.
*/
+ @Deprecated
public int getMcc() {
- return this.mMcc;
+ try {
+ return this.mMcc == null ? 0 : Integer.valueOf(this.mMcc);
+ } catch (NumberFormatException e) {
+ Log.w(SubscriptionInfo.class.getSimpleName(), "MCC string is not a number");
+ return 0;
+ }
}
/**
* @return the MNC.
+ * @deprecated Use {@link #getMncString()} instead.
*/
+ @Deprecated
public int getMnc() {
+ try {
+ return this.mMnc == null ? 0 : Integer.valueOf(this.mMnc);
+ } catch (NumberFormatException e) {
+ Log.w(SubscriptionInfo.class.getSimpleName(), "MNC string is not a number");
+ return 0;
+ }
+ }
+
+ /**
+ * @return The MCC, as a string.
+ */
+ public String getMccString() {
+ return this.mMcc;
+ }
+
+ /**
+ * @return The MNC, as a string.
+ */
+ public String getMncString() {
return this.mMnc;
}
@@ -425,8 +453,8 @@ public class SubscriptionInfo implements Parcelable {
int iconTint = source.readInt();
String number = source.readString();
int dataRoaming = source.readInt();
- int mcc = source.readInt();
- int mnc = source.readInt();
+ String mcc = source.readString();
+ String mnc = source.readString();
String countryIso = source.readString();
Bitmap iconBitmap = Bitmap.CREATOR.createFromParcel(source);
boolean isEmbedded = source.readBoolean();
@@ -455,8 +483,8 @@ public class SubscriptionInfo implements Parcelable {
dest.writeInt(mIconTint);
dest.writeString(mNumber);
dest.writeInt(mDataRoaming);
- dest.writeInt(mMcc);
- dest.writeInt(mMnc);
+ dest.writeString(mMcc);
+ dest.writeString(mMnc);
dest.writeString(mCountryIso);
mIconBitmap.writeToParcel(dest, flags);
dest.writeBoolean(mIsEmbedded);
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ece646ca7b2c..17e7c49f6289 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -253,6 +253,20 @@ public class SubscriptionManager {
public static final int SIM_PROVISIONED = 0;
/**
+ * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
+ * <P>Type: TEXT (String)</P>
+ * @hide
+ */
+ public static final String MCC_STRING = "mcc_string";
+
+ /**
+ * TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
+ * <P>Type: TEXT (String)</P>
+ * @hide
+ */
+ public static final String MNC_STRING = "mnc_string";
+
+ /**
* TelephonyProvider column name for the MCC associated with a SIM.
* <P>Type: INTEGER (int)</P>
* @hide
diff --git a/test-base/Android.bp b/test-base/Android.bp
index a0e39856e9c7..d25b47727c0b 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -21,6 +21,7 @@
// Also contains the com.android.internal.util.Predicate[s] classes.
java_library {
name: "android.test.base",
+ installable: true,
srcs: ["src/**/*.java"],
@@ -42,6 +43,7 @@ java_library {
// Also contains the com.android.internal.util.Predicate[s] classes.
java_library {
name: "legacy-test",
+ installable: true,
sdk_version: "current",
static_libs: ["android.test.base"],
@@ -115,4 +117,5 @@ java_library_static {
},
},
sdk_version: "current",
+ compile_dex: true,
}
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 51fa86bacc75..8d3faaef9f6b 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -18,6 +18,7 @@
// ===================================
java_library {
name: "android.test.mock",
+ installable: true,
java_version: "1.8",
srcs: ["src/**/*.java"],
@@ -91,6 +92,7 @@ java_library_static {
enabled: false,
},
},
+ compile_dex: true,
}
java_library_static {
@@ -104,4 +106,5 @@ java_library_static {
enabled: false,
},
},
+ compile_dex: true,
}
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index b50ba3b58275..2caa6c45f16b 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -18,6 +18,7 @@
// =====================================
java_library {
name: "android.test.runner",
+ installable: true,
// Needs to be consistent with the repackaged version of this make target.
java_version: "1.8",
@@ -120,4 +121,5 @@ java_library_static {
},
},
sdk_version: "current",
+ compile_dex: true,
}
diff --git a/vr/Android.bp b/vr/Android.bp
index b5904d6f35ab..775ec968f59d 100644
--- a/vr/Android.bp
+++ b/vr/Android.bp
@@ -26,6 +26,7 @@ cc_library_shared {
// Java platform library for vr stuff.
java_library {
name: "com.google.vr.platform",
+ installable: true,
owner: "google",
required: [
"libdvr_loader",