summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/media/framework/java/android/media/MediaParser.java29
-rw-r--r--apex/statsd/aidl/android/os/IStatsd.aidl6
-rw-r--r--apex/statsd/framework/java/android/util/StatsLog.java99
-rw-r--r--api/current.txt6
-rw-r--r--api/test-current.txt6
-rw-r--r--cmds/screencap/screencap.cpp2
-rw-r--r--cmds/statsd/Android.bp4
-rw-r--r--cmds/statsd/src/FieldValue.h12
-rw-r--r--cmds/statsd/src/StatsLogProcessor.cpp42
-rw-r--r--cmds/statsd/src/StatsLogProcessor.h5
-rw-r--r--cmds/statsd/src/StatsService.cpp24
-rw-r--r--cmds/statsd/src/StatsService.h12
-rw-r--r--cmds/statsd/src/external/StatsCallbackPuller.cpp10
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.cpp46
-rw-r--r--cmds/statsd/src/guardrail/StatsdStats.h20
-rw-r--r--cmds/statsd/src/logd/LogEvent.cpp17
-rw-r--r--cmds/statsd/src/logd/LogEvent.h33
-rw-r--r--cmds/statsd/src/main.cpp6
-rw-r--r--cmds/statsd/src/metrics/CountMetricProducer.h5
-rw-r--r--cmds/statsd/src/metrics/DurationMetricProducer.h10
-rw-r--r--cmds/statsd/src/metrics/GaugeMetricProducer.h22
-rw-r--r--cmds/statsd/src/metrics/MetricProducer.h32
-rw-r--r--cmds/statsd/src/metrics/MetricsManager.cpp15
-rw-r--r--cmds/statsd/src/metrics/MetricsManager.h2
-rw-r--r--cmds/statsd/src/metrics/ValueMetricProducer.h26
-rw-r--r--cmds/statsd/src/state/StateTracker.cpp2
-rw-r--r--cmds/statsd/src/stats_log.proto2
-rw-r--r--cmds/statsd/src/stats_log_util.cpp3
-rw-r--r--cmds/statsd/src/utils/MultiConditionTrigger.cpp (renamed from cmds/statsd/src/utils/NamedLatch.cpp)37
-rw-r--r--cmds/statsd/src/utils/MultiConditionTrigger.h55
-rw-r--r--cmds/statsd/src/utils/NamedLatch.h58
-rw-r--r--cmds/statsd/tests/LogEvent_test.cpp2
-rw-r--r--cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp134
-rw-r--r--cmds/statsd/tests/guardrail/StatsdStats_test.cpp35
-rw-r--r--cmds/statsd/tests/metrics/CountMetricProducer_test.cpp61
-rw-r--r--cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp174
-rw-r--r--cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp57
-rw-r--r--cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp355
-rw-r--r--cmds/statsd/tests/statsd_test_util.h2
-rw-r--r--cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp174
-rw-r--r--cmds/statsd/tests/utils/NamedLatch_test.cpp96
-rw-r--r--core/java/android/accessibilityservice/AccessibilityService.java7
-rw-r--r--core/java/android/accessibilityservice/AccessibilityShortcutInfo.java8
-rw-r--r--core/java/android/app/ActivityTaskManager.java12
-rw-r--r--core/java/android/app/Notification.java24
-rw-r--r--core/java/android/app/NotificationChannel.java39
-rw-r--r--core/java/android/app/TaskInfo.java15
-rw-r--r--core/java/android/content/ClipData.java4
-rw-r--r--core/java/android/content/ContentProviderOperation.java16
-rw-r--r--core/java/android/content/Intent.java28
-rw-r--r--core/java/android/content/pm/ActivityInfo.java28
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java96
-rw-r--r--core/java/android/content/pm/ComponentInfo.java8
-rw-r--r--core/java/android/content/pm/FeatureInfo.java4
-rw-r--r--core/java/android/content/pm/IDataLoaderStatusListener.aidl15
-rw-r--r--core/java/android/content/pm/InstrumentationInfo.java44
-rw-r--r--core/java/android/content/pm/PackageInfo.java32
-rw-r--r--core/java/android/content/pm/PackageItemInfo.java8
-rw-r--r--core/java/android/content/pm/PackageParser.java42
-rw-r--r--core/java/android/content/pm/PackageParserCacheHelper.java22
-rw-r--r--core/java/android/content/pm/ProviderInfo.java12
-rw-r--r--core/java/android/content/pm/ServiceInfo.java4
-rw-r--r--core/java/android/content/pm/parsing/ApkLiteParseUtils.java2
-rw-r--r--core/java/android/content/pm/parsing/ParsingPackageUtils.java21
-rw-r--r--core/java/android/database/sqlite/SQLiteConnection.java33
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java12
-rw-r--r--core/java/android/net/Uri.java20
-rw-r--r--core/java/android/os/Parcel.java75
-rw-r--r--core/java/android/os/VibrationEffect.java28
-rw-r--r--core/java/android/os/storage/StorageManager.java7
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java8
-rw-r--r--core/java/android/text/TextUtils.java8
-rw-r--r--core/java/android/view/IScrollCaptureClient.aidl49
-rw-r--r--core/java/android/view/IScrollCaptureController.aidl63
-rw-r--r--core/java/android/view/IWindow.aidl16
-rw-r--r--core/java/android/view/IWindowManager.aidl15
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java2
-rw-r--r--core/java/android/view/ScrollCaptureCallback.java151
-rw-r--r--core/java/android/view/ScrollCaptureClient.java312
-rw-r--r--core/java/android/view/ScrollCaptureSession.java105
-rw-r--r--core/java/android/view/ScrollCaptureTarget.java135
-rw-r--r--core/java/android/view/ScrollCaptureTargetResolver.java387
-rw-r--r--core/java/android/view/View.java184
-rw-r--r--core/java/android/view/ViewGroup.java128
-rw-r--r--core/java/android/view/ViewRootImpl.java150
-rw-r--r--core/java/android/view/Window.java27
-rw-r--r--core/java/android/view/WindowManager.java41
-rw-r--r--core/java/android/view/WindowlessWindowManager.java1
-rw-r--r--core/java/android/widget/RemoteViews.java12
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java5
-rw-r--r--core/java/com/android/internal/app/IntentForwarderActivity.java99
-rw-r--r--core/java/com/android/internal/os/BatteryStatsImpl.java3
-rw-r--r--core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java8
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java33
-rw-r--r--core/java/com/android/internal/util/ScreenshotHelper.java177
-rw-r--r--core/java/com/android/internal/view/BaseIWindow.java10
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureInternal.java117
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureViewHelper.java87
-rw-r--r--core/java/com/android/internal/view/ScrollCaptureViewSupport.java239
-rw-r--r--core/java/com/android/internal/view/ScrollViewCaptureHelper.java167
-rw-r--r--core/jni/AndroidRuntime.cpp3
-rw-r--r--core/jni/android_os_Parcel.cpp47
-rw-r--r--core/jni/com_android_internal_os_Zygote.cpp5
-rw-r--r--core/res/AndroidManifest.xml5
-rw-r--r--core/res/res/layout/resolver_empty_states.xml1
-rw-r--r--core/res/res/values/attrs.xml15
-rw-r--r--core/res/res/values/public.xml2
-rw-r--r--core/res/res/xml/default_zen_mode_config.xml4
-rw-r--r--core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java72
-rw-r--r--core/tests/coretests/src/android/os/ParcelTest.java23
-rw-r--r--core/tests/coretests/src/android/os/VibrationEffectTest.java102
-rw-r--r--core/tests/coretests/src/android/view/ScrollCaptureClientTest.java309
-rw-r--r--core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java498
-rw-r--r--core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java480
-rw-r--r--core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java18
-rw-r--r--core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java352
-rw-r--r--core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java4
-rw-r--r--data/etc/services.core.protolog.json18
-rw-r--r--data/keyboards/Vendor_18d1_Product_0200.kcm48
-rw-r--r--data/keyboards/Vendor_18d1_Product_0200.kl71
-rw-r--r--graphics/java/android/graphics/SurfaceTexture.java52
-rw-r--r--identity/java/android/security/identity/IdentityCredential.java69
-rw-r--r--identity/java/android/security/identity/IdentityCredentialStore.java35
-rw-r--r--identity/java/android/security/identity/ResultData.java43
-rw-r--r--identity/java/android/security/identity/WritableIdentityCredential.java15
-rw-r--r--media/java/android/media/AudioPortEventHandler.java3
-rw-r--r--media/java/android/media/IMediaRouter2.aidl6
-rw-r--r--media/java/android/media/IMediaRouterService.aidl2
-rw-r--r--media/java/android/media/MediaCas.java20
-rw-r--r--media/java/android/media/MediaFormat.java14
-rw-r--r--media/java/android/media/MediaRouter2.java49
-rw-r--r--media/java/android/media/MediaRouter2Manager.java16
-rw-r--r--media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java3
-rw-r--r--media/java/android/media/tv/ITvRemoteServiceInput.aidl8
-rw-r--r--media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl10
-rw-r--r--media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java15
-rw-r--r--media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java163
-rw-r--r--media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java48
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java105
-rw-r--r--media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java4
-rw-r--r--packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml2
-rw-r--r--packages/CarSystemUI/res/layout/car_qs_footer.xml83
-rw-r--r--packages/CarSystemUI/res/layout/car_qs_panel.xml43
-rw-r--r--packages/CarSystemUI/res/layout/car_status_bar_header.xml4
-rw-r--r--packages/CarSystemUI/res/layout/car_top_navigation_bar.xml4
-rw-r--r--packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml4
-rw-r--r--packages/CarSystemUI/res/values/colors.xml1
-rw-r--r--packages/CarSystemUI/res/values/colors_car.xml28
-rw-r--r--packages/CarSystemUI/res/values/config.xml20
-rw-r--r--packages/CarSystemUI/res/values/dimens.xml26
-rw-r--r--packages/CarSystemUI/res/values/dimens_car.xml45
-rw-r--r--packages/CarSystemUI/res/values/ids.xml (renamed from packages/CarSystemUI/res/values/ids_car.xml)0
-rw-r--r--packages/CarSystemUI/res/values/integers.xml16
-rw-r--r--packages/CarSystemUI/res/values/integers_car.xml34
-rw-r--r--packages/CarSystemUI/res/values/strings.xml12
-rw-r--r--packages/CarSystemUI/res/values/strings_car.xml32
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java (renamed from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java)7
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java (renamed from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java)4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java (renamed from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java)14
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java (renamed from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java)6
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java (renamed from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java)10
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java (renamed from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java)4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java (renamed from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java)4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java6
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java16
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java (renamed from packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java)2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarStatusBarHeader.java (renamed from packages/CarSystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java)4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java (renamed from packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java)2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java4
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java2
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFooter.java139
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java274
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java3
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java6
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java12
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java (renamed from packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java)4
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java13
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java (renamed from packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java)2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java (renamed from packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java)8
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java2
-rw-r--r--packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java6
-rw-r--r--packages/CtsShim/build/Android.bp24
-rw-r--r--packages/CtsShim/build/jni/Android.bp9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java62
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java9
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java29
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java33
-rw-r--r--packages/SystemUI/res/drawable/bubble_manage_menu_row.xml21
-rw-r--r--packages/SystemUI/res/layout/bubble_manage_menu.xml99
-rw-r--r--packages/SystemUI/res/layout/controls_management.xml1
-rw-r--r--packages/SystemUI/res/values/dimens.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java7
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java216
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt180
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java316
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java259
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java146
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java156
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java289
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt92
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java101
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java52
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java162
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java3
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java2
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java28
-rw-r--r--services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java9
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java87
-rw-r--r--services/core/java/android/content/pm/PackageManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/AlarmManagerService.java3
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java4
-rw-r--r--services/core/java/com/android/server/VibratorService.java3
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java56
-rw-r--r--services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java103
-rw-r--r--services/core/java/com/android/server/media/BluetoothRouteProvider.java3
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java112
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java8
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider.java20
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java6
-rw-r--r--services/core/java/com/android/server/notification/ShortcutHelper.java41
-rw-r--r--services/core/java/com/android/server/pm/AppsFilter.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java32
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java144
-rw-r--r--services/core/java/com/android/server/pm/Settings.java14
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java7
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java51
-rw-r--r--services/core/java/com/android/server/pm/UserSystemPackageInstaller.java153
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageCacher.java15
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java12
-rw-r--r--services/core/java/com/android/server/power/AttentionDetector.java4
-rw-r--r--services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java80
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java18
-rw-r--r--services/core/java/com/android/server/tv/TvRemoteServiceInput.java111
-rw-r--r--services/core/java/com/android/server/tv/UinputBridge.java51
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java152
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java28
-rw-r--r--services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java285
-rw-r--r--services/core/java/com/android/server/wm/AccessibilityController.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java64
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java9
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java43
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java15
-rw-r--r--services/core/java/com/android/server/wm/DockedStackDividerController.java5
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java6
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java6
-rw-r--r--services/core/java/com/android/server/wm/ShellRoot.java24
-rw-r--r--services/core/java/com/android/server/wm/Task.java164
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java41
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java73
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java2
-rw-r--r--services/core/jni/com_android_server_tv_GamepadKeys.h131
-rw-r--r--services/core/jni/com_android_server_tv_TvUinputBridge.cpp133
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java64
-rw-r--r--services/incremental/IncrementalService.cpp2
-rw-r--r--services/people/java/com/android/server/people/data/DataManager.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java31
-rw-r--r--services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java252
-rw-r--r--services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java219
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java12
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java58
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestIWindow.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java27
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java2
-rwxr-xr-xtelecomm/java/android/telecom/Connection.java25
-rwxr-xr-xtelecomm/java/android/telecom/ConnectionService.java44
-rw-r--r--telecomm/java/android/telecom/Logging/Session.java2
-rw-r--r--telecomm/java/android/telecom/RemoteConnectionService.java20
-rw-r--r--telephony/java/android/telephony/ims/ImsRcsManager.java24
-rw-r--r--telephony/java/com/android/internal/telephony/SmsHeader.java82
-rw-r--r--tests/AutoVerify/app1/Android.bp11
-rw-r--r--tests/AutoVerify/app1/AndroidManifest.xml43
-rw-r--r--tests/AutoVerify/app1/res/values/strings.xml21
-rw-r--r--tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java15
-rw-r--r--tests/AutoVerify/app2/Android.bp11
-rw-r--r--tests/AutoVerify/app2/AndroidManifest.xml44
-rw-r--r--tests/AutoVerify/app2/res/values/strings.xml21
-rw-r--r--tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java15
-rw-r--r--tests/AutoVerify/app3/Android.bp11
-rw-r--r--tests/AutoVerify/app3/AndroidManifest.xml44
-rw-r--r--tests/AutoVerify/app3/res/values/strings.xml21
-rw-r--r--tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java15
-rw-r--r--tests/AutoVerify/app4/Android.bp11
-rw-r--r--tests/AutoVerify/app4/AndroidManifest.xml45
-rw-r--r--tests/AutoVerify/app4/res/values/strings.xml21
-rw-r--r--tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java15
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java7
381 files changed, 13321 insertions, 3684 deletions
diff --git a/apex/media/framework/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
index d1e9e4e4289b..50f4ddd5e2e3 100644
--- a/apex/media/framework/java/android/media/MediaParser.java
+++ b/apex/media/framework/java/android/media/MediaParser.java
@@ -1412,14 +1412,12 @@ public final class MediaParser {
setOptionalMediaFormatInt(result, MediaFormat.KEY_HEIGHT, format.height);
List<byte[]> initData = format.initializationData;
- if (initData != null) {
- for (int i = 0; i < initData.size(); i++) {
- result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
- }
+ for (int i = 0; i < initData.size(); i++) {
+ result.setByteBuffer("csd-" + i, ByteBuffer.wrap(initData.get(i)));
}
+ setPcmEncoding(format, result);
setOptionalMediaFormatString(result, MediaFormat.KEY_LANGUAGE, format.language);
setOptionalMediaFormatInt(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
- setOptionalMediaFormatInt(result, MediaFormat.KEY_PCM_ENCODING, format.pcmEncoding);
setOptionalMediaFormatInt(result, MediaFormat.KEY_ROTATION, format.rotationDegrees);
setOptionalMediaFormatInt(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
setOptionalMediaFormatInt(
@@ -1462,6 +1460,27 @@ public final class MediaParser {
return result;
}
+ private static void setPcmEncoding(Format format, MediaFormat result) {
+ int exoPcmEncoding = format.pcmEncoding;
+ setOptionalMediaFormatInt(result, "exo-pcm-encoding", format.pcmEncoding);
+ int mediaFormatPcmEncoding;
+ switch (exoPcmEncoding) {
+ case C.ENCODING_PCM_8BIT:
+ mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT;
+ break;
+ case C.ENCODING_PCM_16BIT:
+ mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_16BIT;
+ break;
+ case C.ENCODING_PCM_FLOAT:
+ mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT;
+ break;
+ default:
+ // No matching value. Do nothing.
+ return;
+ }
+ result.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding);
+ }
+
private static void setOptionalMediaFormatInt(MediaFormat mediaFormat, String key, int value) {
if (value != Format.NO_VALUE) {
mediaFormat.setInteger(key, value);
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index 80308d26a430..0d3f4208a2ab 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -182,12 +182,6 @@ interface IStatsd {
void unsetBroadcastSubscriber(long configId, long subscriberId, int callingUid);
/**
- * Apps can send an atom via this application breadcrumb with the specified label and state for
- * this label. This allows building custom metrics and predicates.
- */
- void sendAppBreadcrumbAtom(int label, int state);
-
- /**
* Tell the stats daemon that all the pullers registered during boot have been sent.
*/
oneway void allPullersFromBootRegistered();
diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java
index 536b71a0e625..4eeae57fe195 100644
--- a/apex/statsd/framework/java/android/util/StatsLog.java
+++ b/apex/statsd/framework/java/android/util/StatsLog.java
@@ -25,8 +25,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.IStatsd;
-import android.os.RemoteException;
-import android.os.StatsFrameworkInitializer;
+import android.os.Process;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.StatsdStatsLog;
@@ -45,10 +44,6 @@ public final class StatsLog {
private static final boolean DEBUG = false;
private static final int EXPERIMENT_IDS_FIELD_ID = 1;
- private static IStatsd sService;
-
- private static Object sLogLock = new Object();
-
private StatsLog() {
}
@@ -59,26 +54,13 @@ public final class StatsLog {
* @return True if the log request was sent to statsd.
*/
public static boolean logStart(int label) {
- synchronized (sLogLock) {
- try {
- IStatsd service = getIStatsdLocked();
- if (service == null) {
- if (DEBUG) {
- Log.d(TAG, "Failed to find statsd when logging start");
- }
- return false;
- }
- service.sendAppBreadcrumbAtom(label,
- StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
- return true;
- } catch (RemoteException e) {
- sService = null;
- if (DEBUG) {
- Log.d(TAG, "Failed to connect to statsd when logging start");
- }
- return false;
- }
- }
+ int callingUid = Process.myUid();
+ StatsdStatsLog.write(
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+ callingUid,
+ label,
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+ return true;
}
/**
@@ -88,26 +70,13 @@ public final class StatsLog {
* @return True if the log request was sent to statsd.
*/
public static boolean logStop(int label) {
- synchronized (sLogLock) {
- try {
- IStatsd service = getIStatsdLocked();
- if (service == null) {
- if (DEBUG) {
- Log.d(TAG, "Failed to find statsd when logging stop");
- }
- return false;
- }
- service.sendAppBreadcrumbAtom(
- label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
- return true;
- } catch (RemoteException e) {
- sService = null;
- if (DEBUG) {
- Log.d(TAG, "Failed to connect to statsd when logging stop");
- }
- return false;
- }
- }
+ int callingUid = Process.myUid();
+ StatsdStatsLog.write(
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+ callingUid,
+ label,
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+ return true;
}
/**
@@ -117,26 +86,13 @@ public final class StatsLog {
* @return True if the log request was sent to statsd.
*/
public static boolean logEvent(int label) {
- synchronized (sLogLock) {
- try {
- IStatsd service = getIStatsdLocked();
- if (service == null) {
- if (DEBUG) {
- Log.d(TAG, "Failed to find statsd when logging event");
- }
- return false;
- }
- service.sendAppBreadcrumbAtom(
- label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
- return true;
- } catch (RemoteException e) {
- sService = null;
- if (DEBUG) {
- Log.d(TAG, "Failed to connect to statsd when logging event");
- }
- return false;
- }
- }
+ int callingUid = Process.myUid();
+ StatsdStatsLog.write(
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+ callingUid,
+ label,
+ StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+ return true;
}
/**
@@ -181,17 +137,6 @@ public final class StatsLog {
return true;
}
- private static IStatsd getIStatsdLocked() throws RemoteException {
- if (sService != null) {
- return sService;
- }
- sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer
- .getStatsServiceManager()
- .getStatsdServiceRegisterer()
- .get());
- return sService;
- }
-
/**
* Write an event to stats log using the raw format.
*
diff --git a/api/current.txt b/api/current.txt
index 80e2d00b3e48..3f8bee9230a6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -48759,12 +48759,8 @@ package android.telephony.ims {
method public void onCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
}
- public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
- method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
- method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+ public class ImsRcsManager {
method @NonNull public android.telephony.ims.RcsUceAdapter getUceAdapter();
- method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
- method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
field public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN = "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN";
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 755380e93b2a..ca291f373be2 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -126,6 +126,7 @@ package android.app {
public class ActivityTaskManager {
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>);
+ method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks();
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
@@ -504,6 +505,11 @@ package android.app {
method public boolean isStatusBarExpansionDisabled();
}
+ public class TaskInfo {
+ method @NonNull public android.content.res.Configuration getConfiguration();
+ method @NonNull public android.window.WindowContainerToken getToken();
+ }
+
public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener {
method public android.widget.TimePicker getTimePicker();
}
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 4410f1c4570c..bb32dd2fa7ad 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -105,7 +105,7 @@ static status_t notifyMediaScanner(const char* fileName) {
char *cmd[] = {
(char*) "am",
(char*) "broadcast",
- (char*) "am",
+ (char*) "-a",
(char*) "android.intent.action.MEDIA_SCANNER_SCAN_FILE",
(char*) "-d",
&filePath[0],
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index acd9ec3be210..0e49d187a3b5 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -104,7 +104,7 @@ cc_defaults {
"src/subscriber/IncidentdReporter.cpp",
"src/subscriber/SubscriberReporter.cpp",
"src/uid_data.proto",
- "src/utils/NamedLatch.cpp",
+ "src/utils/MultiConditionTrigger.cpp",
],
local_include_dirs: [
@@ -366,7 +366,7 @@ cc_test {
"tests/StatsService_test.cpp",
"tests/storage/StorageManager_test.cpp",
"tests/UidMap_test.cpp",
- "tests/utils/NamedLatch_test.cpp",
+ "tests/utils/MultiConditionTrigger_test.cpp",
],
static_libs: [
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index e251399776fb..ba4cf11b84f1 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -382,10 +382,6 @@ public:
inline void setUidField(bool isUid) { setBitmaskAtPos(UID_POS, isUid); }
- inline void setResetState(int32_t resetState) {
- mResetState = resetState;
- }
-
// Default value = false
inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
@@ -398,12 +394,6 @@ public:
// Default value = false
inline bool isUidField() const { return getValueFromBitmask(UID_POS); }
- // If a reset state is not sent in the StatsEvent, returns -1. Note that a
- // reset satate is only sent if and only if a reset should be triggered.
- inline int32_t getResetState() const {
- return mResetState;
- }
-
private:
inline void setBitmaskAtPos(int pos, bool value) {
mBooleanBitmask &= ~(1 << pos); // clear
@@ -417,8 +407,6 @@ private:
// This is a bitmask over all annotations stored in boolean form. Because
// there are only 4 booleans, just one byte is required.
uint8_t mBooleanBitmask = 0;
-
- int32_t mResetState = -1;
};
/**
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index d914ab2436c7..cc48d50ebd50 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -389,15 +389,24 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) {
void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
+ // Tell StatsdStats about new event
+ const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
+ int atomId = event->GetTagId();
+ StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC);
+ if (!event->isValid()) {
+ StatsdStats::getInstance().noteAtomError(atomId);
+ return;
+ }
+
// Hard-coded logic to update train info on disk and fill in any information
// this log event may be missing.
- if (event->GetTagId() == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
+ if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
onBinaryPushStateChangedEventLocked(event);
}
// Hard-coded logic to update experiment ids on disk for certain rollback
// types and fill the rollback atom with experiment ids
- if (event->GetTagId() == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
+ if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
onWatchdogRollbackOccurredLocked(event);
}
@@ -406,16 +415,11 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
ALOGI("%s", event->ToString().c_str());
}
#endif
- const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
-
resetIfConfigTtlExpiredLocked(eventElapsedTimeNs);
- StatsdStats::getInstance().noteAtomLogged(
- event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC);
-
// Hard-coded logic to update the isolated uid's in the uid-map.
// The field numbers need to be currently updated by hand with atoms.proto
- if (event->GetTagId() == android::os::statsd::util::ISOLATED_UID_CHANGED) {
+ if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) {
onIsolatedUidChangedEventLocked(*event);
}
@@ -432,7 +436,7 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
}
- if (event->GetTagId() != android::os::statsd::util::ISOLATED_UID_CHANGED) {
+ if (atomId != android::os::statsd::util::ISOLATED_UID_CHANGED) {
// Map the isolated uid to host uid if necessary.
mapIsolatedUidToHostUidIfNecessaryLocked(event);
}
@@ -1051,8 +1055,8 @@ int64_t StatsLogProcessor::getLastReportTimeNs(const ConfigKey& key) {
void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk,
const int uid, const int64_t version) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
- ALOGW("Received app upgrade");
- for (auto it : mMetricsManagers) {
+ VLOG("Received app upgrade");
+ for (const auto& it : mMetricsManagers) {
it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version);
}
}
@@ -1060,20 +1064,28 @@ void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const strin
void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
const int uid) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
- ALOGW("Received app removed");
- for (auto it : mMetricsManagers) {
+ VLOG("Received app removed");
+ for (const auto& it : mMetricsManagers) {
it.second->notifyAppRemoved(eventTimeNs, apk, uid);
}
}
void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
- ALOGW("Received uid map");
- for (auto it : mMetricsManagers) {
+ VLOG("Received uid map");
+ for (const auto& it : mMetricsManagers) {
it.second->onUidMapReceived(eventTimeNs);
}
}
+void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) {
+ std::lock_guard<std::mutex> lock(mMetricsMutex);
+ VLOG("Received boot completed signal");
+ for (const auto& it : mMetricsManagers) {
+ it.second->onStatsdInitCompleted(elapsedTimeNs);
+ }
+}
+
void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
std::lock_guard<std::mutex> lock(mMetricsMutex);
mOnDiskDataConfigs.insert(key);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 97512ed7595c..ffd83ba978f4 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -120,6 +120,11 @@ public:
/* Notify all MetricsManagers of uid map snapshots received */
void onUidMapReceived(const int64_t& eventTimeNs) override;
+ /* Notify all metrics managers of boot completed
+ * This will force a bucket split when the boot is finished.
+ */
+ void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+
// Reset all configs.
void resetConfigs();
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index ae7a8d0d30cc..bd9f7a59fcbd 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -118,7 +118,8 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ
}
})),
mEventQueue(queue),
- mBootCompleteLatch({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}),
+ mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag},
+ [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }),
mStatsCompanionServiceDeathRecipient(
AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) {
mUidMap = UidMap::getInstance();
@@ -165,12 +166,6 @@ StatsService::StatsService(const sp<Looper>& handlerLooper, shared_ptr<LogEventQ
std::thread pushedEventThread([this] { readLogs(); });
pushedEventThread.detach();
}
-
- std::thread bootCompletedThread([this] {
- mBootCompleteLatch.wait();
- VLOG("In the boot completed thread");
- });
- bootCompletedThread.detach();
}
StatsService::~StatsService() {
@@ -946,7 +941,7 @@ Status StatsService::informAllUidData(const ScopedFileDescriptor& fd) {
packageNames,
installers);
- mBootCompleteLatch.countDown(kUidMapReceivedTag);
+ mBootCompleteTrigger.markComplete(kUidMapReceivedTag);
VLOG("StatsService::informAllUidData UidData proto parsed successfully.");
return Status::ok();
}
@@ -1066,7 +1061,7 @@ Status StatsService::bootCompleted() {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::bootCompleted was called");
- mBootCompleteLatch.countDown(kBootCompleteTag);
+ mBootCompleteTrigger.markComplete(kBootCompleteTag);
return Status::ok();
}
@@ -1222,20 +1217,11 @@ Status StatsService::unsetBroadcastSubscriber(int64_t configId,
return Status::ok();
}
-Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) {
- // Permission check not necessary as it's meant for applications to write to
- // statsd.
- android::os::statsd::util::stats_write(android::os::statsd::util::APP_BREADCRUMB_REPORTED,
- (int32_t) AIBinder_getCallingUid(), label,
- state);
- return Status::ok();
-}
-
Status StatsService::allPullersFromBootRegistered() {
ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::allPullersFromBootRegistered was called");
- mBootCompleteLatch.countDown(kAllPullersRegisteredTag);
+ mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag);
return Status::ok();
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 79324d89d8e8..b49fa1d42e66 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -33,7 +33,7 @@
#include "packages/UidMap.h"
#include "shell/ShellSubscriber.h"
#include "statscompanion_util.h"
-#include "utils/NamedLatch.h"
+#include "utils/MultiConditionTrigger.h"
using namespace android;
using namespace android::os;
@@ -162,11 +162,6 @@ public:
virtual void sayHiToStatsCompanion();
/**
- * Binder call to get AppBreadcrumbReported atom.
- */
- virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
-
- /**
* Binder call to notify statsd that all pullers from boot have been registered.
*/
virtual Status allPullersFromBootRegistered();
@@ -386,7 +381,7 @@ private:
mutable mutex mShellSubscriberMutex;
std::shared_ptr<LogEventQueue> mEventQueue;
- NamedLatch mBootCompleteLatch;
+ MultiConditionTrigger mBootCompleteTrigger;
static const inline string kBootCompleteTag = "BOOT_COMPLETE";
static const inline string kUidMapReceivedTag = "UID_MAP";
static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED";
@@ -399,11 +394,14 @@ private:
FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid);
FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs);
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp);
+ FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot);
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade);
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval);
FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit);
+ FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket);
FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket);
FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket);
+ FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket);
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket);
FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket);
};
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 933f48d1714b..3618bb0dd08b 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -67,8 +67,14 @@ bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
lock_guard<mutex> lk(*cv_mutex);
for (const StatsEventParcel& parcel: output) {
shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
- event->parseBuffer((uint8_t*)parcel.buffer.data(), parcel.buffer.size());
- sharedData->push_back(event);
+ bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(),
+ parcel.buffer.size());
+ if (valid) {
+ sharedData->push_back(event);
+ } else {
+ StatsdStats::getInstance().noteAtomError(event->GetTagId(),
+ /*pull=*/true);
+ }
}
*pullSuccess = success;
*pullFinish = true;
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index db637b15e969..46f5dbda5521 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -54,6 +54,7 @@ const int FIELD_ID_ACTIVATION_BROADCAST_GUARDRAIL = 19;
const int FIELD_ID_ATOM_STATS_TAG = 1;
const int FIELD_ID_ATOM_STATS_COUNT = 2;
+const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3;
const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1;
@@ -549,6 +550,20 @@ void StatsdStats::noteBucketBoundaryDelayNs(int64_t metricId, int64_t timeDelayN
std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs);
}
+void StatsdStats::noteAtomError(int atomTag, bool pull) {
+ lock_guard<std::mutex> lock(mLock);
+ if (pull) {
+ mPulledAtomStats[atomTag].atomErrorCount++;
+ return;
+ }
+
+ bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end());
+ bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize);
+ if (!full || present) {
+ mPushedAtomErrorStats[atomTag]++;
+ }
+}
+
StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) {
auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
if (atomMetricStatsIter != mAtomMetricStats.end()) {
@@ -604,9 +619,11 @@ void StatsdStats::resetInternalLocked() {
pullStats.second.pullExceedMaxDelay = 0;
pullStats.second.registeredCount = 0;
pullStats.second.unregisteredCount = 0;
+ pullStats.second.atomErrorCount = 0;
}
mAtomMetricStats.clear();
mActivationBroadcastGuardrailStats.clear();
+ mPushedAtomErrorStats.clear();
}
string buildTimeString(int64_t timeSec) {
@@ -617,6 +634,15 @@ string buildTimeString(int64_t timeSec) {
return string(timeBuffer);
}
+int StatsdStats::getPushedAtomErrors(int atomId) const {
+ const auto& it = mPushedAtomErrorStats.find(atomId);
+ if (it != mPushedAtomErrorStats.end()) {
+ return it->second;
+ } else {
+ return 0;
+ }
+}
+
void StatsdStats::dumpStats(int out) const {
lock_guard<std::mutex> lock(mLock);
time_t t = mStartTimeSec;
@@ -721,11 +747,13 @@ void StatsdStats::dumpStats(int out) const {
const size_t atomCounts = mPushedAtomStats.size();
for (size_t i = 2; i < atomCounts; i++) {
if (mPushedAtomStats[i] > 0) {
- dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
+ dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i],
+ getPushedAtomErrors((int)i));
}
}
for (const auto& pair : mNonPlatformPushedAtomStats) {
- dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second);
+ dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second,
+ getPushedAtomErrors(pair.first));
}
dprintf(out, "********Pulled Atom stats***********\n");
@@ -737,13 +765,15 @@ void StatsdStats::dumpStats(int out) const {
"nanos)%lld, "
" (max pull delay nanos)%lld, (data error)%ld\n"
" (pull timeout)%ld, (pull exceed max delay)%ld\n"
- " (registered count) %ld, (unregistered count) %ld\n",
+ " (registered count) %ld, (unregistered count) %ld\n"
+ " (atom error count) %d\n",
(int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache,
(long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec,
(long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs,
(long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs,
pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay,
- pair.second.registeredCount, pair.second.unregisteredCount);
+ pair.second.registeredCount, pair.second.unregisteredCount,
+ pair.second.atomErrorCount);
}
if (mAnomalyAlarmRegisteredStats > 0) {
@@ -919,6 +949,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+ int errors = getPushedAtomErrors(i);
+ if (errors > 0) {
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+ }
proto.end(token);
}
}
@@ -928,6 +962,10 @@ void StatsdStats::dumpStats(std::vector<uint8_t>* output, bool reset) {
proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first);
proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second);
+ int errors = getPushedAtomErrors(pair.first);
+ if (errors > 0) {
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+ }
proto.end(token);
}
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index ff31e9e73fd0..21e524a9fadf 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -461,6 +461,16 @@ public:
*/
void noteActivationBroadcastGuardrailHit(const int uid);
+ /**
+ * Reports that an atom is erroneous or cannot be parsed successfully by
+ * statsd. An atom tag of 0 indicates that the client did not supply the
+ * atom id within the encoding.
+ *
+ * For pushed atoms only, this call should be preceded by a call to
+ * noteAtomLogged.
+ */
+ void noteAtomError(int atomTag, bool pull=false);
+
/**
* Reset the historical stats. Including all stats in icebox, and the tracked stats about
* metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
@@ -499,6 +509,7 @@ public:
long emptyData = 0;
long registeredCount = 0;
long unregisteredCount = 0;
+ int32_t atomErrorCount = 0;
} PulledAtomStats;
typedef struct {
@@ -546,6 +557,12 @@ private:
// Maps PullAtomId to its stats. The size is capped by the puller atom counts.
std::map<int, PulledAtomStats> mPulledAtomStats;
+ // Stores the number of times a pushed atom was logged erroneously. The
+ // corresponding counts for pulled atoms are stored in PulledAtomStats.
+ // The max size of this map is kMaxAtomErrorsStatsSize.
+ std::map<int, int> mPushedAtomErrorStats;
+ int kMaxPushedAtomErrorStatsSize = 100;
+
// Maps metric ID to its stats. The size is capped by the number of metrics.
std::map<int64_t, AtomMetricStats> mAtomMetricStats;
@@ -613,6 +630,8 @@ private:
void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats);
+ int getPushedAtomErrors(int atomId) const;
+
/**
* Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference
* will live as long as `this`.
@@ -631,6 +650,7 @@ private:
FRIEND_TEST(StatsdStatsTest, TestPullAtomStats);
FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
+ FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
};
} // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 61cd01728ab1..eb830e114b40 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -114,14 +114,6 @@ LogEvent::LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
mValues.push_back(FieldValue(Field(mTagId, getSimpleField(4)), Value(trainInfo.status)));
}
-LogEvent::~LogEvent() {
- if (mContext) {
- // This is for the case when LogEvent is created using the test interface
- // but init() isn't called.
- android_log_destroy(&mContext);
- }
-}
-
void LogEvent::parseInt32(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations) {
int32_t value = readNextValue<int32_t>();
addToValues(pos, depth, value, last);
@@ -303,8 +295,7 @@ void LogEvent::parseTriggerStateResetAnnotation(uint8_t annotationType) {
return;
}
- int32_t resetState = readNextValue<int32_t>();
- mValues[mValues.size() - 1].mAnnotations.setResetState(resetState);
+ mResetState = readNextValue<int32_t>();
}
void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
@@ -386,7 +377,6 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) {
typeInfo = readNextValue<uint8_t>();
uint8_t typeId = getTypeId(typeInfo);
- // TODO(b/144373276): handle errors passed to the socket
switch (typeId) {
case BOOL_TYPE:
parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
@@ -413,8 +403,13 @@ bool LogEvent::parseBuffer(uint8_t* buf, size_t len) {
parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0];
break;
+ case ERROR_TYPE:
+ mErrorBitmask = readNextValue<int32_t>();
+ mValid = false;
+ break;
default:
mValid = false;
+ break;
}
}
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 41fdcc2cbe7a..dedcfaf6cd87 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -70,7 +70,7 @@ public:
explicit LogEvent(int64_t wallClockTimestampNs, int64_t elapsedTimestampNs,
const InstallTrainInfo& installTrainInfo);
- ~LogEvent();
+ ~LogEvent() {}
/**
* Get the timestamp associated with this event.
@@ -184,6 +184,12 @@ public:
return mExclusiveStateFieldIndex;
}
+ // If a reset state is not sent in the StatsEvent, returns -1. Note that a
+ // reset state is sent if and only if a reset should be triggered.
+ inline int getResetState() const {
+ return mResetState;
+ }
+
inline LogEvent makeCopy() {
return LogEvent(*this);
}
@@ -204,6 +210,14 @@ public:
return BAD_INDEX;
}
+ bool isValid() const {
+ return mValid;
+ }
+
+ int32_t getErrorBitmask() const {
+ return mErrorBitmask;
+ }
+
private:
/**
* Only use this if copy is absolutely needed.
@@ -230,12 +244,13 @@ private:
bool checkPreviousValueType(Type expected);
/**
- * The below three variables are only valid during the execution of
+ * The below two variables are only valid during the execution of
* parseBuffer. There are no guarantees about the state of these variables
* before/after.
*/
uint8_t* mBuf;
uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
+
bool mValid = true; // stores whether the event we received from the socket is valid
/**
@@ -287,19 +302,15 @@ private:
// matching.
std::vector<FieldValue> mValues;
- // This field is used when statsD wants to create log event object and write fields to it. After
- // calling init() function, this object would be destroyed to save memory usage.
- // When the log event is created from log msg, this field is never initiated.
- android_log_context mContext = NULL;
-
// The timestamp set by the logd.
int64_t mLogdTimestampNs;
// The elapsed timestamp set by statsd log writer.
int64_t mElapsedTimestampNs;
- // The atom tag of the event.
- int mTagId;
+ // The atom tag of the event (defaults to 0 if client does not
+ // appropriately set the atom id).
+ int mTagId = 0;
// The uid of the logging client (defaults to -1).
int32_t mLogUid = -1;
@@ -307,11 +318,15 @@ private:
// The pid of the logging client (defaults to -1).
int32_t mLogPid = -1;
+ // Bitmask of errors sent by StatsEvent/AStatsEvent.
+ int32_t mErrorBitmask = 0;
+
// Annotations
bool mTruncateTimestamp = false;
int mUidFieldIndex = -1;
int mAttributionChainIndex = -1;
int mExclusiveStateFieldIndex = -1;
+ int mResetState = -1;
};
void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/cmds/statsd/src/main.cpp b/cmds/statsd/src/main.cpp
index e3945334aeca..cd9c4e5b947b 100644
--- a/cmds/statsd/src/main.cpp
+++ b/cmds/statsd/src/main.cpp
@@ -37,6 +37,7 @@ using std::shared_ptr;
using std::make_shared;
shared_ptr<StatsService> gStatsService = nullptr;
+sp<StatsSocketListener> gSocketListener = nullptr;
void signalHandler(int sig) {
if (sig == SIGPIPE) {
@@ -47,6 +48,7 @@ void signalHandler(int sig) {
return;
}
+ if (gSocketListener != nullptr) gSocketListener->stopListener();
if (gStatsService != nullptr) gStatsService->Terminate();
ALOGW("statsd terminated on receiving signal %d.", sig);
exit(1);
@@ -92,11 +94,11 @@ int main(int /*argc*/, char** /*argv*/) {
gStatsService->Startup();
- sp<StatsSocketListener> socketListener = new StatsSocketListener(eventQueue);
+ gSocketListener = new StatsSocketListener(eventQueue);
ALOGI("Statsd starts to listen to socket.");
// Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
- if (socketListener->startListener(600)) {
+ if (gSocketListener->startListener(600)) {
exit(1);
}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index a4711e8357f2..f9a8842efc3d 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -109,10 +109,11 @@ private:
FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
- FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
- FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
+
+ FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket);
+ FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index cc48f99add01..6f84076ee6b5 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -154,12 +154,14 @@ private:
FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState);
- FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade);
- FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket);
- FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade);
- FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket);
FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket);
+
+ FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration);
+ FRIEND_TEST(DurationMetricProducerTest_PartialBucket,
+ TestSumDurationWithSplitInFollowingBucket);
+ FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration);
+ FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index aa0cae26080d..2eb584b097ea 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -73,18 +73,23 @@ public:
bool pullSuccess, int64_t originalPullTimeNs) override;
// GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
- void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
- const int64_t version) override {
+ void notifyAppUpgrade(const int64_t& eventTimeNs) override {
std::lock_guard<std::mutex> lock(mMutex);
if (!mSplitBucketForAppUpgrade) {
return;
}
- if (eventTimeNs > getCurrentBucketEndTimeNs()) {
- // Flush full buckets on the normal path up to the latest bucket boundary.
- flushIfNeededLocked(eventTimeNs);
+ flushLocked(eventTimeNs);
+ if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
+ pullAndMatchEventsLocked(eventTimeNs);
}
- flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+ };
+
+ // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
+ void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+
+ flushLocked(eventTimeNs);
if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
pullAndMatchEventsLocked(eventTimeNs);
}
@@ -190,13 +195,14 @@ private:
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
- FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade);
- FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade);
FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled);
FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection);
FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket);
FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger);
FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput);
+
+ FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
+ FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 6aba13ca7859..91c98ea27269 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -141,30 +141,25 @@ public:
}
/**
- * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we
- * first call the standard flushing code to flush up to the latest full bucket. Then we call
- * the flush again when the end timestamp is forced to be now, and then after flushing, update
- * the start timestamp to be now.
+ * Force a partial bucket split on app upgrade
*/
- virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
- const int64_t version) {
+ virtual void notifyAppUpgrade(const int64_t& eventTimeNs) {
std::lock_guard<std::mutex> lock(mMutex);
-
- if (eventTimeNs > getCurrentBucketEndTimeNs()) {
- // Flush full buckets on the normal path up to the latest bucket boundary.
- flushIfNeededLocked(eventTimeNs);
- }
- // Now flush a partial bucket.
- flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
- // Don't update the current bucket number so that the anomaly tracker knows this bucket
- // is a partial bucket and can merge it with the previous bucket.
+ flushLocked(eventTimeNs);
};
- void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) {
+ void notifyAppRemoved(const int64_t& eventTimeNs) {
// Force buckets to split on removal also.
- notifyAppUpgrade(eventTimeNs, apk, uid, 0);
+ notifyAppUpgrade(eventTimeNs);
};
+ /**
+ * Force a partial bucket split on boot complete.
+ */
+ virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) {
+ std::lock_guard<std::mutex> lock(mMutex);
+ flushLocked(eventTimeNs);
+ }
// Consume the parsed stats log entry that already matched the "what" of the metric.
void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
std::lock_guard<std::mutex> lock(mMutex);
@@ -292,8 +287,7 @@ public:
// End: getters/setters
protected:
/**
- * Flushes the current bucket if the eventTime is after the current bucket's end time. This will
- also flush the current partial bucket in memory.
+ * Flushes the current bucket if the eventTime is after the current bucket's end time.
*/
virtual void flushIfNeededLocked(const int64_t& eventTime){};
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index d832ed86580d..bcca1fd9fdc9 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -231,8 +231,8 @@ bool MetricsManager::isConfigValid() const {
void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
const int64_t version) {
// Inform all metric producers.
- for (auto it : mAllMetricProducers) {
- it->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+ for (const auto& it : mAllMetricProducers) {
+ it->notifyAppUpgrade(eventTimeNs);
}
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
@@ -252,8 +252,8 @@ void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string&
void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
const int uid) {
// Inform all metric producers.
- for (auto it : mAllMetricProducers) {
- it->notifyAppRemoved(eventTimeNs, apk, uid);
+ for (const auto& it : mAllMetricProducers) {
+ it->notifyAppRemoved(eventTimeNs);
}
// check if we care this package
if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
@@ -282,6 +282,13 @@ void MetricsManager::onUidMapReceived(const int64_t& eventTimeNs) {
initLogSourceWhiteList();
}
+void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) {
+ // Inform all metric producers.
+ for (const auto& it : mAllMetricProducers) {
+ it->onStatsdInitCompleted(eventTimeNs);
+ }
+}
+
void MetricsManager::init() {
for (const auto& producer : mAllMetricProducers) {
producer->prepareFirstBucket();
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 1fd6572cc760..ef03d2064ab0 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -70,6 +70,8 @@ public:
void onUidMapReceived(const int64_t& eventTimeNs);
+ void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+
void init();
vector<int32_t> getPullAtomUids(int32_t atomId) override;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index e9273dc54424..c8dc8cc290c4 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -69,8 +69,7 @@ public:
bool pullSuccess, int64_t originalPullTimeNs) override;
// ValueMetric needs special logic if it's a pulled atom.
- void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
- const int64_t version) override {
+ void notifyAppUpgrade(const int64_t& eventTimeNs) override {
std::lock_guard<std::mutex> lock(mMutex);
if (!mSplitBucketForAppUpgrade) {
return;
@@ -81,6 +80,15 @@ public:
flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
};
+ // ValueMetric needs special logic if it's a pulled atom.
+ void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+ std::lock_guard<std::mutex> lock(mMutex);
+ if (mIsPulled && mCondition) {
+ pullAndMatchEventsLocked(eventTimeNs);
+ }
+ flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+ };
+
void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
int oldState, int newState) override;
@@ -256,7 +264,6 @@ private:
FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection);
FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange);
- FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade);
FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange);
FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition);
FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition);
@@ -269,10 +276,8 @@ private:
FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled);
FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket);
- FRIEND_TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid);
FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff);
FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff);
- FRIEND_TEST(ValueMetricProducerTest, TestPartialBucketCreated);
FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries);
FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse);
FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue);
@@ -283,15 +288,12 @@ private:
FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset);
FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset);
FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering);
- FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade);
- FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse);
FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled);
FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg);
FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax);
FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin);
FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum);
FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition);
- FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade);
FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition);
FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded);
FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange);
@@ -313,6 +315,14 @@ private:
FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit);
FRIEND_TEST(ValueMetricProducerTest_BucketDrop,
TestInvalidBucketWhenAccumulateEventWrongBucket);
+
+ FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket);
+ FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid);
+ FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated);
+ FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents);
+ FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue);
+ FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse);
+
friend class ValueMetricProducerTestHelper;
};
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index b7f314a819df..b63713b64c5d 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -51,7 +51,7 @@ void StateTracker::onLogEvent(const LogEvent& event) {
return;
}
- const int32_t resetState = stateValue.mAnnotations.getResetState();
+ const int32_t resetState = event.getResetState();
if (resetState != -1) {
VLOG("StateTracker new reset state: %d", resetState);
handleReset(eventTimeNs, resetState);
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index f4247eca6e23..868247bc9d64 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -425,6 +425,7 @@ message StatsdStatsReport {
message AtomStats {
optional int32 tag = 1;
optional int32 count = 2;
+ optional int32 error_count = 3;
}
repeated AtomStats atom_stats = 7;
@@ -460,6 +461,7 @@ message StatsdStatsReport {
optional int64 empty_data = 15;
optional int64 registered_count = 16;
optional int64 unregistered_count = 17;
+ optional int32 atom_error_count = 18;
}
repeated PulledAtomStats pulled_atom_stats = 10;
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 563531351fe0..f9fddc8a7c8a 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -80,6 +80,8 @@ const int FIELD_ID_STATS_COMPANION_BINDER_TRANSACTION_FAILED = 14;
const int FIELD_ID_EMPTY_DATA = 15;
const int FIELD_ID_PULL_REGISTERED_COUNT = 16;
const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17;
+const int FIELD_ID_ATOM_ERROR_COUNT = 18;
+
// for AtomMetricStats proto
const int FIELD_ID_ATOM_METRIC_STATS = 17;
const int FIELD_ID_METRIC_ID = 1;
@@ -492,6 +494,7 @@ void writePullerStatsToStream(const std::pair<int, StatsdStats::PulledAtomStats>
(long long) pair.second.registeredCount);
protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT,
(long long) pair.second.unregisteredCount);
+ protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount);
protoOutput->end(token);
}
diff --git a/cmds/statsd/src/utils/NamedLatch.cpp b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
index 6e77977857cc..43a69337f368 100644
--- a/cmds/statsd/src/utils/NamedLatch.cpp
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
@@ -15,7 +15,9 @@
*/
#define DEBUG false // STOPSHIP if true
-#include "NamedLatch.h"
+#include "MultiConditionTrigger.h"
+
+#include <thread>
using namespace std;
@@ -23,26 +25,33 @@ namespace android {
namespace os {
namespace statsd {
-NamedLatch::NamedLatch(const set<string>& eventNames) : mRemainingEventNames(eventNames) {
+MultiConditionTrigger::MultiConditionTrigger(const set<string>& conditionNames,
+ function<void()> trigger)
+ : mRemainingConditionNames(conditionNames),
+ mTrigger(trigger),
+ mCompleted(mRemainingConditionNames.empty()) {
+ if (mCompleted) {
+ thread executorThread([this] { mTrigger(); });
+ executorThread.detach();
+ }
}
-void NamedLatch::countDown(const string& eventName) {
- bool notify = false;
+void MultiConditionTrigger::markComplete(const string& conditionName) {
+ bool doTrigger = false;
{
lock_guard<mutex> lg(mMutex);
- mRemainingEventNames.erase(eventName);
- notify = mRemainingEventNames.empty();
+ if (mCompleted) {
+ return;
+ }
+ mRemainingConditionNames.erase(conditionName);
+ mCompleted = mRemainingConditionNames.empty();
+ doTrigger = mCompleted;
}
- if (notify) {
- mConditionVariable.notify_all();
+ if (doTrigger) {
+ std::thread executorThread([this] { mTrigger(); });
+ executorThread.detach();
}
}
-
-void NamedLatch::wait() const {
- unique_lock<mutex> unique_lk(mMutex);
- mConditionVariable.wait(unique_lk, [this] { return mRemainingEventNames.empty(); });
-}
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.h b/cmds/statsd/src/utils/MultiConditionTrigger.h
new file mode 100644
index 000000000000..51f6029915be
--- /dev/null
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gtest/gtest_prod.h>
+
+#include <mutex>
+#include <set>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * This class provides a utility to wait for a set of named conditions to occur.
+ *
+ * It will execute the trigger runnable in a detached thread once all conditions have been marked
+ * true.
+ */
+class MultiConditionTrigger {
+public:
+ explicit MultiConditionTrigger(const std::set<std::string>& conditionNames,
+ std::function<void()> trigger);
+
+ MultiConditionTrigger(const MultiConditionTrigger&) = delete;
+ MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete;
+
+ // Mark a specific condition as true. If this condition has called markComplete already or if
+ // the event was not specified in the constructor, the function is a no-op.
+ void markComplete(const std::string& eventName);
+
+private:
+ mutable std::mutex mMutex;
+ std::set<std::string> mRemainingConditionNames;
+ std::function<void()> mTrigger;
+ bool mCompleted;
+
+ FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName);
+};
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/utils/NamedLatch.h b/cmds/statsd/src/utils/NamedLatch.h
deleted file mode 100644
index 70238370f647..000000000000
--- a/cmds/statsd/src/utils/NamedLatch.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <gtest/gtest_prod.h>
-
-#include <condition_variable>
-#include <mutex>
-#include <set>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * This class provides a threading primitive similar to a latch.
- * The primary difference is that it waits for named events to occur instead of waiting for
- * N threads to reach a certain point.
- *
- * It uses a condition variable under the hood.
- */
-class NamedLatch {
-public:
- explicit NamedLatch(const std::set<std::string>& eventNames);
-
- NamedLatch(const NamedLatch&) = delete;
- NamedLatch& operator=(const NamedLatch&) = delete;
-
- // Mark a specific event as completed. If this event has called countDown already or if the
- // event was not specified in the constructor, the function is a no-op.
- void countDown(const std::string& eventName);
-
- // Blocks the calling thread until all events in eventNames have called countDown.
- void wait() const;
-
-private:
- mutable std::mutex mMutex;
- mutable std::condition_variable mConditionVariable;
- std::set<std::string> mRemainingEventNames;
-
- FRIEND_TEST(NamedLatchTest, TestCountDownCalledBySameEventName);
-};
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index bb4578d9b701..e52e2d024e94 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -352,7 +352,7 @@ TEST(LogEventTest, TestResetStateAnnotation) {
const vector<FieldValue>& values = event.getValues();
EXPECT_EQ(values.size(), 1);
- EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState);
+ EXPECT_EQ(event.getResetState(), resetState);
}
} // namespace statsd
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index b173ee0334a7..911762339b70 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -89,6 +89,7 @@ StatsdConfig MakeValueMetricConfig(int64_t minTime) {
valueMetric->set_bucket(FIVE_MINUTES);
valueMetric->set_min_bucket_size_nanos(minTime);
valueMetric->set_use_absolute_value_on_reset(true);
+ valueMetric->set_skip_zero_diff_output(false);
return config;
}
@@ -217,6 +218,35 @@ TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval) {
EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
}
+TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) {
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ SendConfig(service, MakeConfig());
+ int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
+ // initialized with.
+
+ // Goes into the first bucket
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get());
+ int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC;
+ service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+ // Goes into the second bucket.
+ service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get());
+
+ ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC);
+ backfillStartEndTimestamp(&report);
+
+ ASSERT_EQ(1, report.metrics_size());
+ ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+ ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
+ EXPECT_TRUE(report.metrics(0)
+ .count_metrics()
+ .data(0)
+ .bucket_info(0)
+ .has_start_bucket_elapsed_nanos());
+ EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+ report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos());
+ EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
+}
+
TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
service->mPullerManager->RegisterPullAtomCallback(
@@ -229,13 +259,22 @@ TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
// initialized with.
service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
- service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
- String16("v2"), String16(""));
+ int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC;
+ service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"),
+ String16(""));
ConfigMetricsReport report =
- GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
+ backfillStartEndTimestamp(&report);
+
EXPECT_EQ(1, report.metrics_size());
EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size());
+
+ // The fake subsystem state sleep puller returns two atoms.
+ ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+ ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size());
+ EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)),
+ report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos());
}
TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
@@ -249,13 +288,13 @@ TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
- const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
+ const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC;
service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
String16(""));
ConfigMetricsReport report =
- GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
backfillStartEndTimestamp(&report);
ASSERT_EQ(1, report.metrics_size());
@@ -264,10 +303,49 @@ TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
// Can't test the start time since it will be based on the actual time when the pulling occurs.
EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos());
+
+ ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+ EXPECT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size());
+}
+
+TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) {
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ // Initial pull will fail since puller is not registered.
+ SendConfig(service, MakeValueMetricConfig(0));
+ int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
+ // initialized with.
+
+ service->mPullerManager->RegisterPullAtomCallback(
+ /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+ SharedRefBase::make<FakeSubsystemSleepCallback>());
+
+ int64_t bootCompleteTimeNs = start + NS_PER_SEC;
+ service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+
+ service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+
+ ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+ backfillStartEndTimestamp(&report);
+
+ // First bucket is dropped due to the initial pull failing
+ ASSERT_EQ(1, report.metrics_size());
+ EXPECT_EQ(1, report.metrics(0).value_metrics().skipped_size());
+ EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+ report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos());
+
+ // The fake subsystem state sleep puller returns two atoms.
+ ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+ ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size());
+ EXPECT_EQ(
+ MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+ report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos());
}
TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ service->mPullerManager->RegisterPullAtomCallback(
+ /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+ SharedRefBase::make<FakeSubsystemSleepCallback>());
// Partial buckets don't occur when app is first installed.
service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
SendConfig(service, MakeGaugeMetricConfig(0));
@@ -278,16 +356,22 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
String16("v2"), String16(""));
- ConfigMetricsReport report =
- GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
- EXPECT_EQ(1, report.metrics_size());
+ ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+ backfillStartEndTimestamp(&report);
+ ASSERT_EQ(1, report.metrics_size());
EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+ // The fake subsystem state sleep puller returns two atoms.
+ ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+ EXPECT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
}
TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
// Partial buckets don't occur when app is first installed.
service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+ service->mPullerManager->RegisterPullAtomCallback(
+ /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+ SharedRefBase::make<FakeSubsystemSleepCallback>());
SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
// initialized with.
@@ -298,7 +382,7 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
String16(""));
ConfigMetricsReport report =
- GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+ GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
backfillStartEndTimestamp(&report);
ASSERT_EQ(1, report.metrics_size());
ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
@@ -306,6 +390,38 @@ TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos());
EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos());
+ ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+ EXPECT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
+}
+
+TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) {
+ shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+ // Initial pull will fail since puller hasn't been registered.
+ SendConfig(service, MakeGaugeMetricConfig(0));
+ int64_t start = getElapsedRealtimeNs(); // This is the start-time the metrics producers are
+ // initialized with.
+
+ service->mPullerManager->RegisterPullAtomCallback(
+ /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+ SharedRefBase::make<FakeSubsystemSleepCallback>());
+
+ int64_t bootCompleteTimeNs = start + NS_PER_SEC;
+ service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+
+ service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+
+ ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+ backfillStartEndTimestamp(&report);
+
+ ASSERT_EQ(1, report.metrics_size());
+ EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+ // The fake subsystem state sleep puller returns two atoms.
+ ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+ // No data in the first bucket, so nothing is reported
+ ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
+ EXPECT_EQ(
+ MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+ report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos());
}
#else
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 129fafa75b41..cdde603f4c0b 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -486,6 +486,41 @@ TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit) {
EXPECT_TRUE(uid2Good);
}
+TEST(StatsdStatsTest, TestAtomErrorStats) {
+ StatsdStats stats;
+
+ int pushAtomTag = 100;
+ int pullAtomTag = 1000;
+ int numErrors = 10;
+
+ for (int i = 0; i < numErrors; i++) {
+ // We must call noteAtomLogged as well because only those pushed atoms
+ // that have been logged will have stats printed about them in the
+ // proto.
+ stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0);
+ stats.noteAtomError(pushAtomTag, /*pull=*/false);
+
+ stats.noteAtomError(pullAtomTag, /*pull=*/true);
+ }
+
+ vector<uint8_t> output;
+ stats.dumpStats(&output, false);
+ StatsdStatsReport report;
+ EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+ // Check error count = numErrors for push atom
+ EXPECT_EQ(1, report.atom_stats_size());
+ const auto& pushedAtomStats = report.atom_stats(0);
+ EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+ EXPECT_EQ(numErrors, pushedAtomStats.error_count());
+
+ // Check error count = numErrors for pull atom
+ EXPECT_EQ(1, report.pulled_atom_stats_size());
+ const auto& pulledAtomStats = report.pulled_atom_stats(0);
+ EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id());
+ EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 65f8de69711d..8131725cd148 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -38,9 +38,9 @@ namespace android {
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, 12345);
namespace {
+const ConfigKey kConfigKey(0, 12345);
void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -61,6 +61,13 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId, string ui
} // namespace
+// Setup for parameterized tests.
+class CountMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket,
+ CountMetricProducerTest_PartialBucket,
+ testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
TEST(CountMetricProducerTest, TestFirstBucket) {
CountMetric metric;
metric.set_id(1);
@@ -237,11 +244,11 @@ TEST(CountMetricProducerTest, TestEventsWithSlicedCondition) {
EXPECT_EQ(1LL, bucketInfo.mCount);
}
-TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
+TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) {
sp<AlarmMonitor> alarmMonitor;
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
int tagId = 1;
int conditionTagId = 2;
@@ -260,22 +267,30 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
- // Bucket is flushed yet.
+ // Bucket is not flushed yet.
LogEvent event1(/*uid=*/0, /*pid=*/0);
makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111");
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
- // App upgrade forces bucket flush.
+ // App upgrade or boot complete forces bucket flush.
// Check that there's a past bucket and the bucket end is not adjusted.
- countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ countProducer.notifyAppUpgrade(eventTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ countProducer.onStatsdInitCompleted(eventTimeNs);
+ break;
+ }
EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ((long long)bucketStartTimeNs,
+ EXPECT_EQ(bucketStartTimeNs,
countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
- EXPECT_EQ((long long)eventUpgradeTimeNs,
+ EXPECT_EQ(eventTimeNs,
countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
- EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, countProducer.getCurrentBucketNum());
+ EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
// Anomaly tracker only contains full buckets.
EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
@@ -285,7 +300,8 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222");
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, countProducer.getCurrentBucketNum());
EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
// Third event in following bucket.
@@ -294,13 +310,14 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, countProducer.getCurrentBucketNum());
EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
}
-TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
+TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) {
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
int tagId = 1;
int conditionTagId = 2;
@@ -319,15 +336,23 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
- // App upgrade forces bucket flush.
- // Check that there's a past bucket and the bucket end is not adjusted.
- countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ // App upgrade or boot complete forces bucket flush.
+ // Check that there's a past bucket and the bucket end is not adjusted since the upgrade
+ // occurred after the bucket end time.
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ countProducer.notifyAppUpgrade(eventTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ countProducer.onStatsdInitCompleted(eventTimeNs);
+ break;
+ }
EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ((int64_t)bucketStartTimeNs,
+ EXPECT_EQ(bucketStartTimeNs,
countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
- EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
// Next event occurs in same bucket as partial bucket created.
LogEvent event2(/*uid=*/0, /*pid=*/0);
@@ -340,7 +365,7 @@ TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333");
countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ((int64_t)eventUpgradeTimeNs,
+ EXPECT_EQ((int64_t)eventTimeNs,
countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs);
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs);
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 30f815962160..8ef251952db7 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -41,10 +41,10 @@ namespace android {
namespace os {
namespace statsd {
-const ConfigKey kConfigKey(0, 12345);
namespace {
+const ConfigKey kConfigKey(0, 12345);
void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
AStatsEvent_setAtomId(statsEvent, atomId);
@@ -55,6 +55,13 @@ void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
} // namespace
+// Setup for parameterized tests.
+class DurationMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket,
+ DurationMetricProducerTest_PartialBucket,
+ testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
TEST(DurationMetricTrackerTest, TestFirstBucket) {
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
DurationMetric metric;
@@ -205,7 +212,7 @@ TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState) {
EXPECT_EQ(1LL, buckets2[0].mDuration);
}
-TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) {
/**
* The duration starts from the first bucket, through the two partial buckets (10-70sec),
* another bucket, and ends at the beginning of the next full bucket.
@@ -217,15 +224,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
*/
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
- int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
- int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
int tagId = 1;
- LogEvent event1(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event1, startTimeNs, tagId);
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event2, endTimeNs, tagId);
DurationMetric metric;
metric.set_id(1);
@@ -238,32 +237,47 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
3 /* stop_all index */, false /*nesting*/, wizard,
dimensions, bucketStartTimeNs, bucketStartTimeNs);
+ int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+ LogEvent event1(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event1, startTimeNs, tagId);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
- durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
std::vector<DurationBucket> buckets =
durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
- EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs);
- EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration);
- EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs);
+ EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration);
+ EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
// We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event2, endTimeNs, tagId);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
EXPECT_EQ(3UL, buckets.size());
- EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs);
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs);
- EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration);
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs);
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
EXPECT_EQ(bucketSizeNs, buckets[2].mDuration);
}
-TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) {
/**
* Expected buckets (start at 11s, upgrade at 75s, end at 135s):
* - [10,70]: 59 secs
@@ -272,15 +286,7 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
*/
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
- int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
- int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
int tagId = 1;
- LogEvent event1(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event1, startTimeNs, tagId);
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event2, endTimeNs, tagId);
DurationMetric metric;
metric.set_id(1);
@@ -293,11 +299,22 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
3 /* stop_all index */, false /*nesting*/, wizard,
dimensions, bucketStartTimeNs, bucketStartTimeNs);
+ int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+ LogEvent event1(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event1, startTimeNs, tagId);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
- durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
std::vector<DurationBucket> buckets =
durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
@@ -305,32 +322,29 @@ TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration);
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
- EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs);
- EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
- EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs);
+ EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
+ EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
// We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event2, endTimeNs, tagId);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
EXPECT_EQ(3UL, buckets.size());
- EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs);
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
- EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration);
+ EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs,
+ buckets[2].mDuration);
}
-TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) {
sp<AlarmMonitor> alarmMonitor;
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
- int64_t startTimeNs = bucketStartTimeNs + 1;
- int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
-
int tagId = 1;
- LogEvent event1(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event1, startTimeNs, tagId);
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event2, endTimeNs, tagId);
// Setup metric with alert.
DurationMetric metric;
@@ -351,27 +365,35 @@ TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
+ int64_t startTimeNs = bucketStartTimeNs + 1;
+ LogEvent event1(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event1, startTimeNs, tagId);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
- durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
// We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event2, endTimeNs, tagId);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
+
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs,
anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
}
-TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) {
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
- int64_t startTimeNs = bucketStartTimeNs + 1;
- int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
int tagId = 1;
- LogEvent event1(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event1, startTimeNs, tagId);
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event2, endTimeNs, tagId);
DurationMetric metric;
metric.set_id(1);
@@ -385,15 +407,30 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
3 /* stop_all index */, false /*nesting*/, wizard,
dimensions, bucketStartTimeNs, bucketStartTimeNs);
+ int64_t startTimeNs = bucketStartTimeNs + 1;
+ LogEvent event1(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event1, startTimeNs, tagId);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
- durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
// We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+ int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event2, endTimeNs, tagId);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
@@ -406,18 +443,10 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
}
-TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) {
int64_t bucketStartTimeNs = 10000000000;
int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
- int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
- int64_t startTimeNs = bucketStartTimeNs + 1;
- int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
-
int tagId = 1;
- LogEvent event1(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event1, startTimeNs, tagId);
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- makeLogEvent(&event2, endTimeNs, tagId);
DurationMetric metric;
metric.set_id(1);
@@ -431,24 +460,39 @@ TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) {
3 /* stop_all index */, false /*nesting*/, wizard,
dimensions, bucketStartTimeNs, bucketStartTimeNs);
+ int64_t startTimeNs = bucketStartTimeNs + 1;
+ LogEvent event1(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event1, startTimeNs, tagId);
durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
- durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
// Stop occurs in the same partial bucket as created for the app upgrade.
+ int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ makeLogEvent(&event2, endTimeNs, tagId);
durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
std::vector<DurationBucket> buckets =
durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
EXPECT_EQ(1UL, buckets.size());
- EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs);
EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs);
EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
}
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 42d0d5d8c530..9d2ec88e2f9b 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -42,6 +42,8 @@ namespace android {
namespace os {
namespace statsd {
+namespace {
+
const ConfigKey kConfigKey(0, 12345);
const int tagId = 1;
const int64_t metricId = 123;
@@ -52,9 +54,8 @@ const int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000L
const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
-const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-namespace {
shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1,
int32_t value2) {
AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -71,6 +72,13 @@ shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t v
}
} // anonymous namespace
+// Setup for parameterized tests.
+class GaugeMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket,
+ GaugeMetricProducerTest_PartialBucket,
+ testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
/*
* Tests that the first bucket works correctly
*/
@@ -194,7 +202,7 @@ TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition) {
EXPECT_EQ(25L, it->mValue.int_value);
}
-TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
+TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) {
sp<AlarmMonitor> alarmMonitor;
GaugeMetric metric;
metric.set_id(metricId);
@@ -230,11 +238,22 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
- gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(bucketStartTimeNs,
+ gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs,
+ gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
- EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
// Partial buckets are not sent to anomaly tracker.
EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
@@ -244,7 +263,11 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(bucketStartTimeNs,
+ gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs,
+ gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+ EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
// Partial buckets are not sent to anomaly tracker.
EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
@@ -267,7 +290,7 @@ TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
}
-TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
+TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) {
GaugeMetric metric;
metric.set_id(metricId);
metric.set_bucket(ONE_MINUTE);
@@ -293,7 +316,8 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
.WillOnce(Invoke(
[](int tagId, const ConfigKey&, vector<std::shared_ptr<LogEvent>>* data, bool) {
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, eventUpgradeTimeNs, 2));
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 2));
return true;
}));
@@ -311,10 +335,21 @@ TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
.mFields->begin()
->mValue.int_value);
- gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ EXPECT_EQ(bucketStartTimeNs,
+ gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs,
+ gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
- EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin()
->second.front()
@@ -370,7 +405,7 @@ TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled) {
.mFields->begin()
->mValue.int_value);
- gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+ gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
EXPECT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 3b4d646f0f2f..f493cc4033ad 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -41,6 +41,8 @@ namespace android {
namespace os {
namespace statsd {
+namespace {
+
const ConfigKey kConfigKey(0, 12345);
const int tagId = 1;
const int64_t metricId = 123;
@@ -58,10 +60,18 @@ double epsilon = 0.001;
static void assertPastBucketValuesSingleKey(
const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets,
const std::initializer_list<int>& expectedValuesList,
- const std::initializer_list<int64_t>& expectedDurationNsList) {
- std::vector<int> expectedValues(expectedValuesList);
- std::vector<int64_t> expectedDurationNs(expectedDurationNsList);
+ const std::initializer_list<int64_t>& expectedDurationNsList,
+ const std::initializer_list<int64_t>& expectedStartTimeNsList,
+ const std::initializer_list<int64_t>& expectedEndTimeNsList) {
+ vector<int> expectedValues(expectedValuesList);
+ vector<int64_t> expectedDurationNs(expectedDurationNsList);
+ vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList);
+ vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList);
+
ASSERT_EQ(expectedValues.size(), expectedDurationNs.size());
+ ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size());
+ ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size());
+
if (expectedValues.size() == 0) {
ASSERT_EQ(0, mPastBuckets.size());
return;
@@ -70,15 +80,21 @@ static void assertPastBucketValuesSingleKey(
ASSERT_EQ(1, mPastBuckets.size());
ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size());
- auto buckets = mPastBuckets.begin()->second;
+ const vector<ValueBucket>& buckets = mPastBuckets.begin()->second;
for (int i = 0; i < expectedValues.size(); i++) {
EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value)
<< "Values differ at index " << i;
EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs)
<< "Condition duration value differ at index " << i;
+ EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs)
+ << "Start time differs at index " << i;
+ EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs)
+ << "End time differs at index " << i;
}
}
+} // anonymous namespace
+
class ValueMetricProducerTestHelper {
public:
static sp<ValueMetricProducer> createValueProducerNoConditions(
@@ -191,6 +207,13 @@ public:
}
};
+// Setup for parameterized tests.
+class ValueMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket,
+ ValueMetricProducerTest_PartialBucket,
+ testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
/*
* Tests that the first bucket works correctly
*/
@@ -325,9 +348,10 @@ TEST(ValueMetricProducerTest, TestPulledEventsNoCondition) {
EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs);
}
-TEST(ValueMetricProducerTest, TestPartialBucketCreated) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2;
EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
// Initialize bucket.
.WillOnce(Invoke([](int tagId, const ConfigKey&,
@@ -337,10 +361,12 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) {
return true;
}))
// Partial bucket.
- .WillOnce(Invoke([](int tagId, const ConfigKey&,
- vector<std::shared_ptr<LogEvent>>* data, bool) {
+ .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+ vector<std::shared_ptr<LogEvent>>* data,
+ bool) {
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5));
+ data->push_back(
+ CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5));
return true;
}));
@@ -354,19 +380,21 @@ TEST(ValueMetricProducerTest, TestPartialBucketCreated) {
valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs);
// Partial buckets created in 2nd bucket.
- valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1);
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, valueProducer->getCurrentBucketNum());
- // One full bucket and one partial bucket.
- EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
- vector<ValueBucket> buckets = valueProducer->mPastBuckets.begin()->second;
- EXPECT_EQ(2UL, buckets.size());
- // Full bucket (2 - 1)
- EXPECT_EQ(1, buckets[0].values[0].long_value);
- EXPECT_EQ(bucketSizeNs, buckets[0].mConditionTrueNs);
- // Full bucket (5 - 3)
- EXPECT_EQ(3, buckets[1].values[0].long_value);
- // partial bucket [bucket2StartTimeNs, bucket2StartTimeNs + 2]
- EXPECT_EQ(2, buckets[1].mConditionTrueNs);
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3},
+ {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs},
+ {bucketStartTimeNs, bucket2StartTimeNs},
+ {bucket2StartTimeNs, partialBucketSplitTimeNs});
}
/*
@@ -613,7 +641,8 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) {
allData.clear();
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
// has one slice
EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
@@ -625,7 +654,8 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) {
EXPECT_EQ(10, curInterval.value.long_value);
valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
// has one slice
EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
@@ -636,10 +666,12 @@ TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) {
EXPECT_EQ(false, curBaseInfo.hasBase);
valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1},
+ {bucketStartTimeNs, bucket2StartTimeNs},
+ {bucket2StartTimeNs, bucket3StartTimeNs});
}
-TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
@@ -660,25 +692,46 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) {
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
- valueProducer.notifyAppUpgrade(bucketStartTimeNs + 150, "ANY.APP", 1, 1);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10},
+ {partialBucketSplitTimeNs - bucketStartTimeNs},
+ {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, valueProducer.getCurrentBucketNum());
+ // Event arrives after the bucket split.
LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
+
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10},
+ {partialBucketSplitTimeNs - bucketStartTimeNs},
+ {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, valueProducer.getCurrentBucketNum());
// Next value should create a new bucket.
LogEvent event3(/*uid=*/0, /*pid=*/0);
- CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10);
+ CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
- EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20},
+ {partialBucketSplitTimeNs - bucketStartTimeNs,
+ bucket2StartTimeNs - partialBucketSplitTimeNs},
+ {bucketStartTimeNs, partialBucketSplitTimeNs},
+ {partialBucketSplitTimeNs, bucket2StartTimeNs});
EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, valueProducer.getCurrentBucketNum());
}
-TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
UidMap uidMap;
@@ -689,14 +742,16 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150;
EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
.WillOnce(Return(true))
- .WillOnce(Invoke([](int tagId, const ConfigKey&,
- vector<std::shared_ptr<LogEvent>>* data, bool) {
+ .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+ vector<std::shared_ptr<LogEvent>>* data,
+ bool) {
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 149, 120));
+ data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120));
return true;
}));
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
@@ -711,20 +766,27 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
- valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150});
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(1, valueProducer.getCurrentBucketNum());
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs},
+ {partialBucketSplitTimeNs});
allData.clear();
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150));
valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
- EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
- EXPECT_EQ(20L,
- valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value);
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30},
- {150, bucketSizeNs - 150});
+ EXPECT_EQ(2, valueProducer.getCurrentBucketNum());
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150},
+ {bucket2StartTimeNs, partialBucketSplitTimeNs},
+ {partialBucketSplitTimeNs, bucket3StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) {
@@ -754,12 +816,12 @@ TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) {
valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
- valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1);
+ valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150);
EXPECT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
}
-TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -784,14 +846,21 @@ TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) {
valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100);
EXPECT_FALSE(valueProducer->mCondition);
- valueProducer->notifyAppUpgrade(bucket2StartTimeNs - 50, "ANY.APP", 1, 1);
+ int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50;
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
// Expect one full buckets already done and starting a partial bucket.
- EXPECT_EQ(bucket2StartTimeNs - 50, valueProducer->mCurrentBucketStartTimeNs);
- EXPECT_EQ(1UL, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
- EXPECT_EQ(bucketStartTimeNs,
- valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, valueProducer->getCurrentBucketNum());
assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20},
- {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)});
+ {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)},
+ {bucketStartTimeNs}, {partialBucketSplitTimeNs});
EXPECT_FALSE(valueProducer->mCondition);
}
TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
@@ -834,7 +903,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
EXPECT_EQ(30, curInterval.value.long_value);
valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) {
@@ -895,7 +965,8 @@ TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) {
EXPECT_EQ(50, curInterval.value.long_value);
valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20});
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs},
+ {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestAnomalyDetection) {
@@ -1012,7 +1083,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) {
EXPECT_EQ(true, curBaseInfo.hasBase);
EXPECT_EQ(23, curBaseInfo.base.long_value);
EXPECT_EQ(false, curInterval.hasValue);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
+ {bucket2StartTimeNs}, {bucket3StartTimeNs});
// pull 3 come late.
// The previous bucket gets closed with error. (Has start value 23, no ending)
@@ -1028,7 +1100,15 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition) {
EXPECT_EQ(true, curBaseInfo.hasBase);
EXPECT_EQ(36, curBaseInfo.base.long_value);
EXPECT_EQ(false, curInterval.hasValue);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
+ {bucket2StartTimeNs}, {bucket3StartTimeNs});
+ // The 3rd bucket is dropped due to multiple buckets being skipped.
+ ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
+ EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+ EXPECT_EQ(bucket4StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
+ ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size());
+ EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
+ EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs);
}
/*
@@ -1073,7 +1153,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) {
valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
EXPECT_EQ(false, curBaseInfo.hasBase);
// Now the alarm is delivered.
@@ -1082,7 +1163,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) {
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -1090,10 +1172,9 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition) {
}
/*
-* Test pulled event with non sliced condition. The pull on boundary come late, after the
-condition
-* change to false, and then true again. This is due to alarm delivered late.
-*/
+ * Test pulled event with non sliced condition. The pull on boundary come late, after the condition
+ * change to false, and then true again. This is due to alarm delivered late.
+ */
TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
@@ -1139,7 +1220,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) {
// pull on bucket boundary come late, condition change happens before it
valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
@@ -1148,7 +1230,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) {
// condition changed to true again, before the pull alarm is delivered
valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
EXPECT_EQ(true, curBaseInfo.hasBase);
@@ -1167,13 +1250,15 @@ TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) {
EXPECT_EQ(140, curBaseInfo.base.long_value);
EXPECT_EQ(true, curInterval.hasValue);
EXPECT_EQ(10, curInterval.value.long_value);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
allData.clear();
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30},
- {bucketSizeNs - 8, bucketSizeNs - 24});
+ assertPastBucketValuesSingleKey(
+ valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24},
+ {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPushedAggregateMin) {
@@ -1216,7 +1301,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMin) {
EXPECT_EQ(10, curInterval.value.long_value);
valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPushedAggregateMax) {
@@ -1239,9 +1325,6 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) {
LogEvent event1(/*uid=*/0, /*pid=*/0);
CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
-
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
// has one slice
@@ -1251,6 +1334,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) {
EXPECT_EQ(10, curInterval.value.long_value);
EXPECT_EQ(true, curInterval.hasValue);
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
// has one slice
@@ -1258,10 +1343,9 @@ TEST(ValueMetricProducerTest, TestPushedAggregateMax) {
curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
EXPECT_EQ(20, curInterval.value.long_value);
- valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
- /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); */
- /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); */
- /* EXPECT_EQ(20, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); */
+ valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPushedAggregateAvg) {
@@ -1351,7 +1435,8 @@ TEST(ValueMetricProducerTest, TestPushedAggregateSum) {
EXPECT_EQ(25, curInterval.value.long_value);
valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
@@ -1375,10 +1460,8 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
LogEvent event1(/*uid=*/0, /*pid=*/0);
CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
-
- LogEvent event2(/*uid=*/0, /*pid=*/0);
- CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+
// has one slice
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
ValueMetricProducer::Interval curInterval =
@@ -1388,6 +1471,8 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
EXPECT_EQ(10, curBaseInfo.base.long_value);
EXPECT_EQ(false, curInterval.hasValue);
+ LogEvent event2(/*uid=*/0, /*pid=*/0);
+ CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
// has one slice
@@ -1400,12 +1485,14 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
LogEvent event3(/*uid=*/0, /*pid=*/0);
CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15);
valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+
EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
EXPECT_EQ(true, curBaseInfo.hasBase);
EXPECT_EQ(15, curBaseInfo.base.long_value);
EXPECT_EQ(true, curInterval.hasValue);
+ EXPECT_EQ(0, curInterval.value.long_value);
LogEvent event4(/*uid=*/0, /*pid=*/0);
CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
@@ -1416,11 +1503,11 @@ TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
EXPECT_EQ(true, curBaseInfo.hasBase);
EXPECT_EQ(15, curBaseInfo.base.long_value);
EXPECT_EQ(true, curInterval.hasValue);
+ EXPECT_EQ(0, curInterval.value.long_value);
valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
- EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
- assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) {
@@ -1740,8 +1827,8 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) {
EXPECT_EQ(3, baseInfo1.base.long_value);
EXPECT_EQ(false, interval1.hasValue);
EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
- vector<shared_ptr<LogEvent>> allData;
+ vector<shared_ptr<LogEvent>> allData;
allData.clear();
allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4));
allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11));
@@ -1753,7 +1840,8 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) {
EXPECT_EQ(false, interval1.hasValue);
EXPECT_EQ(8, interval1.value.long_value);
EXPECT_FALSE(interval1.seenNewData);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
auto it = valueProducer->mCurrentSlicedBucket.begin();
for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) {
@@ -1769,14 +1857,13 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) {
}
EXPECT_TRUE(it != iter);
EXPECT_TRUE(itBase != iterBase);
- auto& interval2 = it->second[0];
- auto& baseInfo2 = itBase->second[0];
+ auto interval2 = it->second[0];
+ auto baseInfo2 = itBase->second[0];
EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
EXPECT_EQ(true, baseInfo2.hasBase);
EXPECT_EQ(4, baseInfo2.base.long_value);
EXPECT_EQ(false, interval2.hasValue);
EXPECT_FALSE(interval2.seenNewData);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
// next pull somehow did not happen, skip to end of bucket 3
allData.clear();
@@ -1791,7 +1878,8 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) {
EXPECT_EQ(5, baseInfo2.base.long_value);
EXPECT_EQ(false, interval2.hasValue);
EXPECT_FALSE(interval2.seenNewData);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
allData.clear();
allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14));
@@ -1805,9 +1893,13 @@ TEST(ValueMetricProducerTest, TestTrimUnusedDimensionKey) {
EXPECT_FALSE(interval2.seenNewData);
ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
auto iterator = valueProducer->mPastBuckets.begin();
+ EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs);
+ EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs);
EXPECT_EQ(9, iterator->second[0].values[0].long_value);
EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
iterator++;
+ EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs);
+ EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs);
EXPECT_EQ(8, iterator->second[0].values[0].long_value);
EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
}
@@ -2414,7 +2506,8 @@ TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onBucketBoundary) {
EXPECT_EQ(true, valueProducer->mHasGlobalBase);
EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) {
@@ -2461,10 +2554,11 @@ TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) {
EXPECT_EQ(true, valueProducer->mHasGlobalBase);
}
-TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+ int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2;
EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
// Initialization.
.WillOnce(Invoke(
@@ -2474,23 +2568,41 @@ TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) {
return true;
}))
// notifyAppUpgrade.
- .WillOnce(Invoke([](int tagId, const ConfigKey&,
- vector<std::shared_ptr<LogEvent>>* data, bool) {
+ .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+ vector<std::shared_ptr<LogEvent>>* data,
+ bool) {
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(
- tagId, bucketStartTimeNs + bucketSizeNs / 2, 10));
+ data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10));
return true;
}));
sp<ValueMetricProducer> valueProducer =
ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
- valueProducer->notifyAppUpgrade(bucketStartTimeNs + bucketSizeNs / 2, "com.foo", 10000, 1);
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+ EXPECT_EQ(0, valueProducer->getCurrentBucketNum());
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
+ {partialBucketSplitTimeNs - bucketStartTimeNs},
+ {bucketStartTimeNs}, {partialBucketSplitTimeNs});
ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size());
vector<shared_ptr<LogEvent>> allData;
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4));
valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1);
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
+ {partialBucketSplitTimeNs - bucketStartTimeNs},
+ {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+ ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
+ EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+ EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
}
@@ -2537,7 +2649,8 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange) {
valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10);
// Bucket should have been completed.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10},
+ {bucket2StartTimeNs}, {bucket3StartTimeNs});
}
TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) {
@@ -2557,7 +2670,8 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) {
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
// Bucket should have been completed.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) {
@@ -2585,12 +2699,14 @@ TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) {
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
// Bucket should have been completed.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
-TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) {
ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
+ int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2;
sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
// Initialization.
@@ -2601,20 +2717,29 @@ TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) {
return true;
}))
// notifyAppUpgrade.
- .WillOnce(Invoke([](int tagId, const ConfigKey&,
- vector<std::shared_ptr<LogEvent>>* data, bool) {
+ .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+ vector<std::shared_ptr<LogEvent>>* data,
+ bool) {
data->clear();
- data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 2, 10));
+ data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10));
return true;
}));
sp<ValueMetricProducer> valueProducer =
ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
- valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1);
+ switch (GetParam()) {
+ case APP_UPGRADE:
+ valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+ break;
+ case BOOT_COMPLETE:
+ valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+ break;
+ }
// Bucket should have been completed.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) {
@@ -2642,7 +2767,7 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) {
valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
- valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
+ valueProducer->onConditionChanged(false, bucketStartTimeNs + 12);
EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
@@ -2654,7 +2779,8 @@ TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) {
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs},
+ {bucket2StartTimeNs});
}
// TODO: b/145705635 fix or delete this test
@@ -2705,7 +2831,7 @@ TEST(ValueMetricProducerTest, TestBucketInvalidIfGlobalBaseIsNotSet) {
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
// There was not global base available so all buckets are invalid.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
}
TEST(ValueMetricProducerTest, TestPullNeededFastDump) {
@@ -2849,7 +2975,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withoutCondition) {
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30);
// Bucket should have been completed.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
}
TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) {
@@ -2892,7 +3019,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -2923,7 +3051,8 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue) {
allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30));
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8},
+ {bucketStartTimeNs}, {bucket2StartTimeNs});
ValueMetricProducer::Interval curInterval =
valueProducer->mCurrentSlicedBucket.begin()->second[0];
ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
@@ -2946,7 +3075,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse) {
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
// Condition was always false.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
}
TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) {
@@ -2976,7 +3105,7 @@ TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) {
valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
// No buckets, we had a failure.
- assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+ assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
}
/*
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 37b98891797a..4d68ea2ecb79 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -42,6 +42,8 @@ using Status = ::ndk::ScopedAStatus;
const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED;
const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED;
+enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE };
+
// Converts a ProtoOutputStream to a StatsLogReport proto.
StatsLogReport outputStreamToProto(ProtoOutputStream* proto);
diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
new file mode 100644
index 000000000000..db402a0dd658
--- /dev/null
+++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "utils/MultiConditionTrigger.h"
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <set>
+#include <thread>
+#include <vector>
+
+#ifdef __ANDROID__
+
+using namespace std;
+using std::this_thread::sleep_for;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(MultiConditionTrigger, TestMultipleConditions) {
+ int numConditions = 5;
+ string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5";
+ set<string> conditionNames = {t1, t2, t3, t4, t5};
+
+ mutex lock;
+ condition_variable cv;
+ bool triggerCalled = false;
+
+ // Mark done as true and notify in the done.
+ MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
+ {
+ lock_guard lg(lock);
+ triggerCalled = true;
+ }
+ cv.notify_all();
+ });
+
+ vector<thread> threads;
+ vector<bool> done(numConditions, false);
+
+ int i = 0;
+ for (const string& conditionName : conditionNames) {
+ threads.emplace_back([&done, &conditionName, &trigger, i] {
+ sleep_for(chrono::milliseconds(3));
+ done[i] = true;
+ trigger.markComplete(conditionName);
+ });
+ i++;
+ }
+
+ unique_lock<mutex> unique_lk(lock);
+ cv.wait(unique_lk, [&triggerCalled] {
+ return triggerCalled;
+ });
+
+ for (i = 0; i < numConditions; i++) {
+ EXPECT_EQ(done[i], 1);
+ }
+
+ for (i = 0; i < numConditions; i++) {
+ threads[i].join();
+ }
+}
+
+TEST(MultiConditionTrigger, TestNoConditions) {
+ mutex lock;
+ condition_variable cv;
+ bool triggerCalled = false;
+
+ MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] {
+ {
+ lock_guard lg(lock);
+ triggerCalled = true;
+ }
+ cv.notify_all();
+ });
+
+ unique_lock<mutex> unique_lk(lock);
+ cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+ EXPECT_TRUE(triggerCalled);
+ // Ensure that trigger occurs immediately if no events need to be completed.
+}
+
+TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) {
+ string t1 = "t1", t2 = "t2";
+ set<string> conditionNames = {t1, t2};
+
+ mutex lock;
+ condition_variable cv;
+ bool triggerCalled = false;
+
+ MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
+ {
+ lock_guard lg(lock);
+ triggerCalled = true;
+ }
+ cv.notify_all();
+ });
+
+ trigger.markComplete(t1);
+ trigger.markComplete(t1);
+
+ // Ensure that the trigger still hasn't fired.
+ {
+ lock_guard lg(lock);
+ EXPECT_FALSE(triggerCalled);
+ }
+
+ trigger.markComplete(t2);
+ unique_lock<mutex> unique_lk(lock);
+ cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+ EXPECT_TRUE(triggerCalled);
+}
+
+TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) {
+ string t1 = "t1";
+ set<string> conditionNames = {t1};
+
+ mutex lock;
+ condition_variable cv;
+ bool triggerCalled = false;
+ int triggerCount = 0;
+
+ MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] {
+ {
+ lock_guard lg(lock);
+ triggerCount++;
+ triggerCalled = true;
+ }
+ cv.notify_all();
+ });
+
+ trigger.markComplete(t1);
+
+ // Ensure that the trigger fired.
+ {
+ unique_lock<mutex> unique_lk(lock);
+ cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+ EXPECT_TRUE(triggerCalled);
+ EXPECT_EQ(triggerCount, 1);
+ triggerCalled = false;
+ }
+
+ trigger.markComplete(t1);
+
+ // Ensure that the trigger does not fire again.
+ {
+ unique_lock<mutex> unique_lk(lock);
+ cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; });
+ EXPECT_FALSE(triggerCalled);
+ EXPECT_EQ(triggerCount, 1);
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/utils/NamedLatch_test.cpp b/cmds/statsd/tests/utils/NamedLatch_test.cpp
deleted file mode 100644
index de48a133823e..000000000000
--- a/cmds/statsd/tests/utils/NamedLatch_test.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "utils/NamedLatch.h"
-
-#include <gtest/gtest.h>
-
-#include <chrono>
-#include <set>
-#include <thread>
-#include <vector>
-
-#ifdef __ANDROID__
-
-using namespace std;
-using std::this_thread::sleep_for;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-TEST(NamedLatchTest, TestWait) {
- int numEvents = 5;
- string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5";
- set<string> eventNames = {t1, t2, t3, t4, t5};
-
- NamedLatch latch(eventNames);
- vector<thread> threads;
- vector<bool> done(numEvents, false);
-
- int i = 0;
- for (const string& eventName : eventNames) {
- threads.emplace_back([&done, &eventName, &latch, i] {
- sleep_for(chrono::milliseconds(3));
- done[i] = true;
- latch.countDown(eventName);
- });
- i++;
- }
-
- latch.wait();
-
- for (i = 0; i < numEvents; i++) {
- EXPECT_EQ(done[i], 1);
- }
-
- for (i = 0; i < numEvents; i++) {
- threads[i].join();
- }
-}
-
-TEST(NamedLatchTest, TestNoWorkers) {
- NamedLatch latch({});
- latch.wait();
- // Ensure that latch does not wait if no events need to countDown.
-}
-
-TEST(NamedLatchTest, TestCountDownCalledBySameEventName) {
- string t1 = "t1", t2 = "t2";
- set<string> eventNames = {t1, t2};
-
- NamedLatch latch(eventNames);
-
- thread waiterThread([&latch] { latch.wait(); });
-
- latch.countDown(t1);
- latch.countDown(t1);
-
- // Ensure that the latch's remaining threads still has t2.
- latch.mMutex.lock();
- ASSERT_EQ(latch.mRemainingEventNames.size(), 1);
- EXPECT_NE(latch.mRemainingEventNames.find(t2), latch.mRemainingEventNames.end());
- latch.mMutex.unlock();
-
- latch.countDown(t2);
- waiterThread.join();
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 67334f554df7..18e5c3d2d7cc 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -494,6 +494,13 @@ public abstract class AccessibilityService extends Service {
*/
public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
+ /**
+ * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+ * play/stop media
+ * @hide
+ */
+ public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
+
private static final String LOG_TAG = "AccessibilityService";
/**
diff --git a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
index 9a3dad2eb92f..623734ee5662 100644
--- a/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -30,7 +30,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Xml;
@@ -153,7 +152,7 @@ public final class AccessibilityShortcutInfo {
com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity);
asAttributes.recycle();
- if (mDescriptionResId == 0 || mSummaryResId == 0) {
+ if ((mDescriptionResId == 0 && mHtmlDescriptionRes == 0) || mSummaryResId == 0) {
throw new XmlPullParserException("No description or summary in meta-data");
}
} catch (PackageManager.NameNotFoundException e) {
@@ -243,7 +242,10 @@ public final class AccessibilityShortcutInfo {
public String loadHtmlDescription(@NonNull PackageManager packageManager) {
final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
mHtmlDescriptionRes);
- return TextUtils.isEmpty(htmlDescription) ? null : getFilteredHtmlText(htmlDescription);
+ if (htmlDescription != null) {
+ return getFilteredHtmlText(htmlDescription);
+ }
+ return null;
}
/**
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 1b941defba42..1cc63da3db0a 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -23,8 +23,10 @@ import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -430,4 +432,14 @@ public class ActivityTaskManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */
+ public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) {
+ final Configuration config = context.getResources().getConfiguration();
+ int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
+ return (modeType != Configuration.UI_MODE_TYPE_CAR
+ && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
+ && modeType != Configuration.UI_MODE_TYPE_TELEVISION
+ && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
+ }
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8edf03d033f8..af027837ec91 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2301,11 +2301,11 @@ public class Notification implements Parcelable
priority = parcel.readInt();
- category = parcel.readString();
+ category = parcel.readString8();
- mGroupKey = parcel.readString();
+ mGroupKey = parcel.readString8();
- mSortKey = parcel.readString();
+ mSortKey = parcel.readString8();
extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
fixDuplicateExtras();
@@ -2329,12 +2329,12 @@ public class Notification implements Parcelable
color = parcel.readInt();
if (parcel.readInt() != 0) {
- mChannelId = parcel.readString();
+ mChannelId = parcel.readString8();
}
mTimeout = parcel.readLong();
if (parcel.readInt() != 0) {
- mShortcutId = parcel.readString();
+ mShortcutId = parcel.readString8();
}
if (parcel.readInt() != 0) {
@@ -2766,11 +2766,11 @@ public class Notification implements Parcelable
parcel.writeInt(priority);
- parcel.writeString(category);
+ parcel.writeString8(category);
- parcel.writeString(mGroupKey);
+ parcel.writeString8(mGroupKey);
- parcel.writeString(mSortKey);
+ parcel.writeString8(mSortKey);
parcel.writeBundle(extras); // null ok
@@ -2803,7 +2803,7 @@ public class Notification implements Parcelable
if (mChannelId != null) {
parcel.writeInt(1);
- parcel.writeString(mChannelId);
+ parcel.writeString8(mChannelId);
} else {
parcel.writeInt(0);
}
@@ -2811,7 +2811,7 @@ public class Notification implements Parcelable
if (mShortcutId != null) {
parcel.writeInt(1);
- parcel.writeString(mShortcutId);
+ parcel.writeString8(mShortcutId);
} else {
parcel.writeInt(0);
}
@@ -8873,7 +8873,7 @@ public class Notification implements Parcelable
}
mDesiredHeightResId = in.readInt();
if (in.readInt() != 0) {
- mShortcutId = in.readString();
+ mShortcutId = in.readString8();
}
}
@@ -9029,7 +9029,7 @@ public class Notification implements Parcelable
out.writeInt(mDesiredHeightResId);
out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
if (!TextUtils.isEmpty(mShortcutId)) {
- out.writeString(mShortcutId);
+ out.writeString8(mShortcutId);
}
}
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 2feb9277775c..9f8d3c4090d6 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -102,7 +102,7 @@ public final class NotificationChannel implements Parcelable {
private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
private static final String ATT_GROUP = "group";
private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
- private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
+ private static final String ATT_ALLOW_BUBBLE = "allow_bubbles";
private static final String ATT_ORIG_IMP = "orig_imp";
private static final String ATT_PARENT_CHANNEL = "parent";
private static final String ATT_CONVERSATION_ID = "conv_id";
@@ -168,7 +168,12 @@ public final class NotificationChannel implements Parcelable {
NotificationManager.IMPORTANCE_UNSPECIFIED;
private static final boolean DEFAULT_DELETED = false;
private static final boolean DEFAULT_SHOW_BADGE = true;
- private static final boolean DEFAULT_ALLOW_BUBBLE = false;
+ /**
+ * @hide
+ */
+ public static final int DEFAULT_ALLOW_BUBBLE = -1;
+ private static final int ALLOW_BUBBLE_ON = 1;
+ private static final int ALLOW_BUBBLE_OFF = 0;
@UnsupportedAppUsage
private String mId;
@@ -193,7 +198,7 @@ public final class NotificationChannel implements Parcelable {
private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
// If this is a blockable system notification channel.
private boolean mBlockableSystem = false;
- private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
+ private int mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
private boolean mImportanceLockedByOEM;
private boolean mImportanceLockedDefaultApp;
private String mParentId = null;
@@ -261,7 +266,7 @@ public final class NotificationChannel implements Parcelable {
mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
mLightColor = in.readInt();
mBlockableSystem = in.readBoolean();
- mAllowBubbles = in.readBoolean();
+ mAllowBubbles = in.readInt();
mImportanceLockedByOEM = in.readBoolean();
mOriginalImportance = in.readInt();
mParentId = in.readString();
@@ -320,7 +325,7 @@ public final class NotificationChannel implements Parcelable {
}
dest.writeInt(mLightColor);
dest.writeBoolean(mBlockableSystem);
- dest.writeBoolean(mAllowBubbles);
+ dest.writeInt(mAllowBubbles);
dest.writeBoolean(mImportanceLockedByOEM);
dest.writeInt(mOriginalImportance);
dest.writeString(mParentId);
@@ -550,7 +555,14 @@ public final class NotificationChannel implements Parcelable {
* @see Notification#getBubbleMetadata()
*/
public void setAllowBubbles(boolean allowBubbles) {
- mAllowBubbles = allowBubbles;
+ mAllowBubbles = allowBubbles ? ALLOW_BUBBLE_ON : ALLOW_BUBBLE_OFF;
+ }
+
+ /**
+ * @hide
+ */
+ public void setAllowBubbles(int allowed) {
+ mAllowBubbles = allowed;
}
/**
@@ -701,6 +713,13 @@ public final class NotificationChannel implements Parcelable {
* @see Notification#getBubbleMetadata()
*/
public boolean canBubble() {
+ return mAllowBubbles == ALLOW_BUBBLE_ON;
+ }
+
+ /**
+ * @hide
+ */
+ public int getAllowBubbles() {
return mAllowBubbles;
}
@@ -872,7 +891,7 @@ public final class NotificationChannel implements Parcelable {
lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
- setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
+ setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
parser.getAttributeValue(null, ATT_CONVERSATION_ID));
@@ -996,8 +1015,8 @@ public final class NotificationChannel implements Parcelable {
if (isBlockable()) {
out.attribute(null, ATT_BLOCKABLE_SYSTEM, Boolean.toString(isBlockable()));
}
- if (canBubble() != DEFAULT_ALLOW_BUBBLE) {
- out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble()));
+ if (getAllowBubbles() != DEFAULT_ALLOW_BUBBLE) {
+ out.attribute(null, ATT_ALLOW_BUBBLE, Integer.toString(getAllowBubbles()));
}
if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
@@ -1059,7 +1078,7 @@ public final class NotificationChannel implements Parcelable {
record.put(ATT_DELETED, Boolean.toString(isDeleted()));
record.put(ATT_GROUP, getGroup());
record.put(ATT_BLOCKABLE_SYSTEM, isBlockable());
- record.put(ATT_ALLOW_BUBBLE, canBubble());
+ record.put(ATT_ALLOW_BUBBLE, getAllowBubbles());
// TODO: original importance
return record;
}
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 0173731995dd..c7a2a1e11c9e 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -20,6 +20,7 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
@@ -196,6 +197,20 @@ public class TaskInfo {
return resizeMode != RESIZE_MODE_UNRESIZEABLE;
}
+ /** @hide */
+ @NonNull
+ @TestApi
+ public WindowContainerToken getToken() {
+ return token;
+ }
+
+ /** @hide */
+ @NonNull
+ @TestApi
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
/**
* Reads the TaskInfo from a parcel.
*/
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 9c806fa286bc..1923bf3c5a23 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -1206,7 +1206,7 @@ public class ClipData implements Parcelable {
}
} else {
dest.writeInt(PARCEL_TYPE_STRING);
- dest.writeString(text);
+ dest.writeString8(text);
}
}
@@ -1215,7 +1215,7 @@ public class ClipData implements Parcelable {
*/
private static String readHtmlTextFromParcel(Parcel in) {
if (in.readInt() == PARCEL_TYPE_STRING) {
- return in.readString();
+ return in.readString8();
}
ParcelFileDescriptor pfd =
in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 494d2aeaf42a..1fb426eb3cfc 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -91,8 +91,8 @@ public class ContentProviderOperation implements Parcelable {
private ContentProviderOperation(Parcel source) {
mType = source.readInt();
mUri = Uri.CREATOR.createFromParcel(source);
- mMethod = source.readInt() != 0 ? source.readString() : null;
- mArg = source.readInt() != 0 ? source.readString() : null;
+ mMethod = source.readInt() != 0 ? source.readString8() : null;
+ mArg = source.readInt() != 0 ? source.readString8() : null;
final int valuesSize = source.readInt();
if (valuesSize != -1) {
mValues = new ArrayMap<>(valuesSize);
@@ -107,7 +107,7 @@ public class ContentProviderOperation implements Parcelable {
} else {
mExtras = null;
}
- mSelection = source.readInt() != 0 ? source.readString() : null;
+ mSelection = source.readInt() != 0 ? source.readString8() : null;
mSelectionArgs = source.readSparseArray(null);
mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
mYieldAllowed = source.readInt() != 0;
@@ -135,13 +135,13 @@ public class ContentProviderOperation implements Parcelable {
Uri.writeToParcel(dest, mUri);
if (mMethod != null) {
dest.writeInt(1);
- dest.writeString(mMethod);
+ dest.writeString8(mMethod);
} else {
dest.writeInt(0);
}
if (mArg != null) {
dest.writeInt(1);
- dest.writeString(mArg);
+ dest.writeString8(mArg);
} else {
dest.writeInt(0);
}
@@ -159,7 +159,7 @@ public class ContentProviderOperation implements Parcelable {
}
if (mSelection != null) {
dest.writeInt(1);
- dest.writeString(mSelection);
+ dest.writeString8(mSelection);
} else {
dest.writeInt(0);
}
@@ -591,7 +591,7 @@ public class ContentProviderOperation implements Parcelable {
public BackReference(Parcel src) {
this.fromIndex = src.readInt();
if (src.readInt() != 0) {
- this.fromKey = src.readString();
+ this.fromKey = src.readString8();
} else {
this.fromKey = null;
}
@@ -620,7 +620,7 @@ public class ContentProviderOperation implements Parcelable {
dest.writeInt(fromIndex);
if (fromKey != null) {
dest.writeInt(1);
- dest.writeString(fromKey);
+ dest.writeString8(fromKey);
} else {
dest.writeInt(0);
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b1d6c830d3b7..def150ab49e5 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -888,8 +888,8 @@ public class Intent implements Parcelable, Cloneable {
public ShortcutIconResource createFromParcel(Parcel source) {
ShortcutIconResource icon = new ShortcutIconResource();
- icon.packageName = source.readString();
- icon.resourceName = source.readString();
+ icon.packageName = source.readString8();
+ icon.resourceName = source.readString8();
return icon;
}
@@ -906,8 +906,8 @@ public class Intent implements Parcelable, Cloneable {
}
public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(packageName);
- dest.writeString(resourceName);
+ dest.writeString8(packageName);
+ dest.writeString8(resourceName);
}
@Override
@@ -10807,12 +10807,12 @@ public class Intent implements Parcelable, Cloneable {
}
public void writeToParcel(Parcel out, int flags) {
- out.writeString(mAction);
+ out.writeString8(mAction);
Uri.writeToParcel(out, mData);
- out.writeString(mType);
- out.writeString(mIdentifier);
+ out.writeString8(mType);
+ out.writeString8(mIdentifier);
out.writeInt(mFlags);
- out.writeString(mPackage);
+ out.writeString8(mPackage);
ComponentName.writeToParcel(mComponent, out);
if (mSourceBounds != null) {
@@ -10826,7 +10826,7 @@ public class Intent implements Parcelable, Cloneable {
final int N = mCategories.size();
out.writeInt(N);
for (int i=0; i<N; i++) {
- out.writeString(mCategories.valueAt(i));
+ out.writeString8(mCategories.valueAt(i));
}
} else {
out.writeInt(0);
@@ -10865,12 +10865,12 @@ public class Intent implements Parcelable, Cloneable {
}
public void readFromParcel(Parcel in) {
- setAction(in.readString());
+ setAction(in.readString8());
mData = Uri.CREATOR.createFromParcel(in);
- mType = in.readString();
- mIdentifier = in.readString();
+ mType = in.readString8();
+ mIdentifier = in.readString8();
mFlags = in.readInt();
- mPackage = in.readString();
+ mPackage = in.readString8();
mComponent = ComponentName.readFromParcel(in);
if (in.readInt() != 0) {
@@ -10882,7 +10882,7 @@ public class Intent implements Parcelable, Cloneable {
mCategories = new ArraySet<String>();
int i;
for (i=0; i<N; i++) {
- mCategories.add(in.readString().intern());
+ mCategories.add(in.readString8().intern());
}
} else {
mCategories = null;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index f25ce76d9365..b1f88693d9c0 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1206,17 +1206,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
dest.writeInt(theme);
dest.writeInt(launchMode);
dest.writeInt(documentLaunchMode);
- dest.writeString(permission);
- dest.writeString(taskAffinity);
- dest.writeString(targetActivity);
- dest.writeString(launchToken);
+ dest.writeString8(permission);
+ dest.writeString8(taskAffinity);
+ dest.writeString8(targetActivity);
+ dest.writeString8(launchToken);
dest.writeInt(flags);
dest.writeInt(privateFlags);
dest.writeInt(screenOrientation);
dest.writeInt(configChanges);
dest.writeInt(softInputMode);
dest.writeInt(uiOptions);
- dest.writeString(parentActivityName);
+ dest.writeString8(parentActivityName);
dest.writeInt(persistableMode);
dest.writeInt(maxRecents);
dest.writeInt(lockTaskLaunchMode);
@@ -1227,7 +1227,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
dest.writeInt(0);
}
dest.writeInt(resizeMode);
- dest.writeString(requestedVrComponent);
+ dest.writeString8(requestedVrComponent);
dest.writeInt(rotationAnimation);
dest.writeInt(colorMode);
dest.writeFloat(maxAspectRatio);
@@ -1327,17 +1327,17 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
theme = source.readInt();
launchMode = source.readInt();
documentLaunchMode = source.readInt();
- permission = source.readString();
- taskAffinity = source.readString();
- targetActivity = source.readString();
- launchToken = source.readString();
+ permission = source.readString8();
+ taskAffinity = source.readString8();
+ targetActivity = source.readString8();
+ launchToken = source.readString8();
flags = source.readInt();
privateFlags = source.readInt();
screenOrientation = source.readInt();
configChanges = source.readInt();
softInputMode = source.readInt();
uiOptions = source.readInt();
- parentActivityName = source.readString();
+ parentActivityName = source.readString8();
persistableMode = source.readInt();
maxRecents = source.readInt();
lockTaskLaunchMode = source.readInt();
@@ -1345,7 +1345,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
windowLayout = new WindowLayout(source);
}
resizeMode = source.readInt();
- requestedVrComponent = source.readString();
+ requestedVrComponent = source.readString8();
rotationAnimation = source.readInt();
colorMode = source.readInt();
maxAspectRatio = source.readFloat();
@@ -1386,7 +1386,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
gravity = source.readInt();
minWidth = source.readInt();
minHeight = source.readInt();
- windowLayoutAffinity = source.readString();
+ windowLayoutAffinity = source.readString8();
}
/**
@@ -1476,7 +1476,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
dest.writeInt(gravity);
dest.writeInt(minWidth);
dest.writeInt(minHeight);
- dest.writeString(windowLayoutAffinity);
+ dest.writeString8(windowLayoutAffinity);
}
}
}
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b67060111785..b37521b1189b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1704,10 +1704,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
return;
}
super.writeToParcel(dest, parcelableFlags);
- dest.writeString(taskAffinity);
- dest.writeString(permission);
- dest.writeString(processName);
- dest.writeString(className);
+ dest.writeString8(taskAffinity);
+ dest.writeString8(permission);
+ dest.writeString8(processName);
+ dest.writeString8(className);
dest.writeInt(theme);
dest.writeInt(flags);
dest.writeInt(privateFlags);
@@ -1721,28 +1721,28 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
} else {
dest.writeInt(0);
}
- dest.writeString(scanSourceDir);
- dest.writeString(scanPublicSourceDir);
- dest.writeString(sourceDir);
- dest.writeString(publicSourceDir);
+ dest.writeString8(scanSourceDir);
+ dest.writeString8(scanPublicSourceDir);
+ dest.writeString8(sourceDir);
+ dest.writeString8(publicSourceDir);
dest.writeStringArray(splitNames);
dest.writeStringArray(splitSourceDirs);
dest.writeStringArray(splitPublicSourceDirs);
dest.writeSparseArray((SparseArray) splitDependencies);
- dest.writeString(nativeLibraryDir);
- dest.writeString(secondaryNativeLibraryDir);
- dest.writeString(nativeLibraryRootDir);
+ dest.writeString8(nativeLibraryDir);
+ dest.writeString8(secondaryNativeLibraryDir);
+ dest.writeString8(nativeLibraryRootDir);
dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0);
- dest.writeString(primaryCpuAbi);
- dest.writeString(secondaryCpuAbi);
+ dest.writeString8(primaryCpuAbi);
+ dest.writeString8(secondaryCpuAbi);
dest.writeStringArray(resourceDirs);
- dest.writeString(seInfo);
- dest.writeString(seInfoUser);
+ dest.writeString8(seInfo);
+ dest.writeString8(seInfoUser);
dest.writeStringArray(sharedLibraryFiles);
dest.writeTypedList(sharedLibraryInfos);
- dest.writeString(dataDir);
- dest.writeString(deviceProtectedDataDir);
- dest.writeString(credentialProtectedDataDir);
+ dest.writeString8(dataDir);
+ dest.writeString8(deviceProtectedDataDir);
+ dest.writeString8(credentialProtectedDataDir);
dest.writeInt(uid);
dest.writeInt(minSdkVersion);
dest.writeInt(targetSdkVersion);
@@ -1750,8 +1750,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(enabledSetting);
dest.writeInt(installLocation);
- dest.writeString(manageSpaceActivityName);
- dest.writeString(backupAgentName);
+ dest.writeString8(manageSpaceActivityName);
+ dest.writeString8(backupAgentName);
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
dest.writeInt(fullBackupContent);
@@ -1759,16 +1759,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeInt(networkSecurityConfigRes);
dest.writeInt(category);
dest.writeInt(targetSandboxVersion);
- dest.writeString(classLoaderName);
+ dest.writeString8(classLoaderName);
dest.writeStringArray(splitClassLoaderNames);
dest.writeInt(compileSdkVersion);
- dest.writeString(compileSdkVersionCodename);
- dest.writeString(appComponentFactory);
+ dest.writeString8(compileSdkVersionCodename);
+ dest.writeString8(appComponentFactory);
dest.writeInt(iconRes);
dest.writeInt(roundIconRes);
dest.writeInt(mHiddenApiPolicy);
dest.writeInt(hiddenUntilInstalled ? 1 : 0);
- dest.writeString(zygotePreloadName);
+ dest.writeString8(zygotePreloadName);
dest.writeInt(gwpAsanMode);
}
@@ -1788,10 +1788,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
@SuppressWarnings("unchecked")
private ApplicationInfo(Parcel source) {
super(source);
- taskAffinity = source.readString();
- permission = source.readString();
- processName = source.readString();
- className = source.readString();
+ taskAffinity = source.readString8();
+ permission = source.readString8();
+ processName = source.readString8();
+ className = source.readString8();
theme = source.readInt();
flags = source.readInt();
privateFlags = source.readInt();
@@ -1802,28 +1802,28 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
storageUuid = new UUID(source.readLong(), source.readLong());
volumeUuid = StorageManager.convert(storageUuid);
}
- scanSourceDir = source.readString();
- scanPublicSourceDir = source.readString();
- sourceDir = source.readString();
- publicSourceDir = source.readString();
+ scanSourceDir = source.readString8();
+ scanPublicSourceDir = source.readString8();
+ sourceDir = source.readString8();
+ publicSourceDir = source.readString8();
splitNames = source.readStringArray();
splitSourceDirs = source.readStringArray();
splitPublicSourceDirs = source.readStringArray();
splitDependencies = source.readSparseArray(null);
- nativeLibraryDir = source.readString();
- secondaryNativeLibraryDir = source.readString();
- nativeLibraryRootDir = source.readString();
+ nativeLibraryDir = source.readString8();
+ secondaryNativeLibraryDir = source.readString8();
+ nativeLibraryRootDir = source.readString8();
nativeLibraryRootRequiresIsa = source.readInt() != 0;
- primaryCpuAbi = source.readString();
- secondaryCpuAbi = source.readString();
+ primaryCpuAbi = source.readString8();
+ secondaryCpuAbi = source.readString8();
resourceDirs = source.readStringArray();
- seInfo = source.readString();
- seInfoUser = source.readString();
+ seInfo = source.readString8();
+ seInfoUser = source.readString8();
sharedLibraryFiles = source.readStringArray();
sharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR);
- dataDir = source.readString();
- deviceProtectedDataDir = source.readString();
- credentialProtectedDataDir = source.readString();
+ dataDir = source.readString8();
+ deviceProtectedDataDir = source.readString8();
+ credentialProtectedDataDir = source.readString8();
uid = source.readInt();
minSdkVersion = source.readInt();
targetSdkVersion = source.readInt();
@@ -1831,8 +1831,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
enabled = source.readInt() != 0;
enabledSetting = source.readInt();
installLocation = source.readInt();
- manageSpaceActivityName = source.readString();
- backupAgentName = source.readString();
+ manageSpaceActivityName = source.readString8();
+ backupAgentName = source.readString8();
descriptionRes = source.readInt();
uiOptions = source.readInt();
fullBackupContent = source.readInt();
@@ -1840,16 +1840,16 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
networkSecurityConfigRes = source.readInt();
category = source.readInt();
targetSandboxVersion = source.readInt();
- classLoaderName = source.readString();
+ classLoaderName = source.readString8();
splitClassLoaderNames = source.readStringArray();
compileSdkVersion = source.readInt();
- compileSdkVersionCodename = source.readString();
- appComponentFactory = source.readString();
+ compileSdkVersionCodename = source.readString8();
+ appComponentFactory = source.readString8();
iconRes = source.readInt();
roundIconRes = source.readInt();
mHiddenApiPolicy = source.readInt();
hiddenUntilInstalled = source.readInt() != 0;
- zygotePreloadName = source.readString();
+ zygotePreloadName = source.readString8();
gwpAsanMode = source.readInt();
}
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 362098c447ce..628bcd70cdf6 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -197,8 +197,8 @@ public class ComponentInfo extends PackageItemInfo {
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
applicationInfo.writeToParcel(dest, parcelableFlags);
- dest.writeString(processName);
- dest.writeString(splitName);
+ dest.writeString8(processName);
+ dest.writeString8(splitName);
dest.writeInt(descriptionRes);
dest.writeInt(enabled ? 1 : 0);
dest.writeInt(exported ? 1 : 0);
@@ -208,8 +208,8 @@ public class ComponentInfo extends PackageItemInfo {
protected ComponentInfo(Parcel source) {
super(source);
applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
- processName = source.readString();
- splitName = source.readString();
+ processName = source.readString8();
+ splitName = source.readString8();
descriptionRes = source.readInt();
enabled = (source.readInt() != 0);
exported = (source.readInt() != 0);
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index 9f3ab77062ef..89269e0d7aa5 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -108,7 +108,7 @@ public class FeatureInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int parcelableFlags) {
- dest.writeString(name);
+ dest.writeString8(name);
dest.writeInt(version);
dest.writeInt(reqGlEsVersion);
dest.writeInt(flags);
@@ -138,7 +138,7 @@ public class FeatureInfo implements Parcelable {
};
private FeatureInfo(Parcel source) {
- name = source.readString();
+ name = source.readString8();
version = source.readInt();
reqGlEsVersion = source.readInt();
flags = source.readInt();
diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
index 9819b5d4eeb9..ffe8b183e926 100644
--- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl
+++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
@@ -21,16 +21,29 @@ package android.content.pm;
* @hide
*/
oneway interface IDataLoaderStatusListener {
- /** Data loader status */
+ /** When this status is returned from DataLoader, it means that the DataLoader
+ * process is running, bound to and has handled onCreate(). */
const int DATA_LOADER_CREATED = 0;
+ /** Listener will receive this status when the DataLoader process died,
+ * binder disconnected or class destroyed. */
const int DATA_LOADER_DESTROYED = 1;
+ /** DataLoader can receive missing pages and read pages notifications,
+ * and ready to provide data. */
const int DATA_LOADER_STARTED = 2;
+ /** DataLoader no longer ready to provide data and is not receiving
+ * any notifications from IncFS. */
const int DATA_LOADER_STOPPED = 3;
+ /** DataLoader streamed everything necessary to continue installation. */
const int DATA_LOADER_IMAGE_READY = 4;
+ /** Installation can't continue as DataLoader failed to stream necessary data. */
const int DATA_LOADER_IMAGE_NOT_READY = 5;
+ /** DataLoader reports that this instance is invalid and can never be restored.
+ * Warning: this is a terminal status that data loader should use carefully and
+ * the system should almost never use - e.g. only if all recovery attempts
+ * fail and all retry limits are exceeded. */
const int DATA_LOADER_UNRECOVERABLE = 6;
/** Data loader status callback */
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 574a1ee2d0ce..745a6c1a0dff 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -157,21 +157,21 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
- dest.writeString(targetPackage);
- dest.writeString(targetProcesses);
- dest.writeString(sourceDir);
- dest.writeString(publicSourceDir);
+ dest.writeString8(targetPackage);
+ dest.writeString8(targetProcesses);
+ dest.writeString8(sourceDir);
+ dest.writeString8(publicSourceDir);
dest.writeStringArray(splitNames);
dest.writeStringArray(splitSourceDirs);
dest.writeStringArray(splitPublicSourceDirs);
dest.writeSparseArray((SparseArray) splitDependencies);
- dest.writeString(dataDir);
- dest.writeString(deviceProtectedDataDir);
- dest.writeString(credentialProtectedDataDir);
- dest.writeString(primaryCpuAbi);
- dest.writeString(secondaryCpuAbi);
- dest.writeString(nativeLibraryDir);
- dest.writeString(secondaryNativeLibraryDir);
+ dest.writeString8(dataDir);
+ dest.writeString8(deviceProtectedDataDir);
+ dest.writeString8(credentialProtectedDataDir);
+ dest.writeString8(primaryCpuAbi);
+ dest.writeString8(secondaryCpuAbi);
+ dest.writeString8(nativeLibraryDir);
+ dest.writeString8(secondaryNativeLibraryDir);
dest.writeInt((handleProfiling == false) ? 0 : 1);
dest.writeInt((functionalTest == false) ? 0 : 1);
}
@@ -189,21 +189,21 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable {
@SuppressWarnings("unchecked")
private InstrumentationInfo(Parcel source) {
super(source);
- targetPackage = source.readString();
- targetProcesses = source.readString();
- sourceDir = source.readString();
- publicSourceDir = source.readString();
+ targetPackage = source.readString8();
+ targetProcesses = source.readString8();
+ sourceDir = source.readString8();
+ publicSourceDir = source.readString8();
splitNames = source.readStringArray();
splitSourceDirs = source.readStringArray();
splitPublicSourceDirs = source.readStringArray();
splitDependencies = source.readSparseArray(null);
- dataDir = source.readString();
- deviceProtectedDataDir = source.readString();
- credentialProtectedDataDir = source.readString();
- primaryCpuAbi = source.readString();
- secondaryCpuAbi = source.readString();
- nativeLibraryDir = source.readString();
- secondaryNativeLibraryDir = source.readString();
+ dataDir = source.readString8();
+ deviceProtectedDataDir = source.readString8();
+ credentialProtectedDataDir = source.readString8();
+ primaryCpuAbi = source.readString8();
+ secondaryCpuAbi = source.readString8();
+ nativeLibraryDir = source.readString8();
+ secondaryNativeLibraryDir = source.readString8();
handleProfiling = source.readInt() != 0;
functionalTest = source.readInt() != 0;
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 85c698f3fb0c..bb56ef7fd3a0 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -441,14 +441,14 @@ public class PackageInfo implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
// Allow ApplicationInfo to be squashed.
final boolean prevAllowSquashing = dest.allowSquashing();
- dest.writeString(packageName);
+ dest.writeString8(packageName);
dest.writeStringArray(splitNames);
dest.writeInt(versionCode);
dest.writeInt(versionCodeMajor);
- dest.writeString(versionName);
+ dest.writeString8(versionName);
dest.writeInt(baseRevisionCode);
dest.writeIntArray(splitRevisionCodes);
- dest.writeString(sharedUserId);
+ dest.writeString8(sharedUserId);
dest.writeInt(sharedUserLabel);
if (applicationInfo != null) {
dest.writeInt(1);
@@ -475,14 +475,14 @@ public class PackageInfo implements Parcelable {
dest.writeInt(isStub ? 1 : 0);
dest.writeInt(coreApp ? 1 : 0);
dest.writeInt(requiredForAllUsers ? 1 : 0);
- dest.writeString(restrictedAccountType);
- dest.writeString(requiredAccountType);
- dest.writeString(overlayTarget);
- dest.writeString(overlayCategory);
+ dest.writeString8(restrictedAccountType);
+ dest.writeString8(requiredAccountType);
+ dest.writeString8(overlayTarget);
+ dest.writeString8(overlayCategory);
dest.writeInt(overlayPriority);
dest.writeBoolean(mOverlayIsStatic);
dest.writeInt(compileSdkVersion);
- dest.writeString(compileSdkVersionCodename);
+ dest.writeString8(compileSdkVersionCodename);
if (signingInfo != null) {
dest.writeInt(1);
signingInfo.writeToParcel(dest, parcelableFlags);
@@ -508,14 +508,14 @@ public class PackageInfo implements Parcelable {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private PackageInfo(Parcel source) {
- packageName = source.readString();
+ packageName = source.readString8();
splitNames = source.createStringArray();
versionCode = source.readInt();
versionCodeMajor = source.readInt();
- versionName = source.readString();
+ versionName = source.readString8();
baseRevisionCode = source.readInt();
splitRevisionCodes = source.createIntArray();
- sharedUserId = source.readString();
+ sharedUserId = source.readString8();
sharedUserLabel = source.readInt();
int hasApp = source.readInt();
if (hasApp != 0) {
@@ -540,14 +540,14 @@ public class PackageInfo implements Parcelable {
isStub = source.readInt() != 0;
coreApp = source.readInt() != 0;
requiredForAllUsers = source.readInt() != 0;
- restrictedAccountType = source.readString();
- requiredAccountType = source.readString();
- overlayTarget = source.readString();
- overlayCategory = source.readString();
+ restrictedAccountType = source.readString8();
+ requiredAccountType = source.readString8();
+ overlayTarget = source.readString8();
+ overlayCategory = source.readString8();
overlayPriority = source.readInt();
mOverlayIsStatic = source.readBoolean();
compileSdkVersion = source.readInt();
- compileSdkVersionCodename = source.readString();
+ compileSdkVersionCodename = source.readString8();
int hasSigningInfo = source.readInt();
if (hasSigningInfo != 0) {
signingInfo = SigningInfo.CREATOR.createFromParcel(source);
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 7fd5531bf20d..d41ace5bcf62 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -422,8 +422,8 @@ public class PackageItemInfo {
}
public void writeToParcel(Parcel dest, int parcelableFlags) {
- dest.writeString(name);
- dest.writeString(packageName);
+ dest.writeString8(name);
+ dest.writeString8(packageName);
dest.writeInt(labelRes);
TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
dest.writeInt(icon);
@@ -452,8 +452,8 @@ public class PackageItemInfo {
}
protected PackageItemInfo(Parcel source) {
- name = source.readString();
- packageName = source.readString();
+ name = source.readString8();
+ packageName = source.readString8();
labelRes = source.readInt();
nonLocalizedLabel
= TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1dadbda1918b..85bafd9d37e2 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1695,7 +1695,7 @@ public class PackageParser {
}
// Check to see if overlay should be excluded based on system property condition
- if (!checkRequiredSystemProperty(requiredSystemPropertyName,
+ if (!checkRequiredSystemProperties(requiredSystemPropertyName,
requiredSystemPropertyValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ codePath + ": overlay ignored due to required system property: "
@@ -1997,7 +1997,7 @@ public class PackageParser {
}
// check to see if overlay should be excluded based on system property condition
- if (!checkRequiredSystemProperty(propName, propValue)) {
+ if (!checkRequiredSystemProperties(propName, propValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + pkg.mOverlayTarget + " and "
+ pkg.baseCodePath+ ": overlay ignored due to required system property: "
+ propName + " with value: " + propValue);
@@ -2427,24 +2427,42 @@ public class PackageParser {
/**
* Returns {@code true} if both the property name and value are empty or if the given system
- * property is set to the specified value. In all other cases, returns {@code false}
+ * property is set to the specified value. Properties can be one or more, and if properties are
+ * more than one, they must be separated by comma, and count of names and values must be equal,
+ * and also every given system property must be set to the corresponding value.
+ * In all other cases, returns {@code false}
*/
- public static boolean checkRequiredSystemProperty(String propName, String propValue) {
- if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
- if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
+ public static boolean checkRequiredSystemProperties(@Nullable String rawPropNames,
+ @Nullable String rawPropValues) {
+ if (TextUtils.isEmpty(rawPropNames) || TextUtils.isEmpty(rawPropValues)) {
+ if (!TextUtils.isEmpty(rawPropNames) || !TextUtils.isEmpty(rawPropValues)) {
// malformed condition - incomplete
- Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
- + "=" + propValue + "' - require both requiredSystemPropertyName"
- + " AND requiredSystemPropertyValue to be specified.");
+ Slog.w(TAG, "Disabling overlay - incomplete property :'" + rawPropNames
+ + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue to be specified.");
return false;
}
// no valid condition set - so no exclusion criteria, overlay will be included.
return true;
}
- // check property value - make sure it is both set and equal to expected value
- final String currValue = SystemProperties.get(propName);
- return (currValue != null && currValue.equals(propValue));
+ final String[] propNames = rawPropNames.split(",");
+ final String[] propValues = rawPropValues.split(",");
+
+ if (propNames.length != propValues.length) {
+ Slog.w(TAG, "Disabling overlay - property :'" + rawPropNames
+ + "=" + rawPropValues + "' - require both requiredSystemPropertyName"
+ + " AND requiredSystemPropertyValue lists to have the same size.");
+ return false;
+ }
+ for (int i = 0; i < propNames.length; i++) {
+ // Check property value: make sure it is both set and equal to expected value
+ final String currValue = SystemProperties.get(propNames[i]);
+ if (!TextUtils.equals(currValue, propValues[i])) {
+ return false;
+ }
+ }
+ return true;
}
/**
diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java
index 44def3321c34..8212224e114c 100644
--- a/core/java/android/content/pm/PackageParserCacheHelper.java
+++ b/core/java/android/content/pm/PackageParserCacheHelper.java
@@ -78,10 +78,19 @@ public class PackageParserCacheHelper {
/**
* Read an string index from a parcel, and returns the corresponding string from the pool.
*/
- @Override
public String readString(Parcel p) {
return mStrings.get(p.readInt());
}
+
+ @Override
+ public String readString8(Parcel p) {
+ return readString(p);
+ }
+
+ @Override
+ public String readString16(Parcel p) {
+ return readString(p);
+ }
}
/**
@@ -110,7 +119,6 @@ public class PackageParserCacheHelper {
* Instead of writing a string directly to a parcel, this method adds it to the pool,
* and write the index in the pool to the parcel.
*/
- @Override
public void writeString(Parcel p, String s) {
final Integer cur = mIndexes.get(s);
if (cur != null) {
@@ -133,6 +141,16 @@ public class PackageParserCacheHelper {
}
}
+ @Override
+ public void writeString8(Parcel p, String s) {
+ writeString(p, s);
+ }
+
+ @Override
+ public void writeString16(Parcel p, String s) {
+ writeString(p, s);
+ }
+
/**
* Closes a parcel by appending the string pool at the end and updating the pool offset,
* which it assumes is at the first byte. It also uninstalls itself as a read-write helper.
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 07d42dc823c4..3984ade73d6c 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -145,9 +145,9 @@ public final class ProviderInfo extends ComponentInfo
@Override public void writeToParcel(Parcel out, int parcelableFlags) {
super.writeToParcel(out, parcelableFlags);
- out.writeString(authority);
- out.writeString(readPermission);
- out.writeString(writePermission);
+ out.writeString8(authority);
+ out.writeString8(readPermission);
+ out.writeString8(writePermission);
out.writeInt(grantUriPermissions ? 1 : 0);
out.writeInt(forceUriPermissions ? 1 : 0);
out.writeTypedArray(uriPermissionPatterns, parcelableFlags);
@@ -175,9 +175,9 @@ public final class ProviderInfo extends ComponentInfo
private ProviderInfo(Parcel in) {
super(in);
- authority = in.readString();
- readPermission = in.readString();
- writePermission = in.readString();
+ authority = in.readString8();
+ readPermission = in.readString8();
+ writePermission = in.readString8();
grantUriPermissions = in.readInt() != 0;
forceUriPermissions = in.readInt() != 0;
uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 5f90b6c43c60..d3f9e2486a64 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -244,7 +244,7 @@ public class ServiceInfo extends ComponentInfo
public void writeToParcel(Parcel dest, int parcelableFlags) {
super.writeToParcel(dest, parcelableFlags);
- dest.writeString(permission);
+ dest.writeString8(permission);
dest.writeInt(flags);
dest.writeInt(mForegroundServiceType);
}
@@ -261,7 +261,7 @@ public class ServiceInfo extends ComponentInfo
private ServiceInfo(Parcel source) {
super(source);
- permission = source.readString();
+ permission = source.readString8();
flags = source.readInt();
mForegroundServiceType = source.readInt();
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 27399e4b39bc..2f416a2538ba 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -413,7 +413,7 @@ public class ApkLiteParseUtils {
}
// Check to see if overlay should be excluded based on system property condition
- if (!PackageParser.checkRequiredSystemProperty(requiredSystemPropertyName,
+ if (!PackageParser.checkRequiredSystemProperties(requiredSystemPropertyName,
requiredSystemPropertyValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + targetPackage + " and "
+ codePath + ": overlay ignored due to required system property: "
diff --git a/core/java/android/content/pm/parsing/ParsingPackageUtils.java b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
index 29ece4924e5c..88f4c31b82cc 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageUtils.java
@@ -84,7 +84,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.FileUtils;
import android.os.RemoteException;
-import android.os.SystemProperties;
import android.os.Trace;
import android.os.ext.SdkExtensions;
import android.text.TextUtils;
@@ -2348,7 +2347,7 @@ public class ParsingPackageUtils {
R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyName);
String propValue = sa.getString(
R.styleable.AndroidManifestResourceOverlay_requiredSystemPropertyValue);
- if (!checkOverlayRequiredSystemProperty(propName, propValue)) {
+ if (!PackageParser.checkRequiredSystemProperties(propName, propValue)) {
Slog.i(TAG, "Skipping target and overlay pair " + target + " and "
+ pkg.getBaseCodePath()
+ ": overlay ignored due to required system property: "
@@ -2522,24 +2521,6 @@ public class ParsingPackageUtils {
}
}
- private static boolean checkOverlayRequiredSystemProperty(String propName, String propValue) {
- if (TextUtils.isEmpty(propName) || TextUtils.isEmpty(propValue)) {
- if (!TextUtils.isEmpty(propName) || !TextUtils.isEmpty(propValue)) {
- // malformed condition - incomplete
- Slog.w(TAG, "Disabling overlay - incomplete property :'" + propName
- + "=" + propValue + "' - require both requiredSystemPropertyName"
- + " AND requiredSystemPropertyValue to be specified.");
- return false;
- }
- // no valid condition set - so no exclusion criteria, overlay will be included.
- return true;
- }
-
- // check property value - make sure it is both set and equal to expected value
- final String currValue = SystemProperties.get(propName);
- return (currValue != null && currValue.equals(propValue));
- }
-
/**
* This is a pre-density application which will get scaled - instead of being pixel perfect.
* This type of application is not resizable.
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index f7c96a3a02c1..2f67f6ddc082 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -228,19 +228,26 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen
} catch (SQLiteCantOpenDatabaseException e) {
String message = String.format("Cannot open database '%s'", file);
- final Path path = FileSystems.getDefault().getPath(file);
- final Path dir = path.getParent();
-
- if (!Files.isDirectory(dir)) {
- message += ": Directory " + dir + " doesn't exist";
- } else if (!Files.exists(path)) {
- message += ": File " + path + " doesn't exist";
- } else if (!Files.isReadable(path)) {
- message += ": File " + path + " is not readable";
- } else if (Files.isDirectory(path)) {
- message += ": Path " + path + " is a directory";
- } else {
- message += ": Unknown reason";
+ try {
+ // Try to diagnose for common reasons. If something fails in here, that's fine;
+ // just swallow the exception.
+
+ final Path path = FileSystems.getDefault().getPath(file);
+ final Path dir = path.getParent();
+
+ if (!Files.isDirectory(dir)) {
+ message += ": Directory " + dir + " doesn't exist";
+ } else if (!Files.exists(path)) {
+ message += ": File " + path + " doesn't exist";
+ } else if (!Files.isReadable(path)) {
+ message += ": File " + path + " is not readable";
+ } else if (Files.isDirectory(path)) {
+ message += ": Path " + path + " is a directory";
+ } else {
+ message += ": Unknown reason";
+ }
+ } catch (Throwable th) {
+ message += ": Unknown reason; cannot examine filesystem: " + th.getMessage();
}
throw new SQLiteCantOpenDatabaseException(message, e);
} finally {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 462110f5a795..ad9bf0745779 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -65,21 +65,23 @@ public abstract class DisplayManagerInternal {
public abstract boolean isProximitySensorAvailable();
/**
- * Take a screenshot of the specified display and return a buffer.
+ * Screenshot for internal system-only use such as rotation, etc. This method includes
+ * secure layers and the result should never be exposed to non-system applications.
+ * This method does not apply any rotation and provides the output in natural orientation.
*
* @param displayId The display id to take the screenshot of.
* @return The buffer or null if we have failed.
*/
- public abstract SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId);
+ public abstract SurfaceControl.ScreenshotGraphicBuffer systemScreenshot(int displayId);
/**
- * Take a screenshot without secure layer of the specified display and return a buffer.
+ * General screenshot functionality that excludes secure layers and applies appropriate
+ * rotation that the device is currently in.
*
* @param displayId The display id to take the screenshot of.
* @return The buffer or null if we have failed.
*/
- public abstract SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(
- int displayId);
+ public abstract SurfaceControl.ScreenshotGraphicBuffer userScreenshot(int displayId);
/**
* Returns information about the specified logical display.
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 525bbfdbb06d..1cb4fe8cf4e7 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -500,7 +500,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
static Uri readFrom(Parcel parcel) {
- return new StringUri(parcel.readString());
+ return new StringUri(parcel.readString8());
}
public int describeContents() {
@@ -509,7 +509,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(TYPE_ID);
- parcel.writeString(uriString);
+ parcel.writeString8(uriString);
}
/** Cached scheme separator index. */
@@ -875,7 +875,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
static Uri readFrom(Parcel parcel) {
return new OpaqueUri(
- parcel.readString(),
+ parcel.readString8(),
Part.readFrom(parcel),
Part.readFrom(parcel)
);
@@ -887,7 +887,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(TYPE_ID);
- parcel.writeString(scheme);
+ parcel.writeString8(scheme);
ssp.writeTo(parcel);
fragment.writeTo(parcel);
}
@@ -1195,7 +1195,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
static Uri readFrom(Parcel parcel) {
return new HierarchicalUri(
- parcel.readString(),
+ parcel.readString8(),
Part.readFrom(parcel),
PathPart.readFrom(parcel),
Part.readFrom(parcel),
@@ -1209,7 +1209,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(TYPE_ID);
- parcel.writeString(scheme);
+ parcel.writeString8(scheme);
authority.writeTo(parcel);
path.writeTo(parcel);
query.writeTo(parcel);
@@ -2028,7 +2028,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
+ mCanonicalRepresentation + ")");
}
parcel.writeInt(mCanonicalRepresentation);
- parcel.writeString(canonicalValue);
+ parcel.writeString8(canonicalValue);
}
}
@@ -2060,7 +2060,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
static Part readFrom(Parcel parcel) {
int representation = parcel.readInt();
- String value = parcel.readString();
+ String value = parcel.readString8();
switch (representation) {
case REPRESENTATION_ENCODED:
return fromEncoded(value);
@@ -2251,9 +2251,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
int representation = parcel.readInt();
switch (representation) {
case REPRESENTATION_ENCODED:
- return fromEncoded(parcel.readString());
+ return fromEncoded(parcel.readString8());
case REPRESENTATION_DECODED:
- return fromDecoded(parcel.readString());
+ return fromDecoded(parcel.readString8());
default:
throw new IllegalArgumentException("Unknown representation: " + representation);
}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f0b7b5fa5a1a..93f6607ff9d4 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -307,7 +307,9 @@ public final class Parcel {
@FastNative
private static native void nativeWriteDouble(long nativePtr, double val);
@FastNative
- static native void nativeWriteString(long nativePtr, String val);
+ private static native void nativeWriteString8(long nativePtr, String val);
+ @FastNative
+ private static native void nativeWriteString16(long nativePtr, String val);
@FastNative
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
@FastNative
@@ -325,7 +327,9 @@ public final class Parcel {
@CriticalNative
private static native double nativeReadDouble(long nativePtr);
@FastNative
- static native String nativeReadString(long nativePtr);
+ private static native String nativeReadString8(long nativePtr);
+ @FastNative
+ private static native String nativeReadString16(long nativePtr);
@FastNative
private static native IBinder nativeReadStrongBinder(long nativePtr);
@FastNative
@@ -386,8 +390,12 @@ public final class Parcel {
* must use {@link #writeStringNoHelper(String)} to avoid
* infinity recursive calls.
*/
- public void writeString(Parcel p, String s) {
- nativeWriteString(p.mNativePtr, s);
+ public void writeString8(Parcel p, String s) {
+ p.writeString8NoHelper(s);
+ }
+
+ public void writeString16(Parcel p, String s) {
+ p.writeString16NoHelper(s);
}
/**
@@ -395,8 +403,12 @@ public final class Parcel {
* must use {@link #readStringNoHelper()} to avoid
* infinity recursive calls.
*/
- public String readString(Parcel p) {
- return nativeReadString(p.mNativePtr);
+ public String readString8(Parcel p) {
+ return p.readString8NoHelper();
+ }
+
+ public String readString16(Parcel p) {
+ return p.readString16NoHelper();
}
}
@@ -759,7 +771,17 @@ public final class Parcel {
* growing dataCapacity() if needed.
*/
public final void writeString(@Nullable String val) {
- mReadWriteHelper.writeString(this, val);
+ writeString16(val);
+ }
+
+ /** {@hide} */
+ public final void writeString8(@Nullable String val) {
+ mReadWriteHelper.writeString8(this, val);
+ }
+
+ /** {@hide} */
+ public final void writeString16(@Nullable String val) {
+ mReadWriteHelper.writeString16(this, val);
}
/**
@@ -770,7 +792,17 @@ public final class Parcel {
* @hide
*/
public void writeStringNoHelper(@Nullable String val) {
- nativeWriteString(mNativePtr, val);
+ writeString16NoHelper(val);
+ }
+
+ /** {@hide} */
+ public void writeString8NoHelper(@Nullable String val) {
+ nativeWriteString8(mNativePtr, val);
+ }
+
+ /** {@hide} */
+ public void writeString16NoHelper(@Nullable String val) {
+ nativeWriteString16(mNativePtr, val);
}
/**
@@ -2337,7 +2369,17 @@ public final class Parcel {
*/
@Nullable
public final String readString() {
- return mReadWriteHelper.readString(this);
+ return readString16();
+ }
+
+ /** {@hide} */
+ public final @Nullable String readString8() {
+ return mReadWriteHelper.readString8(this);
+ }
+
+ /** {@hide} */
+ public final @Nullable String readString16() {
+ return mReadWriteHelper.readString16(this);
}
/**
@@ -2347,9 +2389,18 @@ public final class Parcel {
*
* @hide
*/
- @Nullable
- public String readStringNoHelper() {
- return nativeReadString(mNativePtr);
+ public @Nullable String readStringNoHelper() {
+ return readString16NoHelper();
+ }
+
+ /** {@hide} */
+ public @Nullable String readString8NoHelper() {
+ return nativeReadString8(mNativePtr);
+ }
+
+ /** {@hide} */
+ public @Nullable String readString16NoHelper() {
+ return nativeReadString16(mNativePtr);
}
/**
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index ca861577ab37..1fef07156310 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -897,6 +897,34 @@ public abstract class VibrationEffect implements Parcelable {
return -1;
}
+ /**
+ * Scale all primitives of this effect.
+ *
+ * @param gamma the gamma adjustment to apply
+ * @param maxAmplitude the new maximum amplitude of the effect, must be between 0 and
+ * MAX_AMPLITUDE
+ * @throws IllegalArgumentException if maxAmplitude less than 0 or more than MAX_AMPLITUDE
+ *
+ * @return A {@link Composed} effect with same but scaled primitives.
+ */
+ public Composed scale(float gamma, int maxAmplitude) {
+ if (maxAmplitude > MAX_AMPLITUDE || maxAmplitude < 0) {
+ throw new IllegalArgumentException(
+ "Amplitude is negative or greater than MAX_AMPLITUDE");
+ }
+ if (gamma == 1.0f && maxAmplitude == MAX_AMPLITUDE) {
+ // Just return a copy of the original if there's no scaling to be done.
+ return new Composed(mPrimitiveEffects);
+ }
+ List<Composition.PrimitiveEffect> scaledPrimitives = new ArrayList<>();
+ for (Composition.PrimitiveEffect primitive : mPrimitiveEffects) {
+ float adjustedScale = MathUtils.pow(primitive.scale, gamma);
+ float newScale = adjustedScale * maxAmplitude / (float) MAX_AMPLITUDE;
+ scaledPrimitives.add(new Composition.PrimitiveEffect(
+ primitive.id, newScale, primitive.delay));
+ }
+ return new Composed(scaledPrimitives);
+ }
/**
* @hide
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index aee32ed769ac..a1a11ed54609 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -2447,7 +2447,12 @@ public class StorageManager {
final String filePath = path.getCanonicalPath();
final StorageVolume volume = getStorageVolume(path);
if (volume == null) {
- throw new IllegalStateException("Failed to update quota type for " + filePath);
+ Log.w(TAG, "Failed to update quota type for " + filePath);
+ return;
+ }
+ if (!volume.isEmulated()) {
+ // We only support quota tracking on emulated filesystems
+ return;
}
final int userId = volume.getOwner().getIdentifier();
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index af915966e7eb..1f6555c85a66 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -102,8 +102,8 @@ public class ZenModeConfig implements Parcelable {
private static final boolean DEFAULT_ALLOW_REMINDERS = false;
private static final boolean DEFAULT_ALLOW_EVENTS = false;
private static final boolean DEFAULT_ALLOW_REPEAT_CALLERS = true;
- private static final boolean DEFAULT_ALLOW_CONV = true;
- private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ private static final boolean DEFAULT_ALLOW_CONV = false;
+ private static final int DEFAULT_ALLOW_CONV_FROM = ZenPolicy.CONVERSATION_SENDERS_NONE;
private static final boolean DEFAULT_CHANNELS_BYPASSING_DND = false;
private static final int DEFAULT_SUPPRESSED_VISUAL_EFFECTS = 0;
@@ -125,8 +125,8 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_EVENTS = "events";
private static final String ALLOW_ATT_SCREEN_OFF = "visualScreenOff";
private static final String ALLOW_ATT_SCREEN_ON = "visualScreenOn";
- private static final String ALLOW_ATT_CONV = "conv";
- private static final String ALLOW_ATT_CONV_FROM = "convFrom";
+ private static final String ALLOW_ATT_CONV = "convos";
+ private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
private static final String STATE_TAG = "state";
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index df4ead13d5cb..28639b3ea2f7 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -748,7 +748,7 @@ public class TextUtils {
int parcelableFlags) {
if (cs instanceof Spanned) {
p.writeInt(0);
- p.writeString(cs.toString());
+ p.writeString8(cs.toString());
Spanned sp = (Spanned) cs;
Object[] os = sp.getSpans(0, cs.length(), Object.class);
@@ -785,9 +785,9 @@ public class TextUtils {
} else {
p.writeInt(1);
if (cs != null) {
- p.writeString(cs.toString());
+ p.writeString8(cs.toString());
} else {
- p.writeString(null);
+ p.writeString8(null);
}
}
}
@@ -807,7 +807,7 @@ public class TextUtils {
public CharSequence createFromParcel(Parcel p) {
int kind = p.readInt();
- String string = p.readString();
+ String string = p.readString8();
if (string == null) {
return null;
}
diff --git a/core/java/android/view/IScrollCaptureClient.aidl b/core/java/android/view/IScrollCaptureClient.aidl
new file mode 100644
index 000000000000..5f135a37dfef
--- /dev/null
+++ b/core/java/android/view/IScrollCaptureClient.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Rect;
+import android.view.Surface;
+
+
+ /**
+ * Interface implemented by a client of the Scroll Capture framework to receive requests
+ * to start, capture images and end the session.
+ *
+ * {@hide}
+ */
+interface IScrollCaptureClient {
+
+ /**
+ * Informs the client that it has been selected for scroll capture and should prepare to
+ * to begin handling capture requests.
+ */
+ oneway void startCapture(in Surface surface);
+
+ /**
+ * Request the client capture an image within the provided rectangle.
+ *
+ * @see android.view.ScrollCaptureCallback#onScrollCaptureRequest
+ */
+ oneway void requestImage(in Rect captureArea);
+
+ /**
+ * Inform the client that capture has ended. The client should shut down and release all
+ * local resources in use and prepare for return to normal interactive usage.
+ */
+ oneway void endCapture();
+}
diff --git a/core/java/android/view/IScrollCaptureController.aidl b/core/java/android/view/IScrollCaptureController.aidl
new file mode 100644
index 000000000000..8474a00b302f
--- /dev/null
+++ b/core/java/android/view/IScrollCaptureController.aidl
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Surface;
+
+import android.view.IScrollCaptureClient;
+
+/**
+ * Interface to a controller passed to the {@link IScrollCaptureClient} which provides the client an
+ * asynchronous callback channel for responses.
+ *
+ * {@hide}
+ */
+interface IScrollCaptureController {
+ /**
+ * Scroll capture is available, and a client connect has been returned.
+ *
+ * @param client interface to a ScrollCaptureCallback in the window process
+ * @param scrollAreaInWindow the location of scrolling in global (window) coordinate space
+ */
+ oneway void onClientConnected(in IScrollCaptureClient client, in Rect scrollBounds,
+ in Point positionInWindow);
+
+ /**
+ * Nothing in the window can be scrolled, scroll capture not offered.
+ */
+ oneway void onClientUnavailable();
+
+ /**
+ * Notifies the system that the client has confirmed the request and is ready to begin providing
+ * image requests.
+ */
+ oneway void onCaptureStarted();
+
+ /**
+ * Received a response from a capture request.
+ */
+ oneway void onCaptureBufferSent(long frameNumber, in Rect capturedArea);
+
+ /**
+ * Signals that the capture session has completed and the target window may be returned to
+ * normal interactive use. This may be due to normal shutdown, or after a timeout or other
+ * unrecoverable state change such as activity lifecycle, window visibility or focus.
+ */
+ oneway void onConnectionClosed();
+}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 9f2d36de61cd..e09bf9d2e80a 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -21,15 +21,16 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
+import android.util.MergedConfiguration;
+import android.view.DisplayCutout;
import android.view.DragEvent;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.IScrollCaptureController;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.DisplayCutout;
-import android.view.InsetsState;
-import android.view.InsetsSourceControl;
import com.android.internal.os.IResultReceiver;
-import android.util.MergedConfiguration;
/**
* API back to a client window that the Window Manager uses to inform it of
@@ -139,4 +140,11 @@ oneway interface IWindow {
* Tell the window that it is either gaining or losing pointer capture.
*/
void dispatchPointerCaptureChanged(boolean hasCapture);
+
+ /**
+ * Called when Scroll Capture support is requested for a window.
+ *
+ * @param controller the controller to receive responses
+ */
+ void requestScrollCapture(in IScrollCaptureController controller);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b0bacb955f80..b3b53f029382 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -42,6 +42,7 @@ import android.view.IDisplayFoldListener;
import android.view.IDisplayWindowRotationController;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedStackListener;
+import android.view.IScrollCaptureController;
import android.view.RemoteAnimationAdapter;
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
@@ -749,4 +750,18 @@ interface IWindowManager
* @param flags see definition in SurfaceTracing.cpp
*/
void setLayerTracingFlags(int flags);
+
+ /**
+ * Forwards a scroll capture request to the appropriate window, if available.
+ *
+ * @param displayId the id of the display to target
+ * @param behindClient token for a window, used to filter the search to windows behind it, or
+ * {@code null} to accept a window at any zOrder
+ * @param taskId specifies the id of a task the result must belong to, or -1 to ignore task ids
+ * @param controller the controller to receive results, a call to either
+ * {@link IScrollCaptureController#onClientConnected} or
+ * {@link IScrollCaptureController#onClientUnavailable}.
+ */
+ void requestScrollCapture(int displayId, IBinder behindClient, int taskId,
+ IScrollCaptureController controller);
}
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index bdc8ba835fbe..df891303bb1d 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -153,7 +153,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
@Override
protected boolean isRequestedVisibleAwaitingControl() {
- return mIsRequestedVisibleAwaitingControl;
+ return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
}
private boolean isDummyOrEmptyEditor(EditorInfo info) {
diff --git a/core/java/android/view/ScrollCaptureCallback.java b/core/java/android/view/ScrollCaptureCallback.java
new file mode 100644
index 000000000000..e1a4e7443600
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureCallback.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.graphics.Rect;
+
+import java.util.function.Consumer;
+
+/**
+ * A ScrollCaptureCallback is responsible for providing rendered snapshots of scrolling content for
+ * the scroll capture system. A single callback is responsible for providing support to a single
+ * scrolling UI element. At request time, the system will select the best candidate from among all
+ * callbacks registered within the window.
+ * <p>
+ * A callback is assigned to a View using {@link View#setScrollCaptureCallback}, or to the window as
+ * {@link Window#addScrollCaptureCallback}. The point where the callback is registered defines the
+ * frame of reference for the bounds measurements used.
+ * <p>
+ * <b>Terminology</b>
+ * <dl>
+ * <dt>Containing View</dt>
+ * <dd>The view on which this callback is attached, or the root view of the window if the callback
+ * is assigned directly to a window.</dd>
+ *
+ * <dt>Scroll Bounds</dt>
+ * <dd>A rectangle which describes an area within the containing view where scrolling content may
+ * be positioned. This may be the Containing View bounds itself, or any rectangle within.
+ * Requested by {@link #onScrollCaptureSearch}.</dd>
+ *
+ * <dt>Scroll Delta</dt>
+ * <dd>The distance the scroll position has moved since capture started. Implementations are
+ * responsible for tracking changes in vertical scroll position during capture. This is required to
+ * map the capture area to the correct location, given the current scroll position.
+ *
+ * <dt>Capture Area</dt>
+ * <dd>A rectangle which describes the area to capture, relative to scroll bounds. The vertical
+ * position remains relative to the starting scroll position and any movement since ("Scroll Delta")
+ * should be subtracted to locate the correct local position, and scrolled into view as necessary.
+ * </dd>
+ * </dl>
+ *
+ * @see View#setScrollCaptureHint(int)
+ * @see View#setScrollCaptureCallback(ScrollCaptureCallback)
+ * @see Window#addScrollCaptureCallback(ScrollCaptureCallback)
+ *
+ * @hide
+ */
+@UiThread
+public interface ScrollCaptureCallback {
+
+ /**
+ * The system is searching for the appropriate scrolling container to capture and would like to
+ * know the size and position of scrolling content handled by this callback.
+ * <p>
+ * Implementations should inset {@code containingViewBounds} to cover only the area within the
+ * containing view where scrolling content may be positioned. This should cover only the content
+ * which tracks with scrolling movement.
+ * <p>
+ * Return the updated rectangle to {@code resultConsumer}. If for any reason the scrolling
+ * content is not available to capture, a {@code null} rectangle may be returned, and this view
+ * will be excluded as the target for this request.
+ * <p>
+ * Responses received after XXXms will be discarded.
+ * <p>
+ * TODO: finalize timeout
+ *
+ * @param onReady consumer for the updated rectangle
+ */
+ void onScrollCaptureSearch(@NonNull Consumer<Rect> onReady);
+
+ /**
+ * Scroll Capture has selected this callback to provide the scrolling image content.
+ * <p>
+ * The onReady signal should be called when ready to begin handling image requests.
+ */
+ void onScrollCaptureStart(@NonNull ScrollCaptureSession session, @NonNull Runnable onReady);
+
+ /**
+ * An image capture has been requested from the scrolling content.
+ * <p>
+ * <code>captureArea</code> contains the bounds of the image requested, relative to the
+ * rectangle provided by {@link ScrollCaptureCallback#onScrollCaptureSearch}, referred to as
+ * {@code scrollBounds}.
+ * here.
+ * <p>
+ * A series of requests will step by a constant vertical amount relative to {@code
+ * scrollBounds}, moving through the scrolling range of content, above and below the current
+ * visible area. The rectangle's vertical position will not account for any scrolling movement
+ * since capture started. Implementations therefore must track any scroll position changes and
+ * subtract this distance from requests.
+ * <p>
+ * To handle a request, the content should be scrolled to maximize the visible area of the
+ * requested rectangle. Offset {@code captureArea} again to account for any further scrolling.
+ * <p>
+ * Finally, clip this rectangle against scrollBounds to determine what portion, if any is
+ * visible content to capture. If the rectangle is completely clipped, set it to {@link
+ * Rect#setEmpty() empty} and skip the next step.
+ * <p>
+ * Make a copy of {@code captureArea}, transform to window coordinates and draw the window,
+ * clipped to this rectangle, into the {@link ScrollCaptureSession#getSurface() surface} at
+ * offset (0,0).
+ * <p>
+ * Finally, return the resulting {@code captureArea} using
+ * {@link ScrollCaptureSession#notifyBufferSent}.
+ * <p>
+ * If the response is not supplied within XXXms, the session will end with a call to {@link
+ * #onScrollCaptureEnd}, after which {@code session} is invalid and should be discarded.
+ * <p>
+ * TODO: finalize timeout
+ * <p>
+ *
+ * @param captureArea the area to capture, a rectangle within {@code scrollBounds}
+ */
+ void onScrollCaptureImageRequest(
+ @NonNull ScrollCaptureSession session, @NonNull Rect captureArea);
+
+ /**
+ * Signals that capture has ended. Implementations should release any temporary resources or
+ * references to objects in use during the capture. Any resources obtained from the session are
+ * now invalid and attempts to use them after this point may throw an exception.
+ * <p>
+ * The window should be returned as much as possible to its original state when capture started.
+ * At a minimum, the content should be scrolled to its original position.
+ * <p>
+ * <code>onReady</code> should be called when the window should be made visible and
+ * interactive. The system will wait up to XXXms for this call before proceeding.
+ * <p>
+ * TODO: finalize timeout
+ *
+ * @param onReady a callback to inform the system that the application has completed any
+ * cleanup and is ready to become visible
+ */
+ void onScrollCaptureEnd(@NonNull Runnable onReady);
+}
+
diff --git a/core/java/android/view/ScrollCaptureClient.java b/core/java/android/view/ScrollCaptureClient.java
new file mode 100644
index 000000000000..f163124f3a98
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureClient.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.annotation.WorkerThread;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.CloseGuard;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A client of the system providing Scroll Capture capability on behalf of a Window.
+ * <p>
+ * An instance is created to wrap the selected {@link ScrollCaptureCallback}.
+ *
+ * @hide
+ */
+public class ScrollCaptureClient extends IScrollCaptureClient.Stub {
+
+ private static final String TAG = "ScrollCaptureClient";
+ private static final int DEFAULT_TIMEOUT = 1000;
+
+ private final Handler mHandler;
+ private ScrollCaptureTarget mSelectedTarget;
+ private int mTimeoutMillis = DEFAULT_TIMEOUT;
+
+ protected Surface mSurface;
+ private IScrollCaptureController mController;
+
+ private final Rect mScrollBounds;
+ private final Point mPositionInWindow;
+ private final CloseGuard mCloseGuard;
+
+ // The current session instance in use by the callback.
+ private ScrollCaptureSession mSession;
+
+ // Helps manage timeout callbacks registered to handler and aids testing.
+ private DelayedAction mTimeoutAction;
+
+ /**
+ * Constructs a ScrollCaptureClient.
+ *
+ * @param selectedTarget the target the client is controlling
+ * @param controller the callbacks to reply to system requests
+ *
+ * @hide
+ */
+ public ScrollCaptureClient(
+ @NonNull ScrollCaptureTarget selectedTarget,
+ @NonNull IScrollCaptureController controller) {
+ requireNonNull(selectedTarget, "<selectedTarget> must non-null");
+ requireNonNull(controller, "<controller> must non-null");
+ final Rect scrollBounds = requireNonNull(selectedTarget.getScrollBounds(),
+ "target.getScrollBounds() must be non-null to construct a client");
+
+ mSelectedTarget = selectedTarget;
+ mHandler = selectedTarget.getContainingView().getHandler();
+ mScrollBounds = new Rect(scrollBounds);
+ mPositionInWindow = new Point(selectedTarget.getPositionInWindow());
+
+ mController = controller;
+ mCloseGuard = new CloseGuard();
+ mCloseGuard.open("close");
+
+ selectedTarget.getContainingView().addOnAttachStateChangeListener(
+ new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ selectedTarget.getContainingView().removeOnAttachStateChangeListener(this);
+ endCapture();
+ }
+ });
+ }
+
+ @VisibleForTesting
+ public void setTimeoutMillis(int timeoutMillis) {
+ mTimeoutMillis = timeoutMillis;
+ }
+
+ @Nullable
+ @VisibleForTesting
+ public DelayedAction getTimeoutAction() {
+ return mTimeoutAction;
+ }
+
+ private void checkConnected() {
+ if (mSelectedTarget == null || mController == null) {
+ throw new IllegalStateException("This client has been disconnected.");
+ }
+ }
+
+ private void checkStarted() {
+ if (mSession == null) {
+ throw new IllegalStateException("Capture session has not been started!");
+ }
+ }
+
+ @WorkerThread // IScrollCaptureClient
+ @Override
+ public void startCapture(Surface surface) throws RemoteException {
+ checkConnected();
+ mSurface = surface;
+ scheduleTimeout(mTimeoutMillis, this::onStartCaptureTimeout);
+ mSession = new ScrollCaptureSession(mSurface, mScrollBounds, mPositionInWindow, this);
+ mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureStart(mSession,
+ this::onStartCaptureCompleted));
+ }
+
+ @UiThread
+ private void onStartCaptureCompleted() {
+ if (cancelTimeout()) {
+ mHandler.post(() -> {
+ try {
+ mController.onCaptureStarted();
+ } catch (RemoteException e) {
+ doShutdown();
+ }
+ });
+ }
+ }
+
+ @UiThread
+ private void onStartCaptureTimeout() {
+ endCapture();
+ }
+
+ @WorkerThread // IScrollCaptureClient
+ @Override
+ public void requestImage(Rect requestRect) {
+ checkConnected();
+ checkStarted();
+ scheduleTimeout(mTimeoutMillis, this::onRequestImageTimeout);
+ // Response is dispatched via ScrollCaptureSession, to onRequestImageCompleted
+ mHandler.post(() -> mSelectedTarget.getCallback().onScrollCaptureImageRequest(
+ mSession, new Rect(requestRect)));
+ }
+
+ @UiThread
+ void onRequestImageCompleted(long frameNumber, Rect capturedArea) {
+ final Rect finalCapturedArea = new Rect(capturedArea);
+ if (cancelTimeout()) {
+ mHandler.post(() -> {
+ try {
+ mController.onCaptureBufferSent(frameNumber, finalCapturedArea);
+ } catch (RemoteException e) {
+ doShutdown();
+ }
+ });
+ }
+ }
+
+ @UiThread
+ private void onRequestImageTimeout() {
+ endCapture();
+ }
+
+ @WorkerThread // IScrollCaptureClient
+ @Override
+ public void endCapture() {
+ if (isStarted()) {
+ scheduleTimeout(mTimeoutMillis, this::onEndCaptureTimeout);
+ mHandler.post(() ->
+ mSelectedTarget.getCallback().onScrollCaptureEnd(this::onEndCaptureCompleted));
+ } else {
+ disconnect();
+ }
+ }
+
+ private boolean isStarted() {
+ return mController != null && mSelectedTarget != null;
+ }
+
+ @UiThread
+ private void onEndCaptureCompleted() { // onEndCaptureCompleted
+ if (cancelTimeout()) {
+ doShutdown();
+ }
+ }
+
+ @UiThread
+ private void onEndCaptureTimeout() {
+ doShutdown();
+ }
+
+
+ private void doShutdown() {
+ try {
+ if (mController != null) {
+ mController.onConnectionClosed();
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ } finally {
+ disconnect();
+ }
+ }
+
+ /**
+ * Shuts down this client and releases references to dependent objects. No attempt is made
+ * to notify the controller, use with caution!
+ */
+ public void disconnect() {
+ if (mSession != null) {
+ mSession.disconnect();
+ mSession = null;
+ }
+
+ mSelectedTarget = null;
+ mController = null;
+ }
+
+ /** @return a string representation of the state of this client */
+ public String toString() {
+ return "ScrollCaptureClient{"
+ + ", session=" + mSession
+ + ", selectedTarget=" + mSelectedTarget
+ + ", clientCallbacks=" + mController
+ + "}";
+ }
+
+ private boolean cancelTimeout() {
+ if (mTimeoutAction != null) {
+ return mTimeoutAction.cancel();
+ }
+ return false;
+ }
+
+ private void scheduleTimeout(long timeoutMillis, Runnable action) {
+ if (mTimeoutAction != null) {
+ mTimeoutAction.cancel();
+ }
+ mTimeoutAction = new DelayedAction(mHandler, timeoutMillis, action);
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public static class DelayedAction {
+ private final AtomicBoolean mCompleted = new AtomicBoolean();
+ private final Object mToken = new Object();
+ private final Handler mHandler;
+ private final Runnable mAction;
+
+ @VisibleForTesting
+ public DelayedAction(Handler handler, long timeoutMillis, Runnable action) {
+ mHandler = handler;
+ mAction = action;
+ mHandler.postDelayed(this::onTimeout, mToken, timeoutMillis);
+ }
+
+ private boolean onTimeout() {
+ if (mCompleted.compareAndSet(false, true)) {
+ mAction.run();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Cause the timeout action to run immediately and mark as timed out.
+ *
+ * @return true if the timeout was run, false if the timeout had already been canceled
+ */
+ @VisibleForTesting
+ public boolean timeoutNow() {
+ return onTimeout();
+ }
+
+ /**
+ * Attempt to cancel the timeout action (such as after a callback is made)
+ *
+ * @return true if the timeout was canceled and will not run, false if time has expired and
+ * the timeout action has or will run momentarily
+ */
+ public boolean cancel() {
+ if (!mCompleted.compareAndSet(false, true)) {
+ // Whoops, too late!
+ return false;
+ }
+ mHandler.removeCallbacksAndMessages(mToken);
+ return true;
+ }
+ }
+}
diff --git a/core/java/android/view/ScrollCaptureSession.java b/core/java/android/view/ScrollCaptureSession.java
new file mode 100644
index 000000000000..628e23fb3f5e
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureSession.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+/**
+ * A session represents the scope of interaction between a {@link ScrollCaptureCallback} and the
+ * system during an active scroll capture operation. During the scope of a session, a callback
+ * will receive a series of requests for image data. Resources provided here are valid for use
+ * until {@link ScrollCaptureCallback#onScrollCaptureEnd(Runnable)}.
+ *
+ * @hide
+ */
+public class ScrollCaptureSession {
+
+ private final Surface mSurface;
+ private final Rect mScrollBounds;
+ private final Point mPositionInWindow;
+
+ @Nullable
+ private ScrollCaptureClient mClient;
+
+ /** @hide */
+ public ScrollCaptureSession(Surface surface, Rect scrollBounds, Point positionInWindow,
+ ScrollCaptureClient client) {
+ mSurface = surface;
+ mScrollBounds = scrollBounds;
+ mPositionInWindow = positionInWindow;
+ mClient = client;
+ }
+
+ /**
+ * Returns a
+ * <a href="https://source.android.com/devices/graphics/arch-bq-gralloc">BufferQueue</a> in the
+ * form of a {@link Surface} for transfer of image buffers.
+ *
+ * @return the surface for transferring image buffers
+ * @throws IllegalStateException if the session has been closed
+ */
+ @NonNull
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ /**
+ * Returns the {@code scroll bounds}, as provided by
+ * {@link ScrollCaptureCallback#onScrollCaptureSearch}.
+ *
+ * @return the area of scrolling content within the containing view
+ */
+ @NonNull
+ public Rect getScrollBounds() {
+ return mScrollBounds;
+ }
+
+ /**
+ * Returns the offset of {@code scroll bounds} within the window.
+ *
+ * @return the area of scrolling content within the containing view
+ */
+ @NonNull
+ public Point getPositionInWindow() {
+ return mPositionInWindow;
+ }
+
+ /**
+ * Notify the system that an a buffer has been posted via the getSurface() channel.
+ *
+ * @param frameNumber the frame number of the queued buffer
+ * @param capturedArea the area captured, relative to scroll bounds
+ */
+ public void notifyBufferSent(long frameNumber, @NonNull Rect capturedArea) {
+ if (mClient != null) {
+ mClient.onRequestImageCompleted(frameNumber, capturedArea);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public void disconnect() {
+ mClient = null;
+ if (mSurface.isValid()) {
+ mSurface.release();
+ }
+ }
+}
diff --git a/core/java/android/view/ScrollCaptureTarget.java b/core/java/android/view/ScrollCaptureTarget.java
new file mode 100644
index 000000000000..f3fcabb26b31
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureTarget.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+import com.android.internal.util.FastMath;
+
+/**
+ * A target collects the set of contextual information for a ScrollCaptureHandler discovered during
+ * a {@link View#dispatchScrollCaptureSearch scroll capture search}.
+ *
+ * @hide
+ */
+public final class ScrollCaptureTarget {
+ private final View mContainingView;
+ private final ScrollCaptureCallback mCallback;
+ private final Rect mLocalVisibleRect;
+ private final Point mPositionInWindow;
+ private final int mHint;
+ private Rect mScrollBounds;
+
+ private final float[] mTmpFloatArr = new float[2];
+ private final Matrix mMatrixViewLocalToWindow = new Matrix();
+ private final Rect mTmpRect = new Rect();
+
+ public ScrollCaptureTarget(@NonNull View scrollTarget, @NonNull Rect localVisibleRect,
+ @NonNull Point positionInWindow, @NonNull ScrollCaptureCallback callback) {
+ mContainingView = scrollTarget;
+ mHint = mContainingView.getScrollCaptureHint();
+ mCallback = callback;
+ mLocalVisibleRect = localVisibleRect;
+ mPositionInWindow = positionInWindow;
+ }
+
+ /** @return the hint that the {@code containing view} had during the scroll capture search */
+ @View.ScrollCaptureHint
+ public int getHint() {
+ return mHint;
+ }
+
+ /** @return the {@link ScrollCaptureCallback} for this target */
+ @NonNull
+ public ScrollCaptureCallback getCallback() {
+ return mCallback;
+ }
+
+ /** @return the {@code containing view} for this {@link ScrollCaptureCallback callback} */
+ @NonNull
+ public View getContainingView() {
+ return mContainingView;
+ }
+
+ /**
+ * Returns the un-clipped, visible bounds of the containing view during the scroll capture
+ * search. This is used to determine on-screen area to assist in selecting the primary target.
+ *
+ * @return the visible bounds of the {@code containing view} in view-local coordinates
+ */
+ @NonNull
+ public Rect getLocalVisibleRect() {
+ return mLocalVisibleRect;
+ }
+
+ /** @return the position of the {@code containing view} within the window */
+ @NonNull
+ public Point getPositionInWindow() {
+ return mPositionInWindow;
+ }
+
+ /** @return the {@code scroll bounds} for this {@link ScrollCaptureCallback callback} */
+ @Nullable
+ public Rect getScrollBounds() {
+ return mScrollBounds;
+ }
+
+ /**
+ * Sets the scroll bounds rect to the intersection of provided rect and the current bounds of
+ * the {@code containing view}.
+ */
+ public void setScrollBounds(@Nullable Rect scrollBounds) {
+ mScrollBounds = Rect.copyOrNull(scrollBounds);
+ if (mScrollBounds == null) {
+ return;
+ }
+ if (!mScrollBounds.intersect(0, 0,
+ mContainingView.getWidth(), mContainingView.getHeight())) {
+ mScrollBounds.setEmpty();
+ }
+ }
+
+ private static void zero(float[] pointArray) {
+ pointArray[0] = 0;
+ pointArray[1] = 0;
+ }
+
+ private static void roundIntoPoint(Point pointObj, float[] pointArray) {
+ pointObj.x = FastMath.round(pointArray[0]);
+ pointObj.y = FastMath.round(pointArray[1]);
+ }
+
+ /**
+ * Refresh the value of {@link #mLocalVisibleRect} and {@link #mPositionInWindow} based on the
+ * current state of the {@code containing view}.
+ */
+ @UiThread
+ public void updatePositionInWindow() {
+ mMatrixViewLocalToWindow.reset();
+ mContainingView.transformMatrixToGlobal(mMatrixViewLocalToWindow);
+
+ zero(mTmpFloatArr);
+ mMatrixViewLocalToWindow.mapPoints(mTmpFloatArr);
+ roundIntoPoint(mPositionInWindow, mTmpFloatArr);
+ }
+
+}
diff --git a/core/java/android/view/ScrollCaptureTargetResolver.java b/core/java/android/view/ScrollCaptureTargetResolver.java
new file mode 100644
index 000000000000..71e82c511e2c
--- /dev/null
+++ b/core/java/android/view/ScrollCaptureTargetResolver.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+
+import java.util.Queue;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Queries additional state from a list of {@link ScrollCaptureTarget targets} via asynchronous
+ * callbacks, then aggregates and reduces the target list to a single target, or null if no target
+ * is suitable.
+ * <p>
+ * The rules for selection are (in order):
+ * <ul>
+ * <li>prefer getScrollBounds(): non-empty
+ * <li>prefer View.getScrollCaptureHint == SCROLL_CAPTURE_HINT_INCLUDE
+ * <li>prefer descendants before parents
+ * <li>prefer larger area for getScrollBounds() (clipped to view bounds)
+ * </ul>
+ *
+ * <p>
+ * All calls to {@link ScrollCaptureCallback#onScrollCaptureSearch} are made on the main thread,
+ * with results are queued and consumed to the main thread as well.
+ *
+ * @see #start(Handler, long, Consumer)
+ *
+ * @hide
+ */
+@UiThread
+public class ScrollCaptureTargetResolver {
+ private static final String TAG = "ScrollCaptureTargetRes";
+ private static final boolean DEBUG = true;
+
+ private final Object mLock = new Object();
+
+ private final Queue<ScrollCaptureTarget> mTargets;
+ private Handler mHandler;
+ private long mTimeLimitMillis;
+
+ private Consumer<ScrollCaptureTarget> mWhenComplete;
+ private int mPendingBoundsRequests;
+ private long mDeadlineMillis;
+
+ private ScrollCaptureTarget mResult;
+ private boolean mFinished;
+
+ private boolean mStarted;
+
+ private static int area(Rect r) {
+ return r.width() * r.height();
+ }
+
+ private static boolean nullOrEmpty(Rect r) {
+ return r == null || r.isEmpty();
+ }
+
+ /**
+ * Binary operator which selects the best {@link ScrollCaptureTarget}.
+ */
+ private static ScrollCaptureTarget chooseTarget(ScrollCaptureTarget a, ScrollCaptureTarget b) {
+ Log.d(TAG, "chooseTarget: " + a + " or " + b);
+ // Nothing plus nothing is still nothing.
+ if (a == null && b == null) {
+ Log.d(TAG, "chooseTarget: (both null) return " + null);
+ return null;
+ }
+ // Prefer non-null.
+ if (a == null || b == null) {
+ ScrollCaptureTarget c = (a == null) ? b : a;
+ Log.d(TAG, "chooseTarget: (other is null) return " + c);
+ return c;
+
+ }
+
+ boolean emptyScrollBoundsA = nullOrEmpty(a.getScrollBounds());
+ boolean emptyScrollBoundsB = nullOrEmpty(b.getScrollBounds());
+ if (emptyScrollBoundsA || emptyScrollBoundsB) {
+ if (emptyScrollBoundsA && emptyScrollBoundsB) {
+ // Both have an empty or null scrollBounds
+ Log.d(TAG, "chooseTarget: (both have empty or null bounds) return " + null);
+ return null;
+ }
+ // Prefer the one with a non-empty scroll bounds
+ if (emptyScrollBoundsA) {
+ Log.d(TAG, "chooseTarget: (a has empty or null bounds) return " + b);
+ return b;
+ }
+ Log.d(TAG, "chooseTarget: (b has empty or null bounds) return " + a);
+ return a;
+ }
+
+ final View viewA = a.getContainingView();
+ final View viewB = b.getContainingView();
+
+ // Prefer any view with scrollCaptureHint="INCLUDE", over one without
+ // This is an escape hatch for the next rule (descendants first)
+ boolean hintIncludeA = hasIncludeHint(viewA);
+ boolean hintIncludeB = hasIncludeHint(viewB);
+ if (hintIncludeA != hintIncludeB) {
+ ScrollCaptureTarget c = (hintIncludeA) ? a : b;
+ Log.d(TAG, "chooseTarget: (has hint=INCLUDE) return " + c);
+ return c;
+ }
+
+ // If the views are relatives, prefer the descendant. This allows implementations to
+ // leverage nested scrolling APIs by interacting with the innermost scrollable view (as
+ // would happen with touch input).
+ if (isDescendant(viewA, viewB)) {
+ Log.d(TAG, "chooseTarget: (b is descendant of a) return " + b);
+ return b;
+ }
+ if (isDescendant(viewB, viewA)) {
+ Log.d(TAG, "chooseTarget: (a is descendant of b) return " + a);
+ return a;
+ }
+
+ // finally, prefer one with larger scroll bounds
+ int scrollAreaA = area(a.getScrollBounds());
+ int scrollAreaB = area(b.getScrollBounds());
+ ScrollCaptureTarget c = (scrollAreaA >= scrollAreaB) ? a : b;
+ Log.d(TAG, "chooseTarget: return " + c);
+ return c;
+ }
+
+ /**
+ * Creates an instance to query and filter {@code target}.
+ *
+ * @param targets a list of {@link ScrollCaptureTarget} as collected by {@link
+ * View#dispatchScrollCaptureSearch}.
+ * @param uiHandler the UI thread handler for the view tree
+ * @see #start(long, Consumer)
+ */
+ public ScrollCaptureTargetResolver(Queue<ScrollCaptureTarget> targets) {
+ mTargets = targets;
+ }
+
+ void checkThread() {
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ throw new IllegalStateException("Called from wrong thread! ("
+ + Thread.currentThread().getName() + ")");
+ }
+ }
+
+ /**
+ * Blocks until a result is returned (after completion or timeout).
+ * <p>
+ * For testing only. Normal usage should receive a callback after calling {@link #start}.
+ */
+ @VisibleForTesting
+ public ScrollCaptureTarget waitForResult() throws InterruptedException {
+ synchronized (mLock) {
+ while (!mFinished) {
+ mLock.wait();
+ }
+ }
+ return mResult;
+ }
+
+
+ private void supplyResult(ScrollCaptureTarget target) {
+ checkThread();
+ if (mFinished) {
+ return;
+ }
+ mResult = chooseTarget(mResult, target);
+ boolean finish = mPendingBoundsRequests == 0
+ || SystemClock.elapsedRealtime() >= mDeadlineMillis;
+ if (finish) {
+ System.err.println("We think we're done, or timed out");
+ mPendingBoundsRequests = 0;
+ mWhenComplete.accept(mResult);
+ synchronized (mLock) {
+ mFinished = true;
+ mLock.notify();
+ }
+ mWhenComplete = null;
+ }
+ }
+
+ /**
+ * Asks all targets for {@link ScrollCaptureCallback#onScrollCaptureSearch(Consumer)
+ * scrollBounds}, and selects the primary target according to the {@link
+ * #chooseTarget} function.
+ *
+ * @param timeLimitMillis the amount of time to wait for all responses before delivering the top
+ * result
+ * @param resultConsumer the consumer to receive the primary target
+ */
+ @AnyThread
+ public void start(Handler uiHandler, long timeLimitMillis,
+ Consumer<ScrollCaptureTarget> resultConsumer) {
+ synchronized (mLock) {
+ if (mStarted) {
+ throw new IllegalStateException("already started!");
+ }
+ if (timeLimitMillis < 0) {
+ throw new IllegalArgumentException("Time limit must be positive");
+ }
+ mHandler = uiHandler;
+ mTimeLimitMillis = timeLimitMillis;
+ mWhenComplete = resultConsumer;
+ if (mTargets.isEmpty()) {
+ mHandler.post(() -> supplyResult(null));
+ return;
+ }
+ mStarted = true;
+ uiHandler.post(() -> run(timeLimitMillis, resultConsumer));
+ }
+ }
+
+
+ private void run(long timeLimitMillis, Consumer<ScrollCaptureTarget> resultConsumer) {
+ checkThread();
+
+ mPendingBoundsRequests = mTargets.size();
+ for (ScrollCaptureTarget target : mTargets) {
+ queryTarget(target);
+ }
+ mDeadlineMillis = SystemClock.elapsedRealtime() + mTimeLimitMillis;
+ mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis);
+ }
+
+ private final Runnable mTimeoutRunnable = new Runnable() {
+ @Override
+ public void run() {
+ checkThread();
+ supplyResult(null);
+ }
+ };
+
+
+ /**
+ * Adds a target to the list and requests {@link ScrollCaptureCallback#onScrollCaptureSearch}
+ * scrollBounds} from it. Results are returned by a call to {@link #onScrollBoundsProvided}.
+ *
+ * @param target the target to add
+ */
+ @UiThread
+ private void queryTarget(@NonNull ScrollCaptureTarget target) {
+ checkThread();
+ final ScrollCaptureCallback callback = target.getCallback();
+ // from the UI thread, request scroll bounds
+ callback.onScrollCaptureSearch(
+ // allow only one callback to onReady.accept():
+ new SingletonConsumer<Rect>(
+ // Queue and consume on the UI thread
+ ((scrollBounds) -> mHandler.post(
+ () -> onScrollBoundsProvided(target, scrollBounds)))));
+
+ }
+
+ @UiThread
+ private void onScrollBoundsProvided(ScrollCaptureTarget target, @Nullable Rect scrollBounds) {
+ checkThread();
+ if (mFinished) {
+ return;
+ }
+
+ // Record progress.
+ mPendingBoundsRequests--;
+
+ // Remove the timeout.
+ mHandler.removeCallbacks(mTimeoutRunnable);
+
+ boolean doneOrTimedOut = mPendingBoundsRequests == 0
+ || SystemClock.elapsedRealtime() >= mDeadlineMillis;
+
+ final View containingView = target.getContainingView();
+ if (!nullOrEmpty(scrollBounds) && containingView.isAggregatedVisible()) {
+ target.updatePositionInWindow();
+ target.setScrollBounds(scrollBounds);
+ supplyResult(target);
+ }
+
+ System.err.println("mPendingBoundsRequests: " + mPendingBoundsRequests);
+ System.err.println("mDeadlineMillis: " + mDeadlineMillis);
+ System.err.println("SystemClock.elapsedRealtime(): " + SystemClock.elapsedRealtime());
+
+ if (!mFinished) {
+ // Reschedule the timeout.
+ System.err.println(
+ "We think we're NOT done yet and will check back at " + mDeadlineMillis);
+ mHandler.postAtTime(mTimeoutRunnable, mDeadlineMillis);
+ }
+ }
+
+ private static boolean hasIncludeHint(View view) {
+ return (view.getScrollCaptureHint() & View.SCROLL_CAPTURE_HINT_INCLUDE) != 0;
+ }
+
+ /**
+ * Determines if {@code otherView} is a descendant of {@code view}.
+ *
+ * @param view a view
+ * @param otherView another view
+ * @return true if {@code view} is an ancestor of {@code otherView}
+ */
+ private static boolean isDescendant(@NonNull View view, @NonNull View otherView) {
+ if (view == otherView) {
+ return false;
+ }
+ ViewParent otherParent = otherView.getParent();
+ while (otherParent != view && otherParent != null) {
+ otherParent = otherParent.getParent();
+ }
+ return otherParent == view;
+ }
+
+ private static int findRelation(@NonNull View a, @NonNull View b) {
+ if (a == b) {
+ return 0;
+ }
+
+ ViewParent parentA = a.getParent();
+ ViewParent parentB = b.getParent();
+
+ while (parentA != null || parentB != null) {
+ if (parentA == parentB) {
+ return 0;
+ }
+ if (parentA == b) {
+ return 1; // A is descendant of B
+ }
+ if (parentB == a) {
+ return -1; // B is descendant of A
+ }
+ if (parentA != null) {
+ parentA = parentA.getParent();
+ }
+ if (parentB != null) {
+ parentB = parentB.getParent();
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * A safe wrapper for a consumer callbacks intended to accept a single value. It ensures
+ * that the receiver of the consumer does not retain a reference to {@code target} after use nor
+ * cause race conditions by invoking {@link Consumer#accept accept} more than once.
+ *
+ * @param target the target consumer
+ */
+ static class SingletonConsumer<T> implements Consumer<T> {
+ final AtomicReference<Consumer<T>> mAtomicRef;
+
+ SingletonConsumer(Consumer<T> target) {
+ mAtomicRef = new AtomicReference<>(target);
+ }
+
+ @Override
+ public void accept(T t) {
+ final Consumer<T> consumer = mAtomicRef.getAndSet(null);
+ if (consumer != null) {
+ consumer.accept(t);
+ }
+ }
+ }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8abe72fc91e8..f98c1f660cfa 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -144,6 +144,7 @@ import android.widget.ScrollBarDrawable;
import com.android.internal.R;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.view.ScrollCaptureInternal;
import com.android.internal.view.TooltipPopup;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.widget.ScrollBarUtils;
@@ -167,6 +168,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Queue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
@@ -1311,7 +1313,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static final int AUTOFILL_TYPE_LIST = 3;
-
/**
* Autofill type for a field that contains a date, which is represented by a long representing
* the number of milliseconds since the standard base time known as "the epoch", namely
@@ -1441,6 +1442,58 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public static final int IMPORTANT_FOR_CONTENT_CAPTURE_NO_EXCLUDE_DESCENDANTS = 0x8;
+ /** {@hide} */
+ @IntDef(flag = true, prefix = {"SCROLL_CAPTURE_HINT_"},
+ value = {
+ SCROLL_CAPTURE_HINT_AUTO,
+ SCROLL_CAPTURE_HINT_EXCLUDE,
+ SCROLL_CAPTURE_HINT_INCLUDE,
+ SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScrollCaptureHint {}
+
+ /**
+ * The content of this view will be considered for scroll capture if scrolling is possible.
+ *
+ * @see #getScrollCaptureHint()
+ * @see #setScrollCaptureHint(int)
+ * @hide
+ */
+ public static final int SCROLL_CAPTURE_HINT_AUTO = 0;
+
+ /**
+ * Explicitly exclcude this view as a potential scroll capture target. The system will not
+ * consider it. Mutually exclusive with {@link #SCROLL_CAPTURE_HINT_INCLUDE}, which this flag
+ * takes precedence over.
+ *
+ * @see #getScrollCaptureHint()
+ * @see #setScrollCaptureHint(int)
+ * @hide
+ */
+ public static final int SCROLL_CAPTURE_HINT_EXCLUDE = 0x1;
+
+ /**
+ * Explicitly include this view as a potential scroll capture target. When locating a scroll
+ * capture target, this view will be prioritized before others without this flag. Mutually
+ * exclusive with {@link #SCROLL_CAPTURE_HINT_EXCLUDE}, which takes precedence.
+ *
+ * @see #getScrollCaptureHint()
+ * @see #setScrollCaptureHint(int)
+ * @hide
+ */
+ public static final int SCROLL_CAPTURE_HINT_INCLUDE = 0x2;
+
+ /**
+ * Explicitly exclude all children of this view as potential scroll capture targets. This view
+ * is unaffected. Note: Excluded children are not considered, regardless of {@link
+ * #SCROLL_CAPTURE_HINT_INCLUDE}.
+ *
+ * @see #getScrollCaptureHint()
+ * @see #setScrollCaptureHint(int)
+ * @hide
+ */
+ public static final int SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS = 0x4;
/**
* This view is enabled. Interpretation varies by subclass.
@@ -3430,6 +3483,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* 11 PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK
* 1 PFLAG4_FRAMEWORK_OPTIONAL_FITS_SYSTEM_WINDOWS
* 1 PFLAG4_AUTOFILL_HIDE_HIGHLIGHT
+ * 11 PFLAG4_SCROLL_CAPTURE_HINT_MASK
* |-------|-------|-------|-------|
*/
@@ -3477,6 +3531,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
private static final int PFLAG4_AUTOFILL_HIDE_HIGHLIGHT = 0x200;
+ /**
+ * Shift for the bits in {@link #mPrivateFlags4} related to scroll capture.
+ */
+ static final int PFLAG4_SCROLL_CAPTURE_HINT_SHIFT = 10;
+
+ static final int PFLAG4_SCROLL_CAPTURE_HINT_MASK = (SCROLL_CAPTURE_HINT_INCLUDE
+ | SCROLL_CAPTURE_HINT_EXCLUDE | SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS)
+ << PFLAG4_SCROLL_CAPTURE_HINT_SHIFT;
+
/* End of masks for mPrivateFlags4 */
/** @hide */
@@ -4690,6 +4753,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Used to track {@link #mSystemGestureExclusionRects}
*/
public RenderNode.PositionUpdateListener mPositionUpdateListener;
+
+ /**
+ * Allows the application to implement custom scroll capture support.
+ */
+ ScrollCaptureCallback mScrollCaptureCallback;
}
@UnsupportedAppUsage
@@ -5941,6 +6009,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
case R.styleable.View_forceDarkAllowed:
mRenderNode.setForceDarkAllowed(a.getBoolean(attr, true));
break;
+ case R.styleable.View_scrollCaptureHint:
+ setScrollCaptureHint((a.getInt(attr, SCROLL_CAPTURE_HINT_AUTO)));
+ break;
}
}
@@ -29091,6 +29162,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int mLeashedParentAccessibilityViewId;
/**
+ *
+ */
+ ScrollCaptureInternal mScrollCaptureInternal;
+
+ /**
* Creates a new set of attachment information with the specified
* events handler and thread.
*
@@ -29150,6 +29226,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
return events;
}
+
+ @Nullable
+ ScrollCaptureInternal getScrollCaptureInternal() {
+ if (mScrollCaptureInternal != null) {
+ mScrollCaptureInternal = new ScrollCaptureInternal();
+ }
+ return mScrollCaptureInternal;
+ }
}
/**
@@ -29683,6 +29767,104 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
}
+
+ /**
+ * Returns the current scroll capture hint for this view.
+ *
+ * @return the current scroll capture hint
+ *
+ * @hide
+ */
+ @ScrollCaptureHint
+ public int getScrollCaptureHint() {
+ return (mPrivateFlags4 & PFLAG4_SCROLL_CAPTURE_HINT_MASK)
+ >> PFLAG4_SCROLL_CAPTURE_HINT_SHIFT;
+ }
+
+ /**
+ * Sets the scroll capture hint for this View. These flags affect the search for a potential
+ * scroll capture targets.
+ *
+ * @param hint the scrollCaptureHint flags value to set
+ *
+ * @hide
+ */
+ public void setScrollCaptureHint(@ScrollCaptureHint int hint) {
+ mPrivateFlags4 &= ~PFLAG4_SCROLL_CAPTURE_HINT_MASK;
+ mPrivateFlags4 |= ((hint << PFLAG4_SCROLL_CAPTURE_HINT_SHIFT)
+ & PFLAG4_SCROLL_CAPTURE_HINT_MASK);
+ }
+
+ /**
+ * Sets the callback to receive scroll capture requests. This component is the adapter between
+ * the scroll capture API and application UI code. If no callback is set, the system may provide
+ * an implementation. Any value provided here will take precedence over a system version.
+ * <p>
+ * This view will be ignored when {@link #SCROLL_CAPTURE_HINT_EXCLUDE} is set in its {@link
+ * #setScrollCaptureHint(int) scrollCaptureHint}, regardless whether a callback has been set.
+ * <p>
+ * It is recommended to set the scroll capture hint {@link #SCROLL_CAPTURE_HINT_INCLUDE} when
+ * setting a custom callback to help ensure it is selected as the target.
+ *
+ * @param callback the new callback to assign
+ *
+ * @hide
+ */
+ public void setScrollCaptureCallback(@Nullable ScrollCaptureCallback callback) {
+ getListenerInfo().mScrollCaptureCallback = callback;
+ }
+
+ /** {@hide} */
+ @Nullable
+ public ScrollCaptureCallback createScrollCaptureCallbackInternal(@NonNull Rect localVisibleRect,
+ @NonNull Point windowOffset) {
+ if (mAttachInfo == null) {
+ return null;
+ }
+ if (mAttachInfo.mScrollCaptureInternal == null) {
+ mAttachInfo.mScrollCaptureInternal = new ScrollCaptureInternal();
+ }
+ return mAttachInfo.mScrollCaptureInternal.requestCallback(this, localVisibleRect,
+ windowOffset);
+ }
+
+ /**
+ * Called when scroll capture is requested, to search for appropriate content to scroll. If
+ * applicable, this view adds itself to the provided list for consideration, subject to the
+ * flags set by {@link #setScrollCaptureHint}.
+ *
+ * @param localVisibleRect the local visible rect of this view
+ * @param windowOffset the offset of localVisibleRect within the window
+ * @param targets a queue which collects potential targets
+ *
+ * @throws IllegalStateException if this view is not attached to a window
+ * @hide
+ */
+ public void dispatchScrollCaptureSearch(@NonNull Rect localVisibleRect,
+ @NonNull Point windowOffset, @NonNull Queue<ScrollCaptureTarget> targets) {
+ int hint = getScrollCaptureHint();
+ if ((hint & SCROLL_CAPTURE_HINT_EXCLUDE) != 0) {
+ return;
+ }
+
+ // Get a callback provided by the framework, library or application.
+ ScrollCaptureCallback callback =
+ (mListenerInfo == null) ? null : mListenerInfo.mScrollCaptureCallback;
+
+ // Try internal support for standard scrolling containers.
+ if (callback == null) {
+ callback = createScrollCaptureCallbackInternal(localVisibleRect, windowOffset);
+ }
+
+ // If found, then add it to the list.
+ if (callback != null) {
+ // Add to the list for consideration
+ Point offset = new Point(windowOffset.x, windowOffset.y);
+ Rect rect = new Rect(localVisibleRect);
+ targets.add(new ScrollCaptureTarget(this, rect, offset, callback));
+ }
+ }
+
/**
* Dump all private flags in readable format, useful for documentation and
* sanity checking.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e34e84c977ea..7935eb1ffc39 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -40,6 +40,7 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
+import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -75,6 +76,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.function.Predicate;
/**
@@ -188,7 +190,16 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private PointF mLocalPoint;
// Lazily-created holder for point computations.
- private float[] mTempPoint;
+ private float[] mTempPosition;
+
+ // Lazily-created holder for point computations.
+ private Point mTempPoint;
+
+ // Lazily created Rect for dispatch to children
+ private Rect mTempRect;
+
+ // Lazily created int[2] for dispatch to children
+ private int[] mTempLocation;
// Layout animation
private LayoutAnimationController mLayoutAnimationController;
@@ -1860,7 +1871,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float tx = mCurrentDragStartEvent.mX;
final float ty = mCurrentDragStartEvent.mY;
- final float[] point = getTempPoint();
+ final float[] point = getTempLocationF();
point[0] = tx;
point[1] = ty;
transformPointToViewLocal(point, child);
@@ -2932,9 +2943,23 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
- private float[] getTempPoint() {
+ private Rect getTempRect() {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ return mTempRect;
+ }
+
+ private float[] getTempLocationF() {
+ if (mTempPosition == null) {
+ mTempPosition = new float[2];
+ }
+ return mTempPosition;
+ }
+
+ private Point getTempPoint() {
if (mTempPoint == null) {
- mTempPoint = new float[2];
+ mTempPoint = new Point();
}
return mTempPoint;
}
@@ -2948,7 +2973,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@UnsupportedAppUsage
protected boolean isTransformedTouchPointInView(float x, float y, View child,
PointF outLocalPoint) {
- final float[] point = getTempPoint();
+ final float[] point = getTempLocationF();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
@@ -4568,7 +4593,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final boolean nonActionable = !child.isClickable() && !child.isLongClickable();
final boolean duplicatesState = (child.mViewFlags & DUPLICATE_PARENT_STATE) != 0;
if (nonActionable || duplicatesState) {
- final float[] point = getTempPoint();
+ final float[] point = getTempLocationF();
point[0] = x;
point[1] = y;
transformPointToViewLocal(point, child);
@@ -7354,6 +7379,97 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
/**
+ * Offsets the given rectangle in parent's local coordinates into child's coordinate space
+ * and clips the result to the child View's bounds, padding and clipRect if appropriate. If the
+ * resulting rectangle is not empty, the request is forwarded to the child.
+ * <p>
+ * Note: This method does not account for any static View transformations which may be
+ * applied to the child view.
+ *
+ * @param child the child to dispatch to
+ * @param localVisibleRect the visible (clipped) area of this ViewGroup, in local coordinates
+ * @param windowOffset the offset of localVisibleRect within the window
+ * @param targets a queue to collect located targets
+ */
+ private void dispatchTransformedScrollCaptureSearch(View child, Rect localVisibleRect,
+ Point windowOffset, Queue<ScrollCaptureTarget> targets) {
+
+ // copy local visible rect for modification and dispatch
+ final Rect childVisibleRect = getTempRect();
+ childVisibleRect.set(localVisibleRect);
+
+ // transform to child coords
+ final Point childWindowOffset = getTempPoint();
+ childWindowOffset.set(windowOffset.x, windowOffset.y);
+
+ final int dx = child.mLeft - mScrollX;
+ final int dy = child.mTop - mScrollY;
+
+ childVisibleRect.offset(-dx, -dy);
+ childWindowOffset.offset(dx, dy);
+
+ boolean rectIsVisible = true;
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
+
+ // Clip to child bounds
+ if (getClipChildren()) {
+ rectIsVisible = childVisibleRect.intersect(0, 0, child.getWidth(), child.getHeight());
+ }
+
+ // Clip to child padding.
+ if (rectIsVisible && (child instanceof ViewGroup)
+ && ((ViewGroup) child).getClipToPadding()) {
+ rectIsVisible = childVisibleRect.intersect(
+ child.mPaddingLeft, child.mPaddingTop,
+ child.getWidth() - child.mPaddingRight,
+ child.getHeight() - child.mPaddingBottom);
+ }
+ // Clip to child clipBounds.
+ if (rectIsVisible && child.mClipBounds != null) {
+ rectIsVisible = childVisibleRect.intersect(child.mClipBounds);
+ }
+ if (rectIsVisible) {
+ child.dispatchScrollCaptureSearch(childVisibleRect, childWindowOffset, targets);
+ }
+ }
+
+ /**
+ * Handle the scroll capture search request by checking this view if applicable, then to each
+ * child view.
+ *
+ * @param localVisibleRect the visible area of this ViewGroup in local coordinates, according to
+ * the parent
+ * @param windowOffset the offset of this view within the window
+ * @param targets the collected list of scroll capture targets
+ *
+ * @hide
+ */
+ @Override
+ public void dispatchScrollCaptureSearch(
+ @NonNull Rect localVisibleRect, @NonNull Point windowOffset,
+ @NonNull Queue<ScrollCaptureTarget> targets) {
+
+ // Dispatch to self first.
+ super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+
+ // Then dispatch to children, if not excluding descendants.
+ if ((getScrollCaptureHint() & SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS) == 0) {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ // Only visible views can be captured.
+ if (child.getVisibility() != View.VISIBLE) {
+ continue;
+ }
+ // Transform to child coords and dispatch
+ dispatchTransformedScrollCaptureSearch(child, localVisibleRect, windowOffset,
+ targets);
+ }
+ }
+ }
+
+ /**
* Returns the animation listener to which layout animation events are
* sent.
*
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ed1edc3bd526..9d275cdcb00f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -206,6 +206,7 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_INPUT_STAGES = false || LOCAL_LOGV;
private static final boolean DEBUG_KEEP_SCREEN_ON = false || LOCAL_LOGV;
private static final boolean DEBUG_CONTENT_CAPTURE = false || LOCAL_LOGV;
+ private static final boolean DEBUG_SCROLL_CAPTURE = false || LOCAL_LOGV;
/**
* Set to false if we do not want to use the multi threaded renderer even though
@@ -653,6 +654,8 @@ public final class ViewRootImpl implements ViewParent,
private final InsetsController mInsetsController;
private final ImeFocusController mImeFocusController;
+ private ScrollCaptureClient mScrollCaptureClient;
+
/**
* @return {@link ImeFocusController} for this instance.
*/
@@ -661,6 +664,11 @@ public final class ViewRootImpl implements ViewParent,
return mImeFocusController;
}
+ /** @return The current {@link ScrollCaptureClient} for this instance, if any is active. */
+ @Nullable
+ public ScrollCaptureClient getScrollCaptureClient() {
+ return mScrollCaptureClient;
+ }
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
@@ -694,6 +702,8 @@ public final class ViewRootImpl implements ViewParent,
// draw returns.
private SurfaceControl.Transaction mRtBLASTSyncTransaction = new SurfaceControl.Transaction();
+ private HashSet<ScrollCaptureCallback> mRootScrollCaptureCallbacks;
+
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
@@ -3769,7 +3779,9 @@ public final class ViewRootImpl implements ViewParent,
mNextReportConsumeBLAST = true;
mNextDrawUseBLASTSyncTransaction = false;
- mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
+ if (mBlastBufferQueue != null) {
+ mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
+ }
}
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
@@ -4778,6 +4790,7 @@ public final class ViewRootImpl implements ViewParent,
private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
private static final int MSG_SHOW_INSETS = 34;
private static final int MSG_HIDE_INSETS = 35;
+ private static final int MSG_REQUEST_SCROLL_CAPTURE = 36;
final class ViewRootHandler extends Handler {
@@ -5080,6 +5093,9 @@ public final class ViewRootImpl implements ViewParent,
case MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED: {
updateLocationInParentDisplay(msg.arg1, msg.arg2);
} break;
+ case MSG_REQUEST_SCROLL_CAPTURE:
+ handleScrollCaptureRequest((IScrollCaptureController) msg.obj);
+ break;
}
}
}
@@ -8789,6 +8805,131 @@ public final class ViewRootImpl implements ViewParent,
return false;
}
+ /**
+ * Adds a scroll capture callback to this window.
+ *
+ * @param callback the callback to add
+ */
+ public void addScrollCaptureCallback(ScrollCaptureCallback callback) {
+ if (mRootScrollCaptureCallbacks == null) {
+ mRootScrollCaptureCallbacks = new HashSet<>();
+ }
+ mRootScrollCaptureCallbacks.add(callback);
+ }
+
+ /**
+ * Removes a scroll capture callback from this window.
+ *
+ * @param callback the callback to remove
+ */
+ public void removeScrollCaptureCallback(ScrollCaptureCallback callback) {
+ if (mRootScrollCaptureCallbacks != null) {
+ mRootScrollCaptureCallbacks.remove(callback);
+ if (mRootScrollCaptureCallbacks.isEmpty()) {
+ mRootScrollCaptureCallbacks = null;
+ }
+ }
+ }
+
+ /**
+ * Dispatches a scroll capture request to the view hierarchy on the ui thread.
+ *
+ * @param controller the controller to receive replies
+ */
+ public void dispatchScrollCaptureRequest(@NonNull IScrollCaptureController controller) {
+ mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, controller).sendToTarget();
+ }
+
+ /**
+ * Collect and include any ScrollCaptureCallback instances registered with the window.
+ *
+ * @see #addScrollCaptureCallback(ScrollCaptureCallback)
+ * @param targets the search queue for targets
+ */
+ private void collectRootScrollCaptureTargets(Queue<ScrollCaptureTarget> targets) {
+ for (ScrollCaptureCallback cb : mRootScrollCaptureCallbacks) {
+ // Add to the list for consideration
+ Point offset = new Point(mView.getLeft(), mView.getTop());
+ Rect rect = new Rect(0, 0, mView.getWidth(), mView.getHeight());
+ targets.add(new ScrollCaptureTarget(mView, rect, offset, cb));
+ }
+ }
+
+ /**
+ * Handles an inbound request for scroll capture from the system. If a client is not already
+ * active, a search will be dispatched through the view tree to locate scrolling content.
+ * <p>
+ * Either {@link IScrollCaptureController#onClientConnected(IScrollCaptureClient, Rect,
+ * Point)} or {@link IScrollCaptureController#onClientUnavailable()} will be returned
+ * depending on the results of the search.
+ *
+ * @param controller the interface to the system controller
+ * @see ScrollCaptureTargetResolver
+ */
+ private void handleScrollCaptureRequest(@NonNull IScrollCaptureController controller) {
+ LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Window (root) level callbacks
+ collectRootScrollCaptureTargets(targetList);
+
+ // Search through View-tree
+ View rootView = getView();
+ Point point = new Point();
+ Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight());
+ getChildVisibleRect(rootView, rect, point);
+ rootView.dispatchScrollCaptureSearch(rect, point, targetList);
+
+ // No-op path. Scroll capture not offered for this window.
+ if (targetList.isEmpty()) {
+ dispatchScrollCaptureSearchResult(controller, null);
+ return;
+ }
+
+ // Request scrollBounds from each of the targets.
+ // Continues with the consumer once all responses are consumed, or the timeout expires.
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetList);
+ resolver.start(mHandler, 1000,
+ (selected) -> dispatchScrollCaptureSearchResult(controller, selected));
+ }
+
+ /** Called by {@link #handleScrollCaptureRequest} when a result is returned */
+ private void dispatchScrollCaptureSearchResult(
+ @NonNull IScrollCaptureController controller,
+ @Nullable ScrollCaptureTarget selectedTarget) {
+
+ // If timeout or no eligible targets found.
+ if (selectedTarget == null) {
+ try {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.d(TAG, "scrollCaptureSearch returned no targets available.");
+ }
+ controller.onClientUnavailable();
+ } catch (RemoteException e) {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.w(TAG, "Failed to notify controller of scroll capture search result.", e);
+ }
+ }
+ return;
+ }
+
+ // Create a client instance and return it to the caller
+ mScrollCaptureClient = new ScrollCaptureClient(selectedTarget, controller);
+ try {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.d(TAG, "scrollCaptureSearch returning client: " + getScrollCaptureClient());
+ }
+ controller.onClientConnected(
+ mScrollCaptureClient,
+ selectedTarget.getScrollBounds(),
+ selectedTarget.getPositionInWindow());
+ } catch (RemoteException e) {
+ if (DEBUG_SCROLL_CAPTURE) {
+ Log.w(TAG, "Failed to notify controller of scroll capture search result.", e);
+ }
+ mScrollCaptureClient.disconnect();
+ mScrollCaptureClient = null;
+ }
+ }
private void reportNextDraw() {
if (mReportNextDraw == false) {
@@ -9091,6 +9232,13 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ @Override
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.dispatchScrollCaptureRequest(controller);
+ }
+ }
}
public static final class CalledFromWrongThreadException extends AndroidRuntimeException {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index ae9afabad533..b1536484b515 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2535,6 +2535,33 @@ public abstract class Window {
return Collections.emptyList();
}
+ /**
+ * System request to begin scroll capture.
+ *
+ * @param controller the controller to receive responses
+ * @hide
+ */
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ }
+
+ /**
+ * Registers a {@link ScrollCaptureCallback} with the root of this window.
+ *
+ * @param callback the callback to add
+ * @hide
+ */
+ public void addScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
+ }
+
+ /**
+ * Unregisters a {@link ScrollCaptureCallback} previously registered with this window.
+ *
+ * @param callback the callback to remove
+ * @hide
+ */
+ public void removeScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
+ }
+
/** @hide */
public void setTheme(int resId) {
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index cc380f32297e..e4dbd63765b6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -514,33 +514,24 @@ public interface WindowManager extends ViewManager {
int TAKE_SCREENSHOT_PROVIDED_IMAGE = 3;
/**
- * Parcel key for the screen shot bitmap sent with messages of type
- * {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}, type {@link android.graphics.Bitmap}
- * @hide
- */
- String PARCEL_KEY_SCREENSHOT_BITMAP = "screenshot_screen_bitmap";
-
- /**
- * Parcel key for the screen bounds of the image sent with messages of type
- * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link Rect} in screen coordinates.
- * @hide
- */
- String PARCEL_KEY_SCREENSHOT_BOUNDS = "screenshot_screen_bounds";
-
- /**
- * Parcel key for the task id of the task that the screen shot was taken of, sent with messages
- * of type [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type int.
- * @hide
- */
- String PARCEL_KEY_SCREENSHOT_TASK_ID = "screenshot_task_id";
-
- /**
- * Parcel key for the visible insets of the image sent with messages of type
- * [@link {@link #TAKE_SCREENSHOT_PROVIDED_IMAGE}], type {@link android.graphics.Insets} in
- * screen coordinates.
+ * Enum listing the possible sources from which a screenshot was originated. Used for logging.
+ *
* @hide
*/
- String PARCEL_KEY_SCREENSHOT_INSETS = "screenshot_insets";
+ @IntDef({ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS,
+ ScreenshotSource.SCREENSHOT_KEY_CHORD,
+ ScreenshotSource.SCREENSHOT_KEY_OTHER,
+ ScreenshotSource.SCREENSHOT_OVERVIEW,
+ ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS,
+ ScreenshotSource.SCREENSHOT_OTHER})
+ @interface ScreenshotSource {
+ int SCREENSHOT_GLOBAL_ACTIONS = 0;
+ int SCREENSHOT_KEY_CHORD = 1;
+ int SCREENSHOT_KEY_OTHER = 2;
+ int SCREENSHOT_OVERVIEW = 3;
+ int SCREENSHOT_ACCESSIBILITY_ACTIONS = 4;
+ int SCREENSHOT_OTHER = 5;
+ }
/**
* @hide
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index ec5130143086..397bce44b25e 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -24,7 +24,6 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.MergedConfiguration;
-import android.view.IWindowSession;
import java.util.HashMap;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f6c0d2077f1..7016c5cf0de6 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1213,7 +1213,7 @@ public class RemoteViews implements Parcelable, Filter {
BitmapReflectionAction(Parcel in) {
viewId = in.readInt();
- methodName = in.readString();
+ methodName = in.readString8();
bitmapId = in.readInt();
bitmap = mBitmapCache.getBitmapForId(bitmapId);
}
@@ -1221,7 +1221,7 @@ public class RemoteViews implements Parcelable, Filter {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(viewId);
- dest.writeString(methodName);
+ dest.writeString8(methodName);
dest.writeInt(bitmapId);
}
@@ -1282,7 +1282,7 @@ public class RemoteViews implements Parcelable, Filter {
ReflectionAction(Parcel in) {
this.viewId = in.readInt();
- this.methodName = in.readString();
+ this.methodName = in.readString8();
this.type = in.readInt();
//noinspection ConstantIfStatement
if (false) {
@@ -1318,7 +1318,7 @@ public class RemoteViews implements Parcelable, Filter {
this.value = (char)in.readInt();
break;
case STRING:
- this.value = in.readString();
+ this.value = in.readString8();
break;
case CHAR_SEQUENCE:
this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -1347,7 +1347,7 @@ public class RemoteViews implements Parcelable, Filter {
public void writeToParcel(Parcel out, int flags) {
out.writeInt(this.viewId);
- out.writeString(this.methodName);
+ out.writeString8(this.methodName);
out.writeInt(this.type);
//noinspection ConstantIfStatement
if (false) {
@@ -1383,7 +1383,7 @@ public class RemoteViews implements Parcelable, Filter {
out.writeInt((int)((Character)this.value).charValue());
break;
case STRING:
- out.writeString((String)this.value);
+ out.writeString8((String)this.value);
break;
case CHAR_SEQUENCE:
TextUtils.writeToParcel((CharSequence)this.value, out, flags);
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index d851a099d0e1..970bab9bc53b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2611,11 +2611,12 @@ public class ChooserActivity extends ResolverActivity implements
* does not match either the personal or work user handle.
**/
private int getProfileForUser(UserHandle currentUserHandle) {
- if (currentUserHandle == getPersonalProfileUserHandle()) {
+ if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
return PROFILE_PERSONAL;
- } else if (currentUserHandle == getWorkProfileUserHandle()) {
+ } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
return PROFILE_WORK;
}
+ Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
return -1;
}
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 36eecfb685e8..2c4892561ddd 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -50,6 +50,9 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
/**
* This is used in conjunction with
@@ -74,11 +77,13 @@ public class IntentForwarderActivity extends Activity {
private Injector mInjector;
private MetricsLogger mMetricsLogger;
+ protected ExecutorService mExecutorService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInjector = createInjector();
+ mExecutorService = Executors.newSingleThreadExecutor();
Intent intentReceived = getIntent();
String className = intentReceived.getComponent().getClassName();
@@ -118,30 +123,9 @@ public class IntentForwarderActivity extends Activity {
mInjector.getIPackageManager(), getContentResolver());
if (newIntent != null) {
newIntent.prepareToLeaveUser(callingUserId);
-
- final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY,
- targetUserId);
- try {
- startActivityAsCaller(newIntent, null, null, false, targetUserId);
- } catch (RuntimeException e) {
- int launchedFromUid = -1;
- String launchedFromPackage = "?";
- try {
- launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid(
- getActivityToken());
- launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage(
- getActivityToken());
- } catch (RemoteException ignored) {
- }
-
- Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
- + launchedFromPackage + ", while running in "
- + ActivityThread.currentProcessName(), e);
- }
-
- if (shouldShowDisclosure(ri, intentReceived)) {
- mInjector.showToast(userMessageId, Toast.LENGTH_LONG);
- }
+ maybeShowDisclosureAsync(intentReceived, newIntent, targetUserId, userMessageId);
+ CompletableFuture.runAsync(() -> startActivityAsCaller(
+ newIntent, targetUserId), mExecutorService);
} else {
Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
+ callingUserId + " to user " + targetUserId);
@@ -149,6 +133,44 @@ public class IntentForwarderActivity extends Activity {
finish();
}
+ private void maybeShowDisclosureAsync(
+ Intent intentReceived, Intent newIntent, int userId, int messageId) {
+ final CompletableFuture<ResolveInfo> resolveInfoFuture =
+ mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, userId);
+ resolveInfoFuture.thenAcceptAsync(ri -> {
+ if (shouldShowDisclosure(ri, intentReceived)) {
+ mInjector.showToast(messageId, Toast.LENGTH_LONG);
+ }
+ }, getApplicationContext().getMainExecutor());
+ }
+
+ private void startActivityAsCaller(Intent newIntent, int userId) {
+ try {
+ startActivityAsCaller(
+ newIntent,
+ /* options= */ null,
+ /* permissionToken= */ null,
+ /* ignoreTargetSecurity= */ false,
+ userId);
+ } catch (RuntimeException e) {
+ int launchedFromUid = -1;
+ String launchedFromPackage = "?";
+ try {
+ launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid(
+ getActivityToken());
+ launchedFromPackage = ActivityTaskManager.getService()
+ .getLaunchedFromPackage(getActivityToken());
+ } catch (RemoteException ignored) {
+ }
+
+ Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
+ + launchedFromPackage + ", while running in "
+ + ActivityThread.currentProcessName(), e);
+ } finally {
+ mExecutorService.shutdown();
+ }
+ }
+
private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) {
// When showing the sharesheet, instead of forwarding to the other profile,
// we launch the sharesheet in the current user and select the other tab.
@@ -163,7 +185,7 @@ public class IntentForwarderActivity extends Activity {
return;
}
sanitizeIntent(innerIntent);
- startActivity(intentReceived);
+ startActivityAsCaller(intentReceived, null, null, false, getUserId());
finish();
}
@@ -234,23 +256,7 @@ public class IntentForwarderActivity extends Activity {
Intent intentToCheck = forwardIntent;
if (Intent.ACTION_CHOOSER.equals(forwardIntent.getAction())) {
- // The EXTRA_INITIAL_INTENTS may not be allowed to be forwarded.
- if (forwardIntent.hasExtra(Intent.EXTRA_INITIAL_INTENTS)) {
- Slog.wtf(TAG, "An chooser intent with extra initial intents cannot be forwarded to"
- + " a different user");
- return null;
- }
- if (forwardIntent.hasExtra(Intent.EXTRA_REPLACEMENT_EXTRAS)) {
- Slog.wtf(TAG, "A chooser intent with replacement extras cannot be forwarded to a"
- + " different user");
- return null;
- }
- intentToCheck = forwardIntent.getParcelableExtra(Intent.EXTRA_INTENT);
- if (intentToCheck == null) {
- Slog.wtf(TAG, "Cannot forward a chooser intent with no extra "
- + Intent.EXTRA_INTENT);
- return null;
- }
+ return null;
}
if (forwardIntent.getSelector() != null) {
intentToCheck = forwardIntent.getSelector();
@@ -338,8 +344,11 @@ public class IntentForwarderActivity extends Activity {
}
@Override
- public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
- return getPackageManager().resolveActivityAsUser(intent, flags, userId);
+ @Nullable
+ public CompletableFuture<ResolveInfo> resolveActivityAsUser(
+ Intent intent, int flags, int userId) {
+ return CompletableFuture.supplyAsync(
+ () -> getPackageManager().resolveActivityAsUser(intent, flags, userId));
}
@Override
@@ -355,7 +364,7 @@ public class IntentForwarderActivity extends Activity {
PackageManager getPackageManager();
- ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId);
+ CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId);
void showToast(@StringRes int messageId, int duration);
}
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 43bd4a610910..64324756796a 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6093,7 +6093,8 @@ public class BatteryStatsImpl extends BatteryStats {
return array;
}
- public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
+ /** @hide */
+ public void noteNetworkInterfaceType(String iface, int networkType) {
if (TextUtils.isEmpty(iface)) return;
synchronized (mModemNetworkLock) {
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index ebfea450af88..56a6db95badc 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -46,6 +46,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
r.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
false, this, UserHandle.USER_ALL);
+ r.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+ false, this, UserHandle.USER_ALL);
}
public void unregister() {
@@ -68,6 +71,11 @@ public class GestureNavigationSettingsObserver extends ContentObserver {
return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT);
}
+ public boolean areNavigationButtonForcedVisible() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0;
+ }
+
private int getSensitivity(Resources userRes, String side) {
final int inset = userRes.getDimensionPixelSize(
com.android.internal.R.dimen.config_backGestureInset);
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 25c114f4b7c1..23ba6530b072 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -79,6 +79,7 @@ import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.IRotationWatcher.Stub;
+import android.view.IScrollCaptureController;
import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -89,6 +90,7 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.ScrollCaptureCallback;
import android.view.SearchEvent;
import android.view.SurfaceHolder.Callback2;
import android.view.View;
@@ -3916,4 +3918,35 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
: null);
}
}
+
+ /**
+ * System request to begin scroll capture.
+ *
+ * @param controller the controller to receive responses
+ * @hide
+ */
+ @Override
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ getViewRootImpl().dispatchScrollCaptureRequest(controller);
+ }
+
+ /**
+ * Registers a handler providing scrolling capture support for window content.
+ *
+ * @param callback the callback to add
+ */
+ @Override
+ public void addScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
+ getViewRootImpl().addScrollCaptureCallback(callback);
+ }
+
+ /**
+ * Unregisters the given {@link ScrollCaptureCallback}.
+ *
+ * @param callback the callback to remove
+ */
+ @Override
+ public void removeScrollCaptureCallback(@NonNull ScrollCaptureCallback callback) {
+ getViewRootImpl().removeScrollCaptureCallback(callback);
+ }
}
diff --git a/core/java/com/android/internal/util/ScreenshotHelper.java b/core/java/com/android/internal/util/ScreenshotHelper.java
index 7cff90bbf437..adadc5e20549 100644
--- a/core/java/com/android/internal/util/ScreenshotHelper.java
+++ b/core/java/com/android/internal/util/ScreenshotHelper.java
@@ -1,5 +1,7 @@
package com.android.internal.util;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ComponentName;
@@ -10,11 +12,12 @@ import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.net.Uri;
-import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
@@ -23,6 +26,109 @@ import android.view.WindowManager;
import java.util.function.Consumer;
public class ScreenshotHelper {
+
+ /**
+ * Describes a screenshot request (to make it easier to pass data through to the handler).
+ */
+ public static class ScreenshotRequest implements Parcelable {
+ private int mSource;
+ private boolean mHasStatusBar;
+ private boolean mHasNavBar;
+ private Bitmap mBitmap;
+ private Rect mBoundsInScreen;
+ private Insets mInsets;
+ private int mTaskId;
+
+ ScreenshotRequest(int source, boolean hasStatus, boolean hasNav) {
+ mSource = source;
+ mHasStatusBar = hasStatus;
+ mHasNavBar = hasNav;
+ }
+
+ ScreenshotRequest(
+ int source, Bitmap bitmap, Rect boundsInScreen, Insets insets, int taskId) {
+ mSource = source;
+ mBitmap = bitmap;
+ mBoundsInScreen = boundsInScreen;
+ mInsets = insets;
+ mTaskId = taskId;
+ }
+
+ ScreenshotRequest(Parcel in) {
+ mSource = in.readInt();
+ mHasStatusBar = in.readBoolean();
+ mHasNavBar = in.readBoolean();
+ if (in.readInt() == 1) {
+ mBitmap = in.readParcelable(Bitmap.class.getClassLoader());
+ mBoundsInScreen = in.readParcelable(Rect.class.getClassLoader());
+ mInsets = in.readParcelable(Insets.class.getClassLoader());
+ mTaskId = in.readInt();
+ }
+ }
+
+ public int getSource() {
+ return mSource;
+ }
+
+ public boolean getHasStatusBar() {
+ return mHasStatusBar;
+ }
+
+ public boolean getHasNavBar() {
+ return mHasNavBar;
+ }
+
+ public Bitmap getBitmap() {
+ return mBitmap;
+ }
+
+ public Rect getBoundsInScreen() {
+ return mBoundsInScreen;
+ }
+
+ public Insets getInsets() {
+ return mInsets;
+ }
+
+ public int getTaskId() {
+ return mTaskId;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSource);
+ dest.writeBoolean(mHasStatusBar);
+ dest.writeBoolean(mHasNavBar);
+ if (mBitmap == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeParcelable(mBitmap, 0);
+ dest.writeParcelable(mBoundsInScreen, 0);
+ dest.writeParcelable(mInsets, 0);
+ dest.writeInt(mTaskId);
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<ScreenshotRequest> CREATOR =
+ new Parcelable.Creator<ScreenshotRequest>() {
+
+ @Override
+ public ScreenshotRequest createFromParcel(Parcel source) {
+ return new ScreenshotRequest(source);
+ }
+
+ @Override
+ public ScreenshotRequest[] newArray(int size) {
+ return new ScreenshotRequest[size];
+ }
+ };
+ }
private static final String TAG = "ScreenshotHelper";
// Time until we give up on the screenshot & show an error instead.
@@ -36,8 +142,10 @@ public class ScreenshotHelper {
mContext = context;
}
+
+
/**
- * Request a screenshot be taken with a specific timeout.
+ * Request a screenshot be taken.
*
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
@@ -47,6 +155,32 @@ public class ScreenshotHelper {
* or
* {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
* @param hasStatus {@code true} if the status bar is currently showing. {@code false}
+ * if not.
+ * @param hasNav {@code true} if the navigation bar is currently showing. {@code
+ * false} if not.
+ * @param source The source of the screenshot request. One of
+ * {SCREENSHOT_GLOBAL_ACTIONS, SCREENSHOT_KEY_CHORD,
+ * SCREENSHOT_OVERVIEW, SCREENSHOT_OTHER}
+ * @param handler A handler used in case the screenshot times out
+ * @param completionConsumer Consumes `false` if a screenshot was not taken, and `true` if the
+ * screenshot was taken.
+ */
+ public void takeScreenshot(final int screenshotType, final boolean hasStatus,
+ final boolean hasNav, int source, @NonNull Handler handler,
+ @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(source, hasStatus, hasNav);
+ takeScreenshot(screenshotType, SCREENSHOT_TIMEOUT_MS, handler, screenshotRequest,
+ completionConsumer);
+ }
+
+ /**
+ * Request a screenshot be taken, with provided reason.
+ *
+ * @param screenshotType The type of screenshot, for example either
+ * {@link android.view.WindowManager#TAKE_SCREENSHOT_FULLSCREEN}
+ * or
+ * {@link android.view.WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+ * @param hasStatus {@code true} if the status bar is currently showing. {@code false}
* if
* not.
* @param hasNav {@code true} if the navigation bar is currently showing. {@code
@@ -64,7 +198,7 @@ public class ScreenshotHelper {
}
/**
- * Request a screenshot be taken.
+ * Request a screenshot be taken with a specific timeout.
*
* Added to support reducing unit test duration; the method variant without a timeout argument
* is recommended for general use.
@@ -89,9 +223,9 @@ public class ScreenshotHelper {
public void takeScreenshot(final int screenshotType, final boolean hasStatus,
final boolean hasNav, long timeoutMs, @NonNull Handler handler,
@Nullable Consumer<Uri> completionConsumer) {
- takeScreenshot(screenshotType, hasStatus, hasNav, timeoutMs, handler, null,
- completionConsumer
- );
+ ScreenshotRequest screenshotRequest = new ScreenshotRequest(SCREENSHOT_OTHER, hasStatus,
+ hasNav);
+ takeScreenshot(screenshotType, timeoutMs, handler, screenshotRequest, completionConsumer);
}
/**
@@ -106,23 +240,16 @@ public class ScreenshotHelper {
* screenshot was taken.
*/
public void provideScreenshot(@NonNull Bitmap screenshot, @NonNull Rect boundsInScreen,
- @NonNull Insets insets, int taskId, @NonNull Handler handler,
- @Nullable Consumer<Uri> completionConsumer) {
- Bundle imageBundle = new Bundle();
- imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP, screenshot);
- imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS, boundsInScreen);
- imageBundle.putParcelable(WindowManager.PARCEL_KEY_SCREENSHOT_INSETS, insets);
- imageBundle.putInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID, taskId);
-
- takeScreenshot(
- WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE,
- false, false, // ignored when image bundle is set
- SCREENSHOT_TIMEOUT_MS, handler, imageBundle, completionConsumer);
+ @NonNull Insets insets, int taskId, int source,
+ @NonNull Handler handler, @Nullable Consumer<Uri> completionConsumer) {
+ ScreenshotRequest screenshotRequest =
+ new ScreenshotRequest(source, screenshot, boundsInScreen, insets, taskId);
+ takeScreenshot(WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_TIMEOUT_MS,
+ handler, screenshotRequest, completionConsumer);
}
- private void takeScreenshot(final int screenshotType, final boolean hasStatus,
- final boolean hasNav, long timeoutMs, @NonNull Handler handler,
- @Nullable Bundle providedImage, @Nullable Consumer<Uri> completionConsumer) {
+ private void takeScreenshot(final int screenshotType, long timeoutMs, @NonNull Handler handler,
+ ScreenshotRequest screenshotRequest, @Nullable Consumer<Uri> completionConsumer) {
synchronized (mScreenshotLock) {
if (mScreenshotConnection != null) {
return;
@@ -157,7 +284,7 @@ public class ScreenshotHelper {
return;
}
Messenger messenger = new Messenger(service);
- Message msg = Message.obtain(null, screenshotType);
+ Message msg = Message.obtain(null, screenshotType, screenshotRequest);
final ServiceConnection myConn = this;
Handler h = new Handler(handler.getLooper()) {
@Override
@@ -175,12 +302,6 @@ public class ScreenshotHelper {
}
};
msg.replyTo = new Messenger(h);
- msg.arg1 = hasStatus ? 1 : 0;
- msg.arg2 = hasNav ? 1 : 0;
-
- if (screenshotType == WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) {
- msg.setData(providedImage);
- }
try {
messenger.send(msg);
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 47f094f292f2..7f3eb4515654 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -26,6 +26,7 @@ import android.os.RemoteException;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.DragEvent;
+import android.view.IScrollCaptureController;
import android.view.IWindow;
import android.view.IWindowSession;
import android.view.InsetsSourceControl;
@@ -169,4 +170,13 @@ public class BaseIWindow extends IWindow.Stub {
@Override
public void dispatchPointerCaptureChanged(boolean hasCapture) {
}
+
+ @Override
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ try {
+ controller.onClientUnavailable();
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/ScrollCaptureInternal.java b/core/java/com/android/internal/view/ScrollCaptureInternal.java
new file mode 100644
index 000000000000..c589afdeaa1a
--- /dev/null
+++ b/core/java/com/android/internal/view/ScrollCaptureInternal.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.annotation.Nullable;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.ScrollCaptureCallback;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Provides built-in framework level Scroll Capture support for standard scrolling Views.
+ */
+public class ScrollCaptureInternal {
+ private static final String TAG = "ScrollCaptureInternal";
+
+ private static final int UP = -1;
+ private static final int DOWN = 1;
+
+ /**
+ * Not a ViewGroup, or cannot scroll according to View APIs.
+ */
+ public static final int TYPE_FIXED = 0;
+
+ /**
+ * Slides a single child view using mScrollX/mScrollY.
+ */
+ public static final int TYPE_SCROLLING = 1;
+
+ /**
+ * Slides child views through the viewport by translating their layout positions with {@link
+ * View#offsetTopAndBottom(int)}. Manages Child view lifecycle, creating as needed and
+ * binding views to data from an adapter. Views are reused whenever possible.
+ */
+ public static final int TYPE_RECYCLING = 2;
+
+ /**
+ * Performs tests on the given View and determines:
+ * 1. If scrolling is possible
+ * 2. What mechanisms are used for scrolling.
+ * <p>
+ * This needs to be fast and not alloc memory. It's called on everything in the tree not marked
+ * as excluded during scroll capture search.
+ */
+ public static int detectScrollingType(View view) {
+ // Must be a ViewGroup
+ if (!(view instanceof ViewGroup)) {
+ return TYPE_FIXED;
+ }
+ // Confirm that it can scroll.
+ if (!(view.canScrollVertically(DOWN) || view.canScrollVertically(UP))) {
+ // Nothing to scroll here, move along.
+ return TYPE_FIXED;
+ }
+ // ScrollViews accept only a single child.
+ if (((ViewGroup) view).getChildCount() > 1) {
+ return TYPE_RECYCLING;
+ }
+ //Because recycling containers don't use scrollY, a non-zero value means Scroll view.
+ if (view.getScrollY() != 0) {
+ return TYPE_SCROLLING;
+ }
+ // Since scrollY cannot be negative, this means a Recycling view.
+ if (view.canScrollVertically(UP)) {
+ return TYPE_RECYCLING;
+ }
+ // canScrollVertically(UP) == false, getScrollY() == 0, getChildCount() == 1.
+
+ // For Recycling containers, this should be a no-op (RecyclerView logs a warning)
+ view.scrollTo(view.getScrollX(), 1);
+
+ // A scrolling container would have moved by 1px.
+ if (view.getScrollY() == 1) {
+ view.scrollTo(view.getScrollX(), 0);
+ return TYPE_SCROLLING;
+ }
+ return TYPE_RECYCLING;
+ }
+
+ /**
+ * Creates a scroll capture callback for the given view if possible.
+ *
+ * @param view the view to capture
+ * @param localVisibleRect the visible area of the given view in local coordinates, as supplied
+ * by the view parent
+ * @param positionInWindow the offset of localVisibleRect within the window
+ *
+ * @return a new callback or null if the View isn't supported
+ */
+ @Nullable
+ public ScrollCaptureCallback requestCallback(View view, Rect localVisibleRect,
+ Point positionInWindow) {
+ // Nothing to see here yet.
+ int i = detectScrollingType(view);
+ switch (i) {
+ case TYPE_SCROLLING:
+ return new ScrollCaptureViewSupport<>((ViewGroup) view,
+ new ScrollViewCaptureHelper());
+ }
+ return null;
+ }
+}
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewHelper.java b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
new file mode 100644
index 000000000000..9f100bd6440f
--- /dev/null
+++ b/core/java/com/android/internal/view/ScrollCaptureViewHelper.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.view.View;
+
+interface ScrollCaptureViewHelper<V extends View> {
+ int UP = -1;
+ int DOWN = 1;
+
+ /**
+ * Verifies that the view is still visible and scrollable. If true is returned here, expect a
+ * call to {@link #onComputeScrollBounds(View)} to follow.
+ *
+ * @param view the view being captured
+ * @return true if the callback should respond to a request with scroll bounds
+ */
+ default boolean onAcceptSession(@Nullable V view) {
+ return view != null && view.isVisibleToUser()
+ && (view.canScrollVertically(UP) || view.canScrollVertically(DOWN));
+ }
+
+ /**
+ * Given a scroll capture request for a view, adjust the provided rect to cover the scrollable
+ * content area. The default implementation returns the padded content area of {@code view}.
+ *
+ * @param view the view being captured
+ */
+ default Rect onComputeScrollBounds(@Nullable V view) {
+ return new Rect(view.getPaddingLeft(), view.getPaddingTop(),
+ view.getWidth() - view.getPaddingRight(),
+ view.getHeight() - view.getPaddingBottom());
+ }
+ /**
+ * Adjust the target for capture.
+ * <p>
+ * Do not touch anything that may change layout positions or sizes on screen. Anything else may
+ * be adjusted as long as it can be reversed in {@link #onPrepareForEnd(View)}.
+ *
+ * @param view the view being captured
+ * @param scrollBounds the bounds within {@code view} where content scrolls
+ */
+ void onPrepareForStart(@NonNull V view, Rect scrollBounds);
+
+ /**
+ * Map the request onto the screen.
+ * <p>
+ * Given a rect describing the area to capture, relative to scrollBounds, take actions
+ * necessary to bring the content within the rectangle into the visible area of the view if
+ * needed and return the resulting rectangle describing the position and bounds of the area
+ * which is visible.
+ *
+ * @param scrollBounds the area in which scrolling content moves, local to the {@code containing
+ * view}
+ * @param requestRect the area relative to {@code scrollBounds} which describes the location of
+ * content to capture for the request
+ * @return the visible area within scrollBounds of the requested rectangle, return {@code null}
+ * in the case of an unrecoverable error condition, to abort the capture process
+ */
+ Rect onScrollRequested(@NonNull V view, Rect scrollBounds, Rect requestRect);
+
+ /**
+ * Restore the target after capture.
+ * <p>
+ * Put back anything that was changed in {@link #onPrepareForStart(View, Rect)}.
+ *
+ * @param view the view being captured
+ */
+ void onPrepareForEnd(@NonNull V view);
+}
diff --git a/core/java/com/android/internal/view/ScrollCaptureViewSupport.java b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
new file mode 100644
index 000000000000..4087eda944e0
--- /dev/null
+++ b/core/java/com/android/internal/view/ScrollCaptureViewSupport.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.graphics.HardwareRenderer;
+import android.graphics.Matrix;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.RenderNode;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.view.ScrollCaptureCallback;
+import android.view.ScrollCaptureSession;
+import android.view.Surface;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+import java.util.function.Consumer;
+
+/**
+ * Provides a ScrollCaptureCallback implementation for to handle arbitrary View-based scrolling
+ * containers.
+ * <p>
+ * To use this class, supply the target view and an implementation of {@ScrollCaptureViewHelper}
+ * to the callback.
+ *
+ * @param <V> the specific View subclass handled
+ * @hide
+ */
+public class ScrollCaptureViewSupport<V extends View> implements ScrollCaptureCallback {
+
+ private final WeakReference<V> mWeakView;
+ private final ScrollCaptureViewHelper<V> mViewHelper;
+ private ViewRenderer mRenderer;
+ private Handler mUiHandler;
+ private boolean mStarted;
+ private boolean mEnded;
+
+ static <V extends View> ScrollCaptureCallback createCallback(V view,
+ ScrollCaptureViewHelper<V> impl) {
+ return new ScrollCaptureViewSupport<>(view, impl);
+ }
+
+ ScrollCaptureViewSupport(V containingView, ScrollCaptureViewHelper<V> viewHelper) {
+ mWeakView = new WeakReference<>(containingView);
+ mRenderer = new ViewRenderer();
+ mUiHandler = containingView.getHandler();
+ mViewHelper = viewHelper;
+ }
+
+ // Base implementation of ScrollCaptureCallback
+
+ @Override
+ public final void onScrollCaptureSearch(Consumer<Rect> onReady) {
+ V view = mWeakView.get();
+ mStarted = false;
+ mEnded = false;
+
+ if (view != null && view.isVisibleToUser() && mViewHelper.onAcceptSession(view)) {
+ onReady.accept(mViewHelper.onComputeScrollBounds(view));
+ return;
+ }
+ onReady.accept(null);
+ }
+
+ @Override
+ public final void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) {
+ V view = mWeakView.get();
+ mEnded = false;
+ mStarted = true;
+
+ // Note: If somehow the view is already gone or detached, the first call to
+ // {@code onScrollCaptureImageRequest} will return an error and request the session to
+ // end.
+ if (view != null && view.isVisibleToUser()) {
+ mRenderer.setSurface(session.getSurface());
+ mViewHelper.onPrepareForStart(view, session.getScrollBounds());
+ }
+ onReady.run();
+ }
+
+ @Override
+ public final void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect requestRect) {
+ V view = mWeakView.get();
+ if (view == null || !view.isVisibleToUser()) {
+ // Signal to the controller that we have a problem and can't continue.
+ session.notifyBufferSent(0, null);
+ return;
+ }
+ Rect captureArea = mViewHelper.onScrollRequested(view, session.getScrollBounds(),
+ requestRect);
+ mRenderer.renderFrame(view, captureArea, mUiHandler,
+ () -> session.notifyBufferSent(0, captureArea));
+ }
+
+ @Override
+ public final void onScrollCaptureEnd(Runnable onReady) {
+ V view = mWeakView.get();
+ if (mStarted && !mEnded) {
+ mViewHelper.onPrepareForEnd(view);
+ /* empty */
+ mEnded = true;
+ mRenderer.trimMemory();
+ mRenderer.setSurface(null);
+ }
+ onReady.run();
+ }
+
+ /**
+ * Internal helper class which assists in rendering sections of the view hierarchy relative to a
+ * given view. Used by framework implementations of ScrollCaptureHandler to render and dispatch
+ * image requests.
+ */
+ static final class ViewRenderer {
+ // alpha, "reasonable default" from Javadoc
+ private static final float AMBIENT_SHADOW_ALPHA = 0.039f;
+ private static final float SPOT_SHADOW_ALPHA = 0.039f;
+
+ // Default values:
+ // lightX = (screen.width() / 2) - windowLeft
+ // lightY = 0 - windowTop
+ // lightZ = 600dp
+ // lightRadius = 800dp
+ private static final float LIGHT_Z_DP = 400;
+ private static final float LIGHT_RADIUS_DP = 800;
+ private static final String TAG = "ViewRenderer";
+
+ private HardwareRenderer mRenderer;
+ private RenderNode mRootRenderNode;
+ private final RectF mTempRectF = new RectF();
+ private final Rect mSourceRect = new Rect();
+ private final Rect mTempRect = new Rect();
+ private final Matrix mTempMatrix = new Matrix();
+ private final int[] mTempLocation = new int[2];
+ private long mLastRenderedSourceDrawingId = -1;
+
+
+ ViewRenderer() {
+ mRenderer = new HardwareRenderer();
+ mRootRenderNode = new RenderNode("ScrollCaptureRoot");
+ mRenderer.setContentRoot(mRootRenderNode);
+
+ // TODO: Figure out a way to flip this on when we are sure the source window is opaque
+ mRenderer.setOpaque(false);
+ }
+
+ public void setSurface(Surface surface) {
+ mRenderer.setSurface(surface);
+ }
+
+ /**
+ * Cache invalidation check. If the source view is the same as the previous call (which is
+ * mostly always the case, then we can skip setting up lighting on each call (for now)
+ *
+ * @return true if the view changed, false if the view was previously rendered by this class
+ */
+ private boolean updateForView(View source) {
+ if (mLastRenderedSourceDrawingId == source.getUniqueDrawingId()) {
+ return false;
+ }
+ mLastRenderedSourceDrawingId = source.getUniqueDrawingId();
+ return true;
+ }
+
+ // TODO: may need to adjust lightY based on the virtual canvas position to get
+ // consistent shadow positions across the whole capture. Or possibly just
+ // pull lightZ way back to make shadows more uniform.
+ private void setupLighting(View mSource) {
+ mLastRenderedSourceDrawingId = mSource.getUniqueDrawingId();
+ DisplayMetrics metrics = mSource.getResources().getDisplayMetrics();
+ mSource.getLocationOnScreen(mTempLocation);
+ final float lightX = metrics.widthPixels / 2f - mTempLocation[0];
+ final float lightY = metrics.heightPixels - mTempLocation[1];
+ final int lightZ = (int) (LIGHT_Z_DP * metrics.density);
+ final int lightRadius = (int) (LIGHT_RADIUS_DP * metrics.density);
+
+ // Enable shadows for elevation/Z
+ mRenderer.setLightSourceGeometry(lightX, lightY, lightZ, lightRadius);
+ mRenderer.setLightSourceAlpha(AMBIENT_SHADOW_ALPHA, SPOT_SHADOW_ALPHA);
+
+ }
+
+ public void renderFrame(View localReference, Rect sourceRect, Handler handler,
+ Runnable onFrameCommitted) {
+ if (updateForView(localReference)) {
+ setupLighting(localReference);
+ }
+ buildRootDisplayList(localReference, sourceRect);
+ HardwareRenderer.FrameRenderRequest request = mRenderer.createRenderRequest();
+ request.setVsyncTime(SystemClock.elapsedRealtimeNanos());
+ request.setFrameCommitCallback(handler::post, onFrameCommitted);
+ request.setWaitForPresent(true);
+ request.syncAndDraw();
+ }
+
+ public void trimMemory() {
+ mRenderer.clearContent();
+ }
+
+ public void destroy() {
+ mRenderer.destroy();
+ }
+
+ private void transformToRoot(View local, Rect localRect, Rect outRect) {
+ mTempMatrix.reset();
+ local.transformMatrixToGlobal(mTempMatrix);
+ mTempRectF.set(localRect);
+ mTempMatrix.mapRect(mTempRectF);
+ mTempRectF.round(outRect);
+ }
+
+ private void buildRootDisplayList(View source, Rect localSourceRect) {
+ final View captureSource = source.getRootView();
+ transformToRoot(source, localSourceRect, mTempRect);
+ mRootRenderNode.setPosition(0, 0, mTempRect.width(), mTempRect.height());
+ RecordingCanvas canvas = mRootRenderNode.beginRecording(mTempRect.width(),
+ mTempRect.height());
+ canvas.translate(-mTempRect.left, -mTempRect.top);
+ canvas.drawRenderNode(captureSource.updateDisplayListIfDirty());
+ mRootRenderNode.endRecording();
+ }
+ }
+}
diff --git a/core/java/com/android/internal/view/ScrollViewCaptureHelper.java b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
new file mode 100644
index 000000000000..12bd461f810b
--- /dev/null
+++ b/core/java/com/android/internal/view/ScrollViewCaptureHelper.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.annotation.NonNull;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * ScrollCapture for ScrollView and <i>ScrollView-like</i> ViewGroups.
+ * <p>
+ * Requirements for proper operation:
+ * <ul>
+ * <li>contains at most 1 child.
+ * <li>scrolls to absolute positions with {@link View#scrollTo(int, int)}.
+ * <li>has a finite, known content height and scrolling range
+ * <li>correctly implements {@link View#canScrollVertically(int)}
+ * <li>correctly implements {@link ViewParent#requestChildRectangleOnScreen(View,
+ * Rect, boolean)}
+ * </ul>
+ */
+public class ScrollViewCaptureHelper implements ScrollCaptureViewHelper<ViewGroup> {
+ private int mStartScrollY;
+ private boolean mScrollBarEnabled;
+ private int mOverScrollMode;
+
+ /** @see ScrollCaptureViewHelper#onPrepareForStart(View, Rect) */
+ public void onPrepareForStart(@NonNull ViewGroup view, Rect scrollBounds) {
+ mStartScrollY = view.getScrollY();
+ mOverScrollMode = view.getOverScrollMode();
+ if (mOverScrollMode != View.OVER_SCROLL_NEVER) {
+ view.setOverScrollMode(View.OVER_SCROLL_NEVER);
+ }
+ mScrollBarEnabled = view.isVerticalScrollBarEnabled();
+ if (mScrollBarEnabled) {
+ view.setVerticalScrollBarEnabled(false);
+ }
+ }
+
+ /** @see ScrollCaptureViewHelper#onScrollRequested(View, Rect, Rect) */
+ public Rect onScrollRequested(@NonNull ViewGroup view, Rect scrollBounds, Rect requestRect) {
+ final View contentView = view.getChildAt(0); // returns null, does not throw IOOBE
+ if (contentView == null) {
+ return null;
+ }
+ /*
+ +---------+ <----+ Content [25,25 - 275,1025] (w=250,h=1000)
+ | |
+ ...|.........|... startScrollY=100
+ | |
+ +--+---------+---+ <--+ Container View [0,0 - 300,300] (scrollY=200)
+ | . . |
+ --- | . +-----+ <------+ Scroll Bounds [50,50 - 250,250] (200x200)
+ ^ | . | | . | (Local to Container View, fixed/un-scrolled)
+ | | . | | . |
+ | | . | | . |
+ | | . +-----+ . |
+ | | . . |
+ | +--+---------+---+
+ | | |
+ -+- | +-----+ |
+ | |#####| | <--+ Requested Bounds [0,300 - 200,400] (200x100)
+ | +-----+ | (Local to Scroll Bounds, fixed/un-scrolled)
+ | |
+ +---------+
+
+ Container View (ScrollView) [0,0 - 300,300] (scrollY = 200)
+ \__ Content [25,25 - 275,1025] (250x1000) (contentView)
+ \__ Scroll Bounds[50,50 - 250,250] (w=200,h=200)
+ \__ Requested Bounds[0,300 - 200,400] (200x100)
+ */
+
+ // 0) adjust the requestRect to account for scroll change since start
+ //
+ // Scroll Bounds[50,50 - 250,250] (w=200,h=200)
+ // \__ Requested Bounds[0,200 - 200,300] (200x100)
+
+ // (y-100) (scrollY - mStartScrollY)
+ int scrollDelta = view.getScrollY() - mStartScrollY;
+
+ // 1) Translate request rect to make it relative to container view
+ //
+ // Container View [0,0 - 300,300] (scrollY=200)
+ // \__ Requested Bounds[50,250 - 250,350] (w=250, h=100)
+
+ // (x+50,y+50)
+ Rect requestedContainerBounds = new Rect(requestRect);
+ requestedContainerBounds.offset(0, -scrollDelta);
+ requestedContainerBounds.offset(scrollBounds.left, scrollBounds.top);
+
+ // 2) Translate from container to contentView relative (applying container scrollY)
+ //
+ // Container View [0,0 - 300,300] (scrollY=200)
+ // \__ Content [25,25 - 275,1025] (250x1000) (contentView)
+ // \__ Requested Bounds[25,425 - 200,525] (w=250, h=100)
+
+ // (x-25,y+175) (scrollY - content.top)
+ Rect requestedContentBounds = new Rect(requestedContainerBounds);
+ requestedContentBounds.offset(
+ view.getScrollX() - contentView.getLeft(),
+ view.getScrollY() - contentView.getTop());
+
+
+
+ // requestRect is now local to contentView as requestedContentBounds
+ // contentView (and each parent in turn if possible) will be scrolled
+ // (if necessary) to make all of requestedContent visible, (if possible!)
+ contentView.requestRectangleOnScreen(new Rect(requestedContentBounds), true);
+
+ // update new offset between starting and current scroll position
+ scrollDelta = view.getScrollY() - mStartScrollY;
+
+
+ // TODO: adjust to avoid occlusions/minimize scroll changes
+
+ Point offset = new Point();
+ final Rect capturedRect = new Rect(requestedContentBounds); // empty
+ if (!view.getChildVisibleRect(contentView, capturedRect, offset)) {
+ capturedRect.setEmpty();
+ return capturedRect;
+ }
+ // Transform back from global to content-view local
+ capturedRect.offset(-offset.x, -offset.y);
+
+ // Then back to container view
+ capturedRect.offset(
+ contentView.getLeft() - view.getScrollX(),
+ contentView.getTop() - view.getScrollY());
+
+
+ // And back to relative to scrollBounds
+ capturedRect.offset(-scrollBounds.left, -scrollBounds.top);
+
+ // Apply scrollDelta again to return to make capturedRect relative to scrollBounds at
+ // the scroll position at start of capture.
+ capturedRect.offset(0, scrollDelta);
+ return capturedRect;
+ }
+
+ /** @see ScrollCaptureViewHelper#onPrepareForEnd(View) */
+ public void onPrepareForEnd(@NonNull ViewGroup view) {
+ view.scrollTo(0, mStartScrollY);
+ if (mOverScrollMode != View.OVER_SCROLL_NEVER) {
+ view.setOverScrollMode(mOverScrollMode);
+ }
+ if (mScrollBarEnabled) {
+ view.setVerticalScrollBarEnabled(true);
+ }
+ }
+}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ba7aef7c208e..b51d4f509f38 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -1244,12 +1244,11 @@ void AndroidRuntime::exit(int code)
{
if (mExitWithoutCleanup) {
ALOGI("VM exiting with result code %d, cleanup skipped.", code);
- ::_exit(code);
} else {
ALOGI("VM exiting with result code %d.", code);
onExit(code);
- ::exit(code);
}
+ ::_exit(code);
}
void AndroidRuntime::onVmCreated(JNIEnv* env)
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 483b455965f7..e7a2fb428b50 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -273,7 +273,28 @@ static void android_os_Parcel_writeDouble(JNIEnv* env, jclass clazz, jlong nativ
}
}
-static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
+static void android_os_Parcel_writeString8(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
+{
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel != NULL) {
+ status_t err = NO_MEMORY;
+ if (val) {
+ const size_t len = env->GetStringUTFLength(val);
+ const char* str = env->GetStringUTFChars(val, 0);
+ if (str) {
+ err = parcel->writeString8(str, len);
+ env->ReleaseStringUTFChars(val, str);
+ }
+ } else {
+ err = parcel->writeString8(NULL, 0);
+ }
+ if (err != NO_ERROR) {
+ signalExceptionForError(env, clazz, err);
+ }
+ }
+}
+
+static void android_os_Parcel_writeString16(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -444,7 +465,21 @@ static jdouble android_os_Parcel_readDouble(jlong nativePtr)
return 0;
}
-static jstring android_os_Parcel_readString(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jstring android_os_Parcel_readString8(JNIEnv* env, jclass clazz, jlong nativePtr)
+{
+ Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+ if (parcel != NULL) {
+ size_t len;
+ const char* str = parcel->readString8Inplace(&len);
+ if (str) {
+ return env->NewStringUTF(str);
+ }
+ return NULL;
+ }
+ return NULL;
+}
+
+static jstring android_os_Parcel_readString16(JNIEnv* env, jclass clazz, jlong nativePtr)
{
Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
if (parcel != NULL) {
@@ -722,7 +757,9 @@ static const JNINativeMethod gParcelMethods[] = {
// @FastNative
{"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble},
// @FastNative
- {"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
+ {"nativeWriteString8", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString8},
+ // @FastNative
+ {"nativeWriteString16", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString16},
// @FastNative
{"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
// @FastNative
@@ -740,7 +777,9 @@ static const JNINativeMethod gParcelMethods[] = {
// @CriticalNative
{"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble},
// @FastNative
- {"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
+ {"nativeReadString8", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString8},
+ // @FastNative
+ {"nativeReadString16", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString16},
// @FastNative
{"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
// @FastNative
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 924dc4b3a051..21985f0bb4bb 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -119,6 +119,7 @@ typedef const std::function<void(std::string)>& fail_fn_t;
static pid_t gSystemServerPid = 0;
+static constexpr const char* kVoldAppDataIsolation = "persist.sys.vold_app_data_isolation_enabled";
static constexpr const char* kPropFuse = "persist.sys.fuse";
static const char kZygoteClassName[] = "com/android/internal/os/Zygote";
static jclass gZygoteClass;
@@ -831,6 +832,7 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode,
multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
bool isFuse = GetBoolProperty(kPropFuse, false);
+ bool isAppDataIsolationEnabled = GetBoolProperty(kVoldAppDataIsolation, false);
if (isFuse) {
if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
@@ -840,6 +842,9 @@ static void MountEmulatedStorage(uid_t uid, jint mount_mode,
} else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
BindMount(installer_source, "/storage", fail_fn);
+ } else if (isAppDataIsolationEnabled && mount_mode == MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+ const std::string writable_source = StringPrintf("/mnt/androidwritable/%d", user_id);
+ BindMount(writable_source, "/storage", fail_fn);
} else {
BindMount(user_source, "/storage", fail_fn);
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 6e8b9de2e732..ee25ac27b25c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1111,12 +1111,13 @@
grants your app this permission. If you don't need this permission, be sure your <a
href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
targetSdkVersion}</a> is 4 or higher.
- <p>Protection level: normal
+ <p>Protection level: dangerous
-->
<permission android:name="android.permission.READ_PHONE_STATE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
android:label="@string/permlab_readPhoneState"
android:description="@string/permdesc_readPhoneState"
- android:protectionLevel="normal" />
+ android:protectionLevel="dangerous" />
<!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 5890bed11b07..25615d2dc304 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -43,6 +43,7 @@
android:fontFamily="@string/config_headlineFontFamilyMedium"
android:textColor="@color/resolver_empty_state_text"
android:textSize="14sp"
+ android:gravity="center_horizontal"
android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/resolver_empty_state_subtitle"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 4065a6c83f8f..a8d1605c6fdd 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2527,6 +2527,21 @@
<flag name="noExcludeDescendants" value="0x8" />
</attr>
+ <!-- Hints the Android System whether the this View should be considered a scroll capture target. -->
+ <attr name="scrollCaptureHint">
+ <!-- Let the Android System determine if the view can be a scroll capture target. -->
+ <flag name="auto" value="0" />
+ <!-- Hint the Android System that this view is a likely target. If capable, it will
+ be ranked above other views without this flag. -->
+ <flag name="include" value="0x1" />
+ <!-- Hint the Android System that this view should never be considered a scroll capture
+ target. -->
+ <flag name="exclude" value="0x2" />
+ <!-- Hint the Android System that this view's children should not be examined and should
+ be excluded as a scroll capture target. -->
+ <flag name="excludeDescendants" value="0x4" />
+ </attr>
+
<!-- Boolean that controls whether a view can take focus while in touch mode.
If this is true for a view, that view can gain focus when clicked on, and can keep
focus if another view is clicked on that doesn't have this attribute set to true. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 67d20da03925..fb887c338fc4 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3020,6 +3020,8 @@
<public name="preserveLegacyExternalStorage" />
<public name="mimeGroup" />
<public name="gwpAsanMode" />
+ <!-- @hide -->
+ <public name="scrollCaptureHint" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/xml/default_zen_mode_config.xml b/core/res/res/xml/default_zen_mode_config.xml
index 9110661536e2..873b9ebe4f1a 100644
--- a/core/res/res/xml/default_zen_mode_config.xml
+++ b/core/res/res/xml/default_zen_mode_config.xml
@@ -20,8 +20,8 @@
<!-- Default configuration for zen mode. See android.service.notification.ZenModeConfig. -->
<zen version="9">
<allow alarms="true" media="true" system="false" calls="true" callsFrom="2" messages="false"
- reminders="false" events="false" repeatCallers="true" conversations="true"
- conversationsFrom="2"/>
+ reminders="false" events="false" repeatCallers="true" convos="false"
+ convosFrom="3"/>
<automatic ruleId="EVENTS_DEFAULT_RULE" enabled="false" snoozing="false" name="Event" zen="1"
component="android/com.android.server.notification.EventConditionProvider"
conditionId="condition://android/event?userId=-10000&amp;calendar=&amp;reply=1"/>
diff --git a/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java
new file mode 100644
index 000000000000..daa90c28e0c4
--- /dev/null
+++ b/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Param;
+
+public class ParcelStringBenchmark {
+
+ @Param({"com.example.typical_package_name", "從不喜歡孤單一個 - 蘇永康/吳雨霏"})
+ String mValue;
+
+ private Parcel mParcel;
+
+ @BeforeExperiment
+ protected void setUp() {
+ mParcel = Parcel.obtain();
+ }
+
+ @AfterExperiment
+ protected void tearDown() {
+ mParcel.recycle();
+ mParcel = null;
+ }
+
+ public void timeWriteString8(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mParcel.setDataPosition(0);
+ mParcel.writeString8(mValue);
+ }
+ }
+
+ public void timeReadString8(int reps) {
+ mParcel.writeString8(mValue);
+
+ for (int i = 0; i < reps; i++) {
+ mParcel.setDataPosition(0);
+ mParcel.readString8();
+ }
+ }
+
+ public void timeWriteString16(int reps) {
+ for (int i = 0; i < reps; i++) {
+ mParcel.setDataPosition(0);
+ mParcel.writeString16(mValue);
+ }
+ }
+
+ public void timeReadString16(int reps) {
+ mParcel.writeString16(mValue);
+
+ for (int i = 0; i < reps; i++) {
+ mParcel.setDataPosition(0);
+ mParcel.readString16();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 46873b9eb70b..dcb3e2f23da8 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -87,4 +87,27 @@ public class ParcelTest {
p.recycle();
}
+
+ /**
+ * Verify that writing/reading UTF-8 and UTF-16 strings works well.
+ */
+ @Test
+ public void testStrings() {
+ final String[] strings = {
+ null, "", "abc\0def", "com.example.typical_package_name",
+ "從不喜歡孤單一個 - 蘇永康/吳雨霏", "example"
+ };
+
+ final Parcel p = Parcel.obtain();
+ for (String string : strings) {
+ p.writeString8(string);
+ p.writeString16(string);
+ }
+
+ p.setDataPosition(0);
+ for (String string : strings) {
+ assertEquals(string, p.readString8());
+ assertEquals(string, p.readString16());
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/os/VibrationEffectTest.java b/core/tests/coretests/src/android/os/VibrationEffectTest.java
index ea778fd6710a..a354f1d8109f 100644
--- a/core/tests/coretests/src/android/os/VibrationEffectTest.java
+++ b/core/tests/coretests/src/android/os/VibrationEffectTest.java
@@ -22,9 +22,12 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.content.ContentInterface;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
@@ -37,6 +40,8 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class VibrationEffectTest {
+ private static final float SCALE_TOLERANCE = 1e-2f;
+
private static final String RINGTONE_URI_1 = "content://test/system/ringtone_1";
private static final String RINGTONE_URI_2 = "content://test/system/ringtone_2";
private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
@@ -54,6 +59,12 @@ public class VibrationEffectTest {
VibrationEffect.createOneShot(TEST_TIMING, VibrationEffect.DEFAULT_AMPLITUDE);
private static final VibrationEffect TEST_WAVEFORM =
VibrationEffect.createWaveform(TEST_TIMINGS, TEST_AMPLITUDES, -1);
+ private static final VibrationEffect TEST_COMPOSED =
+ VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 10)
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0, 100)
+ .compose();
@Test
public void getRingtones_noPrebakedRingtones() {
@@ -123,8 +134,14 @@ public class VibrationEffectTest {
@Test
public void testScaleWaveform() {
- VibrationEffect.Waveform scaled =
- ((VibrationEffect.Waveform) TEST_WAVEFORM).scale(1.1f, 200);
+ VibrationEffect.Waveform initial = (VibrationEffect.Waveform) TEST_WAVEFORM;
+
+ VibrationEffect.Waveform copied = initial.scale(1f, 255);
+ assertEquals(255, copied.getAmplitudes()[0]);
+ assertEquals(0, copied.getAmplitudes()[1]);
+ assertEquals(-1, copied.getAmplitudes()[2]);
+
+ VibrationEffect.Waveform scaled = initial.scale(1.1f, 200);
assertEquals(200, scaled.getAmplitudes()[0]);
assertEquals(0, scaled.getAmplitudes()[1]);
}
@@ -156,6 +173,66 @@ public class VibrationEffectTest {
}
}
+ @Test
+ public void testScaleComposed() {
+ VibrationEffect.Composed initial = (VibrationEffect.Composed) TEST_COMPOSED;
+
+ VibrationEffect.Composed copied = initial.scale(1, 255);
+ assertEquals(1f, copied.getPrimitiveEffects().get(0).scale);
+ assertEquals(0.5f, copied.getPrimitiveEffects().get(1).scale);
+ assertEquals(0f, copied.getPrimitiveEffects().get(2).scale);
+
+ VibrationEffect.Composed halved = initial.scale(1, 128);
+ assertEquals(0.5f, halved.getPrimitiveEffects().get(0).scale, SCALE_TOLERANCE);
+ assertEquals(0.25f, halved.getPrimitiveEffects().get(1).scale, SCALE_TOLERANCE);
+ assertEquals(0f, halved.getPrimitiveEffects().get(2).scale);
+
+ VibrationEffect.Composed scaledUp = initial.scale(0.5f, 255);
+ assertEquals(1f, scaledUp.getPrimitiveEffects().get(0).scale); // does not scale up from 1
+ assertTrue(0.5f < scaledUp.getPrimitiveEffects().get(1).scale);
+ assertEquals(0f, scaledUp.getPrimitiveEffects().get(2).scale);
+
+ VibrationEffect.Composed restored = scaledUp.scale(2, 255);
+ assertEquals(1f, restored.getPrimitiveEffects().get(0).scale, SCALE_TOLERANCE);
+ assertEquals(0.5f, restored.getPrimitiveEffects().get(1).scale, SCALE_TOLERANCE);
+ assertEquals(0f, restored.getPrimitiveEffects().get(2).scale);
+
+ VibrationEffect.Composed scaledDown = initial.scale(2, 255);
+ assertEquals(1f, scaledDown.getPrimitiveEffects().get(0).scale, SCALE_TOLERANCE);
+ assertTrue(0.5f > scaledDown.getPrimitiveEffects().get(1).scale);
+ assertEquals(0f, scaledDown.getPrimitiveEffects().get(2).scale, SCALE_TOLERANCE);
+
+ VibrationEffect.Composed changeMax = initial.scale(1f, 51);
+ assertEquals(0.2f, changeMax.getPrimitiveEffects().get(0).scale, SCALE_TOLERANCE);
+ assertEquals(0.1f, changeMax.getPrimitiveEffects().get(1).scale, SCALE_TOLERANCE);
+ assertEquals(0f, changeMax.getPrimitiveEffects().get(2).scale);
+ }
+
+ @Test
+ public void testScaleComposedFailsWhenMaxAmplitudeAboveThreshold() {
+ try {
+ ((VibrationEffect.Composed) TEST_COMPOSED).scale(1.1f, 1000);
+ fail("Max amplitude above threshold, should throw IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testScaleAppliesSameAdjustmentsOnAllEffects() {
+ VibrationEffect.OneShot oneShot = new VibrationEffect.OneShot(TEST_TIMING, TEST_AMPLITUDE);
+ VibrationEffect.Waveform waveform = new VibrationEffect.Waveform(
+ new long[] { TEST_TIMING }, new int[]{ TEST_AMPLITUDE }, -1);
+ VibrationEffect.Composed composed =
+ (VibrationEffect.Composed) VibrationEffect.startComposition()
+ .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, TEST_AMPLITUDE / 255f)
+ .compose();
+
+ assertEquals(oneShot.scale(2f, 128).getAmplitude(),
+ waveform.scale(2f, 128).getAmplitudes()[0]);
+ assertEquals(oneShot.scale(2f, 128).getAmplitude() / 255f, // convert amplitude to scale
+ composed.scale(2f, 128).getPrimitiveEffects().get(0).scale,
+ SCALE_TOLERANCE);
+ }
private Resources mockRingtoneResources() {
return mockRingtoneResources(new String[] {
@@ -172,9 +249,22 @@ public class VibrationEffectTest {
return mockResources;
}
- private Context mockContext(Resources r) {
- Context ctx = mock(Context.class);
- when(ctx.getResources()).thenReturn(r);
- return ctx;
+ private Context mockContext(Resources resources) {
+ Context context = mock(Context.class);
+ ContentInterface contentInterface = mock(ContentInterface.class);
+ ContentResolver contentResolver = ContentResolver.wrap(contentInterface);
+
+ try {
+ // ContentResolver#uncanonicalize is final, so we need to mock the ContentInterface it
+ // delegates the call to for the tests that require matching with the mocked URIs.
+ when(contentInterface.uncanonicalize(any())).then(
+ invocation -> invocation.getArgument(0));
+ when(context.getContentResolver()).thenReturn(contentResolver);
+ when(context.getResources()).thenReturn(resources);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+
+ return context;
}
}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureClientTest.java b/core/tests/coretests/src/android/view/ScrollCaptureClientTest.java
new file mode 100644
index 000000000000..e6ac2d6c43da
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollCaptureClientTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+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.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
+
+/**
+ * Tests of {@link ScrollCaptureClient}.
+ */
+@SuppressWarnings("UnnecessaryLocalVariable")
+@RunWith(AndroidJUnit4.class)
+public class ScrollCaptureClientTest {
+
+ private final Point mPositionInWindow = new Point(1, 2);
+ private final Rect mLocalVisibleRect = new Rect(2, 3, 4, 5);
+ private final Rect mScrollBounds = new Rect(3, 4, 5, 6);
+
+ private Handler mHandler;
+ private ScrollCaptureTarget mTarget1;
+
+ @Mock
+ private Surface mSurface;
+ @Mock
+ private IScrollCaptureController mClientCallbacks;
+ @Mock
+ private View mMockView1;
+ @Mock
+ private ScrollCaptureCallback mCallback1;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mHandler = new Handler(getTargetContext().getMainLooper());
+
+ when(mMockView1.getHandler()).thenReturn(mHandler);
+ when(mMockView1.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE);
+
+ mTarget1 = new ScrollCaptureTarget(
+ mMockView1, mLocalVisibleRect, mPositionInWindow, mCallback1);
+ mTarget1.setScrollBounds(mScrollBounds);
+ }
+
+ /** Test the DelayedAction timeout helper class works as expected. */
+ @Test
+ public void testDelayedAction() {
+ Runnable action = Mockito.mock(Runnable.class);
+ ScrollCaptureClient.DelayedAction delayed =
+ new ScrollCaptureClient.DelayedAction(mHandler, 100, action);
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ex) {
+ /* ignore */
+ }
+ getInstrumentation().waitForIdleSync();
+ assertFalse(delayed.cancel());
+ assertFalse(delayed.timeoutNow());
+ verify(action, times(1)).run();
+ }
+
+ /** Test the DelayedAction cancel() */
+ @Test
+ public void testDelayedAction_cancel() {
+ Runnable action = Mockito.mock(Runnable.class);
+ ScrollCaptureClient.DelayedAction delayed =
+ new ScrollCaptureClient.DelayedAction(mHandler, 100, action);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ /* ignore */
+ }
+ assertTrue(delayed.cancel());
+ assertFalse(delayed.timeoutNow());
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException ex) {
+ /* ignore */
+ }
+ getInstrumentation().waitForIdleSync();
+ verify(action, never()).run();
+ }
+
+ /** Test the DelayedAction timeoutNow() - for testing only */
+ @Test
+ public void testDelayedAction_timeoutNow() {
+ Runnable action = Mockito.mock(Runnable.class);
+ ScrollCaptureClient.DelayedAction delayed =
+ new ScrollCaptureClient.DelayedAction(mHandler, 100, action);
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException ex) {
+ /* ignore */
+ }
+ assertTrue(delayed.timeoutNow());
+ assertFalse(delayed.cancel());
+ getInstrumentation().waitForIdleSync();
+ verify(action, times(1)).run();
+ }
+
+ /** Test creating a client with valid info */
+ @Test
+ public void testConstruction() {
+ new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ }
+
+ /** Test creating a client fails if arguments are not valid. */
+ @Test
+ public void testConstruction_requiresScrollBounds() {
+ try {
+ mTarget1.setScrollBounds(null);
+ new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ fail("An exception was expected.");
+ } catch (RuntimeException ex) {
+ // Ignore, expected.
+ }
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private static Answer<Void> runRunnable(int arg) {
+ return invocation -> {
+ Runnable r = invocation.getArgument(arg);
+ r.run();
+ return null;
+ };
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private static Answer<Void> reportBufferSent(int sessionArg, long frameNum, Rect capturedArea) {
+ return invocation -> {
+ ScrollCaptureSession session = invocation.getArgument(sessionArg);
+ session.notifyBufferSent(frameNum, capturedArea);
+ return null;
+ };
+ }
+
+ /** @see ScrollCaptureClient#startCapture(Surface) */
+ @Test
+ public void testStartCapture() throws Exception {
+ final ScrollCaptureClient client = new ScrollCaptureClient(mTarget1, mClientCallbacks);
+
+ // Have the session start accepted immediately
+ doAnswer(runRunnable(1)).when(mCallback1)
+ .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class));
+ client.startCapture(mSurface);
+ getInstrumentation().waitForIdleSync();
+
+ verify(mCallback1, times(1))
+ .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class));
+ verify(mClientCallbacks, times(1)).onCaptureStarted();
+ verifyNoMoreInteractions(mClientCallbacks);
+ }
+
+ @Test
+ public void testStartCaptureTimeout() throws Exception {
+ final ScrollCaptureClient client = new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ client.startCapture(mSurface);
+
+ // Force timeout to fire
+ client.getTimeoutAction().timeoutNow();
+
+ getInstrumentation().waitForIdleSync();
+ verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
+ }
+
+ private void startClient(ScrollCaptureClient client) throws Exception {
+ doAnswer(runRunnable(1)).when(mCallback1)
+ .onScrollCaptureStart(any(ScrollCaptureSession.class), any(Runnable.class));
+ client.startCapture(mSurface);
+ getInstrumentation().waitForIdleSync();
+ reset(mCallback1, mClientCallbacks);
+ }
+
+ /** @see ScrollCaptureClient#requestImage(Rect) */
+ @Test
+ public void testRequestImage() throws Exception {
+ final ScrollCaptureClient client = new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ startClient(client);
+
+ // Stub the callback to complete the request immediately
+ doAnswer(reportBufferSent(/* sessionArg */ 0, /* frameNum */ 1L, new Rect(1, 2, 3, 4)))
+ .when(mCallback1)
+ .onScrollCaptureImageRequest(any(ScrollCaptureSession.class), any(Rect.class));
+
+ // Make the inbound binder call
+ client.requestImage(new Rect(1, 2, 3, 4));
+
+ // Wait for handler thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mCallback1, times(1)).onScrollCaptureImageRequest(
+ any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4)));
+
+ // Wait for binder thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mClientCallbacks, times(1)).onCaptureBufferSent(eq(1L), eq(new Rect(1, 2, 3, 4)));
+
+ verifyNoMoreInteractions(mCallback1, mClientCallbacks);
+ }
+
+ @Test
+ public void testRequestImageTimeout() throws Exception {
+ final ScrollCaptureClient client = new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ startClient(client);
+
+ // Make the inbound binder call
+ client.requestImage(new Rect(1, 2, 3, 4));
+
+ // Wait for handler thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mCallback1, times(1)).onScrollCaptureImageRequest(
+ any(ScrollCaptureSession.class), eq(new Rect(1, 2, 3, 4)));
+
+ // Force timeout to fire
+ client.getTimeoutAction().timeoutNow();
+ getInstrumentation().waitForIdleSync();
+
+ // (callback not stubbed, does nothing)
+ // Timeout triggers request to end capture
+ verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
+ verifyNoMoreInteractions(mCallback1, mClientCallbacks);
+ }
+
+ /** @see ScrollCaptureClient#endCapture() */
+ @Test
+ public void testEndCapture() throws Exception {
+ final ScrollCaptureClient client = new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ startClient(client);
+
+ // Stub the callback to complete the request immediately
+ doAnswer(runRunnable(0))
+ .when(mCallback1)
+ .onScrollCaptureEnd(any(Runnable.class));
+
+ // Make the inbound binder call
+ client.endCapture();
+
+ // Wait for handler thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
+
+ // Wait for binder thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mClientCallbacks, times(1)).onConnectionClosed();
+
+ verifyNoMoreInteractions(mCallback1, mClientCallbacks);
+ }
+
+ @Test
+ public void testEndCaptureTimeout() throws Exception {
+ final ScrollCaptureClient client = new ScrollCaptureClient(mTarget1, mClientCallbacks);
+ startClient(client);
+
+ // Make the inbound binder call
+ client.endCapture();
+
+ // Wait for handler thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mCallback1, times(1)).onScrollCaptureEnd(any(Runnable.class));
+
+ // Force timeout to fire
+ client.getTimeoutAction().timeoutNow();
+
+ // Wait for binder thread dispatch
+ getInstrumentation().waitForIdleSync();
+ verify(mClientCallbacks, times(1)).onConnectionClosed();
+
+ verifyNoMoreInteractions(mCallback1, mClientCallbacks);
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java b/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java
new file mode 100644
index 000000000000..8b21b8ecee89
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ScrollCaptureTargetResolverTest.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.LinkedList;
+import java.util.function.Consumer;
+
+/**
+ * Tests of {@link ScrollCaptureTargetResolver}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ScrollCaptureTargetResolverTest {
+
+ private static final long TEST_TIMEOUT_MS = 2000;
+ private static final long RESOLVER_TIMEOUT_MS = 1000;
+
+ private Handler mHandler;
+ private TargetConsumer mTargetConsumer;
+
+ @Before
+ public void setUp() {
+ mTargetConsumer = new TargetConsumer();
+ mHandler = new Handler(getTargetContext().getMainLooper());
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testEmptyQueue() throws InterruptedException {
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(new LinkedList<>());
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ // Test only
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertNull("Expected null due to empty queue", result);
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testNoValidTargets() throws InterruptedException {
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+
+ // Supplies scrollBounds = null
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
+ callback1.setScrollBounds(null);
+ ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
+ new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // Supplies scrollBounds = empty rect
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
+ callback2.setScrollBounds(new Rect());
+ ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
+ new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
+
+ targetQueue.add(target1);
+ targetQueue.add(target2);
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ // Test only
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertNull("Expected null due to no valid targets", result);
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testSingleTarget() throws InterruptedException {
+ FakeScrollCaptureCallback callback = new FakeScrollCaptureCallback();
+ ScrollCaptureTarget target = createTarget(callback,
+ new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ callback.setScrollBounds(new Rect(2, 2, 18, 18));
+
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+ targetQueue.add(target);
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ // Test only
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertSame("Excepted the same target as a result", target, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(2, 2, 18, 18), result.getScrollBounds());
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testSingleTarget_backgroundThread() throws InterruptedException {
+ BackgroundTestCallback callback1 = new BackgroundTestCallback();
+ ScrollCaptureTarget target1 = createTarget(callback1,
+ new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ callback1.setDelay(100);
+ callback1.setScrollBounds(new Rect(2, 2, 18, 18));
+
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+ targetQueue.add(target1);
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ // Test only
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertSame("Excepted the single target1 as a result", target1, result);
+ assertEquals("Result has wrong scroll bounds",
+ new Rect(2, 2, 18, 18), result.getScrollBounds());
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testPreferNonEmptyBounds() throws InterruptedException {
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
+ callback1.setScrollBounds(new Rect());
+ ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
+ new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
+ callback2.setScrollBounds(new Rect(0, 0, 20, 20));
+ ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
+ new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
+
+ FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback();
+ callback3.setScrollBounds(null);
+ ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50),
+ new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ targetQueue.add(target1);
+ targetQueue.add(target2); // scrollBounds not null or empty()
+ targetQueue.add(target3);
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertEquals("Expected " + target2 + " as a result", target2, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(0, 0, 20, 20), result.getScrollBounds());
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testPreferHintInclude() throws InterruptedException {
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
+ callback1.setScrollBounds(new Rect(0, 0, 20, 20));
+ ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
+ new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
+ callback2.setScrollBounds(new Rect(1, 1, 19, 19));
+ ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
+ new Point(0, 20), View.SCROLL_CAPTURE_HINT_INCLUDE);
+
+ FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback();
+ callback3.setScrollBounds(new Rect(2, 2, 18, 18));
+ ScrollCaptureTarget target3 = createTarget(callback3, new Rect(20, 30, 40, 50),
+ new Point(0, 40), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ targetQueue.add(target1);
+ targetQueue.add(target2); // * INCLUDE > AUTO
+ targetQueue.add(target3);
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertEquals("input = " + targetQueue + " Expected " + target2
+ + " as the result, due to hint=INCLUDE", target2, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(1, 1, 19, 19), result.getScrollBounds());
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testDescendantPreferred() throws InterruptedException {
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+
+ ViewGroup targetView1 = new FakeRootView(getTargetContext(), 0, 0, 60, 60); // 60x60
+ ViewGroup targetView2 = new FakeRootView(getTargetContext(), 20, 30, 40, 50); // 20x20
+ ViewGroup targetView3 = new FakeRootView(getTargetContext(), 5, 5, 15, 15); // 10x10
+
+ targetView1.addView(targetView2);
+ targetView2.addView(targetView3);
+
+ // Create first target with an unrelated parent
+ FakeScrollCaptureCallback callback1 = new FakeScrollCaptureCallback();
+ callback1.setScrollBounds(new Rect(0, 0, 60, 60));
+ ScrollCaptureTarget target1 = createTargetWithView(targetView1, callback1,
+ new Rect(0, 0, 60, 60),
+ new Point(0, 0), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // Create second target associated with a view within parent2
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
+ callback2.setScrollBounds(new Rect(0, 0, 20, 20));
+ ScrollCaptureTarget target2 = createTargetWithView(targetView2, callback2,
+ new Rect(0, 0, 20, 20),
+ new Point(20, 30), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ // Create third target associated with a view within parent3
+ FakeScrollCaptureCallback callback3 = new FakeScrollCaptureCallback();
+ callback3.setScrollBounds(new Rect(0, 0, 15, 15));
+ ScrollCaptureTarget target3 = createTargetWithView(targetView3, callback3,
+ new Rect(0, 0, 15, 15),
+ new Point(25, 35), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ targetQueue.add(target1); // auto, 60x60
+ targetQueue.add(target2); // auto, 20x20
+ targetQueue.add(target3); // auto, 15x15 <- innermost scrollable
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ // Test only
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertSame("Expected target3 as the result, due to relation", target3, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(0, 0, 15, 15), result.getScrollBounds());
+ }
+
+ /**
+ * If a timeout expires, late results are ignored.
+ */
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testTimeout() throws InterruptedException {
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+
+ // callback 1, 10x10, hint=AUTO, responds immediately from bg thread
+ BackgroundTestCallback callback1 = new BackgroundTestCallback();
+ callback1.setScrollBounds(new Rect(5, 5, 15, 15));
+ ScrollCaptureTarget target1 = createTarget(
+ callback1, new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ targetQueue.add(target1);
+
+ // callback 2, 20x20, hint=AUTO, responds after 5s from bg thread
+ BackgroundTestCallback callback2 = new BackgroundTestCallback();
+ callback2.setScrollBounds(new Rect(0, 0, 20, 20));
+ callback2.setDelay(5000);
+ ScrollCaptureTarget target2 = createTarget(
+ callback2, new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_AUTO);
+ targetQueue.add(target2);
+
+ // callback 3, 20x20, hint=INCLUDE, responds after 10s from bg thread
+ BackgroundTestCallback callback3 = new BackgroundTestCallback();
+ callback3.setScrollBounds(new Rect(0, 0, 20, 20));
+ callback3.setDelay(10000);
+ ScrollCaptureTarget target3 = createTarget(
+ callback3, new Rect(20, 30, 40, 50), new Point(10, 10),
+ View.SCROLL_CAPTURE_HINT_INCLUDE);
+ targetQueue.add(target3);
+
+ // callback 1 will be received
+ // callback 2 & 3 will be ignored due to timeout
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertSame("Expected target1 as the result, due to timeouts of others", target1, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(5, 5, 15, 15), result.getScrollBounds());
+ assertEquals("callback1 should have been called",
+ 1, callback1.getOnScrollCaptureSearchCount());
+ assertEquals("callback2 should have been called",
+ 1, callback2.getOnScrollCaptureSearchCount());
+ assertEquals("callback3 should have been called",
+ 1, callback3.getOnScrollCaptureSearchCount());
+ }
+
+ @Test(timeout = TEST_TIMEOUT_MS)
+ public void testWithCallbackMultipleReplies() throws InterruptedException {
+ // Calls response methods 3 times each
+ RepeatingCaptureCallback callback1 = new RepeatingCaptureCallback(3);
+ callback1.setScrollBounds(new Rect(2, 2, 18, 18));
+ ScrollCaptureTarget target1 = createTarget(callback1, new Rect(20, 30, 40, 50),
+ new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ FakeScrollCaptureCallback callback2 = new FakeScrollCaptureCallback();
+ callback2.setScrollBounds(new Rect(0, 0, 20, 20));
+ ScrollCaptureTarget target2 = createTarget(callback2, new Rect(20, 30, 40, 50),
+ new Point(10, 10), View.SCROLL_CAPTURE_HINT_AUTO);
+
+ LinkedList<ScrollCaptureTarget> targetQueue = new LinkedList<>();
+ targetQueue.add(target1);
+ targetQueue.add(target2);
+
+ ScrollCaptureTargetResolver resolver = new ScrollCaptureTargetResolver(targetQueue);
+ resolver.start(mHandler, RESOLVER_TIMEOUT_MS, mTargetConsumer);
+
+ resolver.waitForResult();
+
+ ScrollCaptureTarget result = mTargetConsumer.getLastValue();
+ assertSame("Expected target2 as the result, due to hint=INCLUDE", target2, result);
+ assertEquals("result has wrong scroll bounds",
+ new Rect(0, 0, 20, 20), result.getScrollBounds());
+ assertEquals("callback1 should have been called once",
+ 1, callback1.getOnScrollCaptureSearchCount());
+ assertEquals("callback2 should have been called once",
+ 1, callback2.getOnScrollCaptureSearchCount());
+ }
+
+ private static class TargetConsumer implements Consumer<ScrollCaptureTarget> {
+ volatile ScrollCaptureTarget mResult;
+ int mAcceptCount;
+
+ ScrollCaptureTarget getLastValue() {
+ return mResult;
+ }
+
+ int acceptCount() {
+ return mAcceptCount;
+ }
+
+ @Override
+ public void accept(@Nullable ScrollCaptureTarget t) {
+ mAcceptCount++;
+ mResult = t;
+ }
+ }
+
+ private void setupTargetView(View view, Rect localVisibleRect, int scrollCaptureHint) {
+ view.setScrollCaptureHint(scrollCaptureHint);
+ view.onVisibilityAggregated(true);
+ // Treat any offset as padding, outset localVisibleRect on all sides and use this as
+ // child bounds
+ Rect bounds = new Rect(localVisibleRect);
+ bounds.inset(-bounds.left, -bounds.top, bounds.left, bounds.top);
+ view.layout(bounds.left, bounds.top, bounds.right, bounds.bottom);
+ view.onVisibilityAggregated(true);
+ }
+
+ private ScrollCaptureTarget createTarget(ScrollCaptureCallback callback, Rect localVisibleRect,
+ Point positionInWindow, int scrollCaptureHint) {
+ View mockView = new View(getTargetContext());
+ return createTargetWithView(mockView, callback, localVisibleRect, positionInWindow,
+ scrollCaptureHint);
+ }
+
+ private ScrollCaptureTarget createTargetWithView(View view, ScrollCaptureCallback callback,
+ Rect localVisibleRect, Point positionInWindow, int scrollCaptureHint) {
+ setupTargetView(view, localVisibleRect, scrollCaptureHint);
+ return new ScrollCaptureTarget(view, localVisibleRect, positionInWindow, callback);
+ }
+
+
+ static class FakeRootView extends ViewGroup implements ViewParent {
+ FakeRootView(Context context, int l, int t, int r, int b) {
+ super(context);
+ layout(l, t, r, b);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ }
+ }
+
+ static class FakeScrollCaptureCallback implements ScrollCaptureCallback {
+ private Rect mScrollBounds;
+ private long mDelayMillis;
+ private int mOnScrollCaptureSearchCount;
+
+ public int getOnScrollCaptureSearchCount() {
+ return mOnScrollCaptureSearchCount;
+ }
+
+ @Override
+ public void onScrollCaptureSearch(Consumer<Rect> onReady) {
+ mOnScrollCaptureSearchCount++;
+ run(() -> {
+ Rect b = getScrollBounds();
+ onReady.accept(b);
+ });
+ }
+
+ @Override
+ public void onScrollCaptureStart(ScrollCaptureSession session, Runnable onReady) {
+ run(onReady);
+ }
+
+ @Override
+ public void onScrollCaptureImageRequest(ScrollCaptureSession session, Rect captureArea) {
+ run(() -> session.notifyBufferSent(0, captureArea));
+ }
+
+ @Override
+ public void onScrollCaptureEnd(Runnable onReady) {
+ run(onReady);
+ }
+
+ public void setScrollBounds(@Nullable Rect scrollBounds) {
+ mScrollBounds = scrollBounds;
+ }
+
+ public void setDelay(long delayMillis) {
+ mDelayMillis = delayMillis;
+ }
+
+ protected Rect getScrollBounds() {
+ return mScrollBounds;
+ }
+
+ protected void run(Runnable r) {
+ delay();
+ r.run();
+ }
+
+ protected void delay() {
+ if (mDelayMillis > 0) {
+ try {
+ Thread.sleep(mDelayMillis);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ static class RepeatingCaptureCallback extends FakeScrollCaptureCallback {
+ private int mRepeatCount;
+
+ RepeatingCaptureCallback(int repeatCount) {
+ mRepeatCount = repeatCount;
+ }
+
+ protected void run(Runnable r) {
+ delay();
+ for (int i = 0; i < mRepeatCount; i++) {
+ r.run();
+ }
+ }
+ }
+
+ /** Response to async calls on an arbitrary background thread */
+ static class BackgroundTestCallback extends FakeScrollCaptureCallback {
+ static int sCount = 0;
+ private void runOnBackgroundThread(Runnable r) {
+ final Runnable target = () -> {
+ delay();
+ r.run();
+ };
+ Thread t = new Thread(target);
+ synchronized (BackgroundTestCallback.this) {
+ sCount++;
+ }
+ t.setName("Background-Thread-" + sCount);
+ t.start();
+ }
+
+ @Override
+ protected void run(Runnable r) {
+ runOnBackgroundThread(r);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
new file mode 100644
index 000000000000..3af0533e763c
--- /dev/null
+++ b/core/tests/coretests/src/android/view/ViewGroupScrollCaptureTest.java
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.AssertJUnit.assertSame;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Exercises Scroll Capture search in {@link ViewGroup}.
+ */
+@Presubmit
+@SmallTest
+@FlakyTest(detail = "promote once confirmed flake-free")
+@RunWith(MockitoJUnitRunner.class)
+public class ViewGroupScrollCaptureTest {
+
+ @Mock
+ ScrollCaptureCallback mMockCallback;
+ @Mock
+ ScrollCaptureCallback mMockCallback2;
+
+ /** Make sure the hint flags are saved and loaded correctly. */
+ @Test
+ public void testSetScrollCaptureHint() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ final MockViewGroup viewGroup = new MockViewGroup(context);
+
+ assertNotNull(viewGroup);
+ assertEquals("Default scroll capture hint flags should be [SCROLL_CAPTURE_HINT_AUTO]",
+ ViewGroup.SCROLL_CAPTURE_HINT_AUTO, viewGroup.getScrollCaptureHint());
+
+ viewGroup.setScrollCaptureHint(View.SCROLL_CAPTURE_HINT_INCLUDE);
+ assertEquals("The scroll capture hint was not stored correctly.",
+ ViewGroup.SCROLL_CAPTURE_HINT_INCLUDE, viewGroup.getScrollCaptureHint());
+
+ viewGroup.setScrollCaptureHint(ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE);
+ assertEquals("The scroll capture hint was not stored correctly.",
+ ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE, viewGroup.getScrollCaptureHint());
+
+ viewGroup.setScrollCaptureHint(ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS);
+ assertEquals("The scroll capture hint was not stored correctly.",
+ ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS,
+ viewGroup.getScrollCaptureHint());
+
+ viewGroup.setScrollCaptureHint(ViewGroup.SCROLL_CAPTURE_HINT_INCLUDE
+ | ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS);
+ assertEquals("The scroll capture hint was not stored correctly.",
+ ViewGroup.SCROLL_CAPTURE_HINT_INCLUDE
+ | ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS,
+ viewGroup.getScrollCaptureHint());
+
+ viewGroup.setScrollCaptureHint(ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE
+ | ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS);
+ assertEquals("The scroll capture hint was not stored correctly.",
+ ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE
+ | ViewGroup.SCROLL_CAPTURE_HINT_EXCLUDE_DESCENDANTS,
+ viewGroup.getScrollCaptureHint());
+ }
+
+ /**
+ * Ensure a ViewGroup with 'scrollCaptureHint=auto', but no ScrollCaptureCallback set dispatches
+ * correctly. Verifies that the framework helper is called. Verifies a that non-null callback
+ * return results in an expected target in the results.
+ */
+ @MediumTest
+ @Test
+ public void testDispatchScrollCaptureSearch_noCallback_hintAuto() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+
+ // When system internal scroll capture is requested, this callback is returned.
+ viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback);
+
+ Rect localVisibleRect = new Rect(0, 0, 200, 200);
+ Point windowOffset = new Point();
+ LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Dispatch
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+
+ // Verify the system checked for fallback support
+ viewGroup.assertDispatchScrollCaptureCount(1);
+ viewGroup.assertLastDispatchScrollCaptureArgs(localVisibleRect, windowOffset);
+
+ // Verify the target is as expected.
+ assertEquals(1, targetList.size());
+ ScrollCaptureTarget target = targetList.get(0);
+ assertSame("Target has the wrong callback", mMockCallback, target.getCallback());
+ assertSame("Target has the wrong View", viewGroup, target.getContainingView());
+ assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO,
+ target.getContainingView().getScrollCaptureHint());
+ }
+
+ /**
+ * Ensure a ViewGroup with 'scrollCaptureHint=exclude' is ignored. The Framework helper is
+ * stubbed to return a callback. Verifies that the framework helper is not called (because of
+ * exclude), and no scroll capture target is added to the results.
+ */
+ @MediumTest
+ @Test
+ public void testDispatchScrollCaptureSearch_noCallback_hintExclude() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ final MockViewGroup viewGroup =
+ new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE);
+
+ // When system internal scroll capture is requested, this callback is returned.
+ viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback);
+
+ Rect localVisibleRect = new Rect(0, 0, 200, 200);
+ Point windowOffset = new Point();
+ LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Dispatch
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+
+ // Verify the results.
+ assertEquals("Target list size should be zero.", 0, targetList.size());
+ }
+
+ /**
+ * Ensure that a ViewGroup with 'scrollCaptureHint=auto', and a scroll capture callback set
+ * dispatches as expected. Also verifies that the system fallback support is not called, and the
+ * the returned target is constructed correctly.
+ */
+ @MediumTest
+ @Test
+ public void testDispatchScrollCaptureSearch_withCallback_hintAuto() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+
+ // With an already provided scroll capture callback
+ viewGroup.setScrollCaptureCallback(mMockCallback);
+
+ // When system internal scroll capture is requested, this callback is returned.
+ viewGroup.setScrollCaptureCallbackInternalForTest(mMockCallback);
+
+ Rect localVisibleRect = new Rect(0, 0, 200, 200);
+ Point windowOffset = new Point();
+ LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Dispatch to the ViewGroup
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+
+ // Confirm that framework support was not requested,
+ // because this view already had a callback set.
+ viewGroup.assertCreateScrollCaptureCallbackInternalCount(0);
+
+ // Verify the target is as expected.
+ assertEquals(1, targetList.size());
+ ScrollCaptureTarget target = targetList.get(0);
+ assertSame("Target has the wrong callback", mMockCallback, target.getCallback());
+ assertSame("Target has the wrong View", viewGroup, target.getContainingView());
+ assertEquals("Target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO,
+ target.getContainingView().getScrollCaptureHint());
+ }
+
+ /**
+ * Ensure a ViewGroup with a callback set, but 'scrollCaptureHint=exclude' is ignored. The
+ * exclude flag takes precedence. Verifies that the framework helper is not called (because of
+ * exclude, and a callback being set), and no scroll capture target is added to the results.
+ */
+ @MediumTest
+ @Test
+ public void testDispatchScrollCaptureSearch_withCallback_hintExclude() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ MockViewGroup viewGroup =
+ new MockViewGroup(context, 0, 0, 200, 200, View.SCROLL_CAPTURE_HINT_EXCLUDE);
+ // With an already provided scroll capture callback
+ viewGroup.setScrollCaptureCallback(mMockCallback);
+
+ Rect localVisibleRect = new Rect(0, 0, 200, 200);
+ Point windowOffset = new Point();
+ LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Dispatch to the ViewGroup itself
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+
+ // Confirm that framework support was not requested, because this view is excluded.
+ // (And because this view has a callback set.)
+ viewGroup.assertCreateScrollCaptureCallbackInternalCount(0);
+
+ // Has callback, but hint=excluded, so excluded.
+ assertTrue(targetList.isEmpty());
+ }
+
+ /**
+ * Test scroll capture search dispatch to child views.
+ * <p>
+ * Verifies computation of child visible bounds.
+ * TODO: with scrollX / scrollY, split up into discrete tests
+ */
+ @MediumTest
+ @Test
+ public void testDispatchScrollCaptureSearch_toChildren() throws Exception {
+ final Context context = getInstrumentation().getContext();
+ final MockViewGroup viewGroup = new MockViewGroup(context, 0, 0, 200, 200);
+
+ Rect localVisibleRect = new Rect(25, 50, 175, 150);
+ Point windowOffset = new Point(0, 0);
+
+ // visible area
+ // |<- l=25, |
+ // | r=175 ->|
+ // +--------------------------+
+ // | view1 (0, 0, 200, 25) |
+ // +---------------+----------+
+ // | | |
+ // | view2 | view4 | --+
+ // | (0, 25, | (inv) | | visible area
+ // | 150, 100)| | |
+ // +---------------+----------+ | t=50, b=150
+ // | view3 | view5 | |
+ // | (0, 100 |(150, 100 | --+
+ // | 200, 200) | 200, 200)|
+ // | | |
+ // | | |
+ // +---------------+----------+ (200,200)
+
+ // View 1 is clipped and not visible.
+ final MockView view1 = new MockView(context, 0, 0, 200, 25);
+ viewGroup.addView(view1);
+
+ // View 2 is partially visible.
+ final MockView view2 = new MockView(context, 0, 25, 150, 100);
+ viewGroup.addView(view2);
+
+ // View 3 is partially visible.
+ // Pretend View3 can scroll by having framework provide fallback support
+ final MockView view3 = new MockView(context, 0, 100, 200, 200);
+ // When system internal scroll capture is requested for this view, return this callback.
+ view3.setScrollCaptureCallbackInternalForTest(mMockCallback);
+ viewGroup.addView(view3);
+
+ // View 4 is invisible and should be ignored.
+ final MockView view4 = new MockView(context, 150, 25, 200, 100, View.INVISIBLE);
+ viewGroup.addView(view4);
+
+ // View 4 is invisible and should be ignored.
+ final MockView view5 = new MockView(context, 150, 100, 200, 200);
+ // When system internal scroll capture is requested for this view, return this callback.
+ view5.setScrollCaptureCallback(mMockCallback2);
+ view5.setScrollCaptureHint(View.SCROLL_CAPTURE_HINT_INCLUDE);
+ viewGroup.addView(view5);
+
+ // Where targets are added
+ final LinkedList<ScrollCaptureTarget> targetList = new LinkedList<>();
+
+ // Dispatch to the ViewGroup
+ viewGroup.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targetList);
+
+ // View 1 is entirely clipped by the parent and not visible, dispatch
+ // skips this view entirely.
+ view1.assertDispatchScrollCaptureSearchCount(0);
+ view1.assertCreateScrollCaptureCallbackInternalCount(0);
+
+ // View 2, verify the computed localVisibleRect and windowOffset are correctly transformed
+ // to the child coordinate space
+ view2.assertDispatchScrollCaptureSearchCount(1);
+ view2.assertDispatchScrollCaptureSearchLastArgs(
+ new Rect(25, 25, 150, 75), new Point(0, 25));
+ // No callback set, so the framework is asked for support
+ view2.assertCreateScrollCaptureCallbackInternalCount(1);
+
+ // View 3, verify the computed localVisibleRect and windowOffset are correctly transformed
+ // to the child coordinate space
+ view3.assertDispatchScrollCaptureSearchCount(1);
+ view3.assertDispatchScrollCaptureSearchLastArgs(
+ new Rect(25, 0, 175, 50), new Point(0, 100));
+ // No callback set, so the framework is asked for support
+ view3.assertCreateScrollCaptureCallbackInternalCount(1);
+
+ // view4 is invisible, so it should be skipped entirely.
+ view4.assertDispatchScrollCaptureSearchCount(0);
+ view4.assertCreateScrollCaptureCallbackInternalCount(0);
+
+ // view5 is partially visible
+ view5.assertDispatchScrollCaptureSearchCount(1);
+ view5.assertDispatchScrollCaptureSearchLastArgs(
+ new Rect(0, 0, 25, 50), new Point(150, 100));
+ // view5 has a callback set on it, so internal framework support should not be consulted.
+ view5.assertCreateScrollCaptureCallbackInternalCount(0);
+
+ // 2 views should have been returned, view3 & view5
+ assertEquals(2, targetList.size());
+
+ ScrollCaptureTarget target = targetList.get(0);
+ assertSame("First target has the wrong View", view3, target.getContainingView());
+ assertSame("First target has the wrong callback", mMockCallback, target.getCallback());
+ assertEquals("First target hint is incorrect", View.SCROLL_CAPTURE_HINT_AUTO,
+ target.getContainingView().getScrollCaptureHint());
+
+ target = targetList.get(1);
+ assertSame("Second target has the wrong View", view5, target.getContainingView());
+ assertSame("Second target has the wrong callback", mMockCallback2, target.getCallback());
+ assertEquals("Second target hint is incorrect", View.SCROLL_CAPTURE_HINT_INCLUDE,
+ target.getContainingView().getScrollCaptureHint());
+ }
+
+ public static final class MockView extends View {
+ private ScrollCaptureCallback mInternalCallback;
+
+ private int mDispatchScrollCaptureSearchNumCalls;
+ private Rect mDispatchScrollCaptureSearchLastLocalVisibleRect;
+ private Point mDispatchScrollCaptureSearchLastWindowOffset;
+ private int mCreateScrollCaptureCallbackInternalCount;
+
+ MockView(Context context) {
+ this(context, /* left */ 0, /* top */0, /* right */ 0, /* bottom */0);
+ }
+
+ MockView(Context context, int left, int top, int right, int bottom) {
+ this(context, left, top, right, bottom, View.VISIBLE);
+ }
+
+ MockView(Context context, int left, int top, int right, int bottom, int visibility) {
+ super(context);
+ setVisibility(visibility);
+ setFrame(left, top, right, bottom);
+ }
+
+ public void setScrollCaptureCallbackInternalForTest(ScrollCaptureCallback internal) {
+ mInternalCallback = internal;
+ }
+
+ void assertDispatchScrollCaptureSearchCount(int count) {
+ assertEquals("Unexpected number of calls to dispatchScrollCaptureSearch",
+ count, mDispatchScrollCaptureSearchNumCalls);
+ }
+
+ void assertDispatchScrollCaptureSearchLastArgs(Rect localVisibleRect, Point windowOffset) {
+ assertEquals("arg localVisibleRect was incorrect.",
+ localVisibleRect, mDispatchScrollCaptureSearchLastLocalVisibleRect);
+ assertEquals("arg windowOffset was incorrect.",
+ windowOffset, mDispatchScrollCaptureSearchLastWindowOffset);
+ }
+
+ void assertCreateScrollCaptureCallbackInternalCount(int count) {
+ assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal",
+ count, mCreateScrollCaptureCallbackInternalCount);
+ }
+
+ void reset() {
+ mDispatchScrollCaptureSearchNumCalls = 0;
+ mDispatchScrollCaptureSearchLastWindowOffset = null;
+ mDispatchScrollCaptureSearchLastLocalVisibleRect = null;
+ mCreateScrollCaptureCallbackInternalCount = 0;
+
+ }
+
+ @Override
+ public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset,
+ Queue<ScrollCaptureTarget> targets) {
+ mDispatchScrollCaptureSearchNumCalls++;
+ mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect);
+ mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset);
+ super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+ }
+
+ @Override
+ @Nullable
+ public ScrollCaptureCallback createScrollCaptureCallbackInternal(Rect localVisibleRect,
+ Point offsetInWindow) {
+ mCreateScrollCaptureCallbackInternalCount++;
+ return mInternalCallback;
+ }
+ }
+
+ public static final class MockViewGroup extends ViewGroup {
+ private ScrollCaptureCallback mInternalCallback;
+ private int mDispatchScrollCaptureSearchNumCalls;
+ private Rect mDispatchScrollCaptureSearchLastLocalVisibleRect;
+ private Point mDispatchScrollCaptureSearchLastWindowOffset;
+ private int mCreateScrollCaptureCallbackInternalCount;
+
+
+ MockViewGroup(Context context) {
+ this(context, /* left */ 0, /* top */0, /* right */ 0, /* bottom */0);
+ }
+
+ MockViewGroup(Context context, int left, int top, int right, int bottom) {
+ this(context, left, top, right, bottom, View.SCROLL_CAPTURE_HINT_AUTO);
+ }
+
+ MockViewGroup(Context context, int left, int top, int right, int bottom,
+ int scrollCaptureHint) {
+ super(context);
+ setScrollCaptureHint(scrollCaptureHint);
+ setFrame(left, top, right, bottom);
+ }
+
+ public void setScrollCaptureCallbackInternalForTest(ScrollCaptureCallback internal) {
+ mInternalCallback = internal;
+ }
+
+ void assertDispatchScrollCaptureSearchCount(int count) {
+ assertEquals("Unexpected number of calls to dispatchScrollCaptureSearch",
+ count, mDispatchScrollCaptureSearchNumCalls);
+ }
+
+ @Override
+ @Nullable
+ public ScrollCaptureCallback createScrollCaptureCallbackInternal(Rect localVisibleRect,
+ Point offsetInWindow) {
+ mCreateScrollCaptureCallbackInternalCount++;
+ return mInternalCallback;
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ // We don't layout this view.
+ }
+
+ void assertDispatchScrollCaptureCount(int count) {
+ assertEquals(count, mDispatchScrollCaptureSearchNumCalls);
+ }
+
+ void assertLastDispatchScrollCaptureArgs(Rect localVisibleRect, Point windowOffset) {
+ assertEquals("arg localVisibleRect to dispatchScrollCaptureCallback was incorrect.",
+ localVisibleRect, mDispatchScrollCaptureSearchLastLocalVisibleRect);
+ assertEquals("arg windowOffset to dispatchScrollCaptureCallback was incorrect.",
+ windowOffset, mDispatchScrollCaptureSearchLastWindowOffset);
+ }
+ void assertCreateScrollCaptureCallbackInternalCount(int count) {
+ assertEquals("Unexpected number of calls to createScrollCaptureCallackInternal",
+ count, mCreateScrollCaptureCallbackInternalCount);
+ }
+
+ void reset() {
+ mDispatchScrollCaptureSearchNumCalls = 0;
+ mDispatchScrollCaptureSearchLastWindowOffset = null;
+ mDispatchScrollCaptureSearchLastLocalVisibleRect = null;
+ mCreateScrollCaptureCallbackInternalCount = 0;
+ }
+
+ @Override
+ public void dispatchScrollCaptureSearch(Rect localVisibleRect, Point windowOffset,
+ Queue<ScrollCaptureTarget> targets) {
+ mDispatchScrollCaptureSearchNumCalls++;
+ mDispatchScrollCaptureSearchLastLocalVisibleRect = new Rect(localVisibleRect);
+ mDispatchScrollCaptureSearchLastWindowOffset = new Point(windowOffset);
+ super.dispatchScrollCaptureSearch(localVisibleRect, windowOffset, targets);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 769c57835662..43590bae6770 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class IntentForwarderActivityTest {
@@ -633,6 +635,11 @@ public class IntentForwarderActivityTest {
public void onCreate(@Nullable Bundle savedInstanceState) {
getIntent().setComponent(sComponentName);
super.onCreate(savedInstanceState);
+ try {
+ mExecutorService.awaitTermination(/* timeout= */ 30, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
}
@Override
@@ -648,12 +655,6 @@ public class IntentForwarderActivityTest {
}
@Override
- public void startActivity(Intent intent) {
- mStartActivityIntent = intent;
- mUserIdActivityLaunchedIn = getUserId();
- }
-
- @Override
protected MetricsLogger getMetricsLogger() {
return mMetricsLogger;
}
@@ -677,7 +678,8 @@ public class IntentForwarderActivityTest {
}
@Override
- public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
+ public CompletableFuture<ResolveInfo> resolveActivityAsUser(
+ Intent intent, int flags, int userId) {
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = sPackageName;
activityInfo.name = sActivityName;
@@ -686,7 +688,7 @@ public class IntentForwarderActivityTest {
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = activityInfo;
- return resolveInfo;
+ return CompletableFuture.completedFuture(resolveInfo);
}
@Override
diff --git a/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
new file mode 100644
index 000000000000..63a68e99b788
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/view/ScrollViewCaptureHelperTest.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.test.annotation.UiThreadTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Random;
+
+public class ScrollViewCaptureHelperTest {
+
+ private FrameLayout mParent;
+ private ScrollView mTarget;
+ private LinearLayout mContent;
+ private WindowManager mWm;
+
+ private WindowManager.LayoutParams mWindowLayoutParams;
+
+ private static final int CHILD_VIEWS = 12;
+ public static final int CHILD_VIEW_HEIGHT = 300;
+
+ private static final int WINDOW_WIDTH = 800;
+ private static final int WINDOW_HEIGHT = 1200;
+
+ private static final int CAPTURE_HEIGHT = 600;
+
+ private Random mRandom;
+
+ private static float sDensity;
+
+ @BeforeClass
+ public static void setUpClass() {
+ sDensity = getContext().getResources().getDisplayMetrics().density;
+ }
+
+ @Before
+ @UiThreadTest
+ public void setUp() {
+ mRandom = new Random();
+ mParent = new FrameLayout(getContext());
+
+ mTarget = new ScrollView(getContext());
+ mParent.addView(mTarget, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+ mContent = new LinearLayout(getContext());
+ mContent.setOrientation(LinearLayout.VERTICAL);
+ mTarget.addView(mContent, new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+
+ for (int i = 0; i < CHILD_VIEWS; i++) {
+ TextView view = new TextView(getContext());
+ view.setText("Child #" + i);
+ view.setTextColor(Color.WHITE);
+ view.setTextSize(30f);
+ view.setBackgroundColor(Color.rgb(mRandom.nextFloat(), mRandom.nextFloat(),
+ mRandom.nextFloat()));
+ mContent.addView(view, new ViewGroup.LayoutParams(MATCH_PARENT, CHILD_VIEW_HEIGHT));
+ }
+
+ // Window -> Parent -> Target -> Content
+
+ mWm = getContext().getSystemService(WindowManager.class);
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(WINDOW_WIDTH, WINDOW_HEIGHT,
+ TYPE_APPLICATION_OVERLAY, FLAG_NOT_TOUCHABLE, PixelFormat.OPAQUE);
+ mWindowLayoutParams.setTitle("ScrollViewCaptureHelper");
+ mWindowLayoutParams.gravity = Gravity.CENTER;
+ mWm.addView(mParent, mWindowLayoutParams);
+ }
+
+ @After
+ @UiThreadTest
+ public void tearDown() {
+ mWm.removeViewImmediate(mParent);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onPrepareForStart() {
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+ }
+
+ static void assertEmpty(Rect r) {
+ if (r != null && !r.isEmpty()) {
+ fail("Not true that " + r + " is empty");
+ }
+ }
+
+ static void assertContains(Rect parent, Rect child) {
+ if (!parent.contains(child)) {
+ fail("Not true that " + parent + " contains " + child);
+ }
+ }
+
+ static void assertRectEquals(Rect parent, Rect child) {
+ if (!parent.equals(child)) {
+ fail("Not true that " + parent + " is equal to " + child);
+ }
+ }
+
+ static Rect getVisibleRect(View v) {
+ Rect r = new Rect(0, 0, v.getWidth(), v.getHeight());
+ v.getLocalVisibleRect(r);
+ return r;
+ }
+
+
+ static int assertScrollToY(View v, int scrollY) {
+ v.scrollTo(0, scrollY);
+ int dest = v.getScrollY();
+ assertEquals(scrollY, dest);
+ return scrollY;
+ }
+
+
+ static void assertCapturedAreaCompletelyVisible(int startScrollY, Rect requestRect,
+ Rect localVisibleNow) {
+ Rect captured = new Rect(localVisibleNow);
+ captured.offset(0, -startScrollY); // make relative
+
+ if (!captured.contains(requestRect)) {
+ fail("Not true that all of " + requestRect + " is contained by " + captured);
+ }
+ }
+ static void assertCapturedAreaPartiallyVisible(int startScrollY, Rect requestRect,
+ Rect localVisibleNow) {
+ Rect captured = new Rect(localVisibleNow);
+ captured.offset(0, -startScrollY); // make relative
+
+ if (!Rect.intersects(captured, requestRect)) {
+ fail("Not true that any of " + requestRect + " intersects " + captured);
+ }
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_up_fromTop() {
+ final int startScrollY = assertScrollToY(mTarget, 0);
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
+
+ Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+
+ // The result is an empty rectangle and no scrolling, since it
+ // is not possible to physically scroll further up to make the
+ // requested area visible at all (it doesn't exist).
+ assertEmpty(result);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_down_fromTop() {
+ final int startScrollY = assertScrollToY(mTarget, 0);
+
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ assertTrue(scrollBounds.height() > CAPTURE_HEIGHT);
+
+ // Capture between y = +1200 to +1500 pixels BELOW current top
+ Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+ WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+ assertRectEquals(request, result);
+
+ assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+ }
+
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_up_fromMiddle() {
+ final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+
+ assertRectEquals(request, result);
+
+ assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_down_fromMiddle() {
+ final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT);
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+ WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+ assertRectEquals(request, result);
+
+ assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_up_fromBottom() {
+ final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, -CAPTURE_HEIGHT, scrollBounds.width(), 0);
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+ assertRectEquals(request, result);
+
+ assertCapturedAreaCompletelyVisible(startScrollY, request, getVisibleRect(mContent));
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_down_fromBottom() {
+ final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2);
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ Rect request = new Rect(0, WINDOW_HEIGHT, scrollBounds.width(),
+ WINDOW_HEIGHT + CAPTURE_HEIGHT);
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+
+ // The result is an empty rectangle and no scrolling, since it
+ // is not possible to physically scroll further down to make the
+ // requested area visible at all (it doesn't exist).
+ assertEmpty(result);
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_offTopEdge() {
+ final int startScrollY = assertScrollToY(mTarget, 0);
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ // Create a request which lands halfway off the top of the content
+ //from -1500 to -900, (starting at 1200 = -300 to +300 within the content)
+ int top = 0;
+ Rect request = new Rect(
+ 0, top - (CAPTURE_HEIGHT / 2),
+ scrollBounds.width(), top + (CAPTURE_HEIGHT / 2));
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+ // The result is a partial result
+ Rect expectedResult = new Rect(request);
+ expectedResult.top += 300; // top half clipped
+ assertRectEquals(expectedResult, result);
+ assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
+ }
+
+ @Test
+ @UiThreadTest
+ public void onScrollRequested_offBottomEdge() {
+ final int startScrollY = assertScrollToY(mTarget, WINDOW_HEIGHT * 2); // 2400
+
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ Rect scrollBounds = svc.onComputeScrollBounds(mTarget);
+ svc.onPrepareForStart(mTarget, scrollBounds);
+
+ // Create a request which lands halfway off the bottom of the content
+ //from 600 to to 1200, (starting at 2400 = 3000 to 3600 within the content)
+
+ int bottom = WINDOW_HEIGHT;
+ Rect request = new Rect(
+ 0, bottom - (CAPTURE_HEIGHT / 2),
+ scrollBounds.width(), bottom + (CAPTURE_HEIGHT / 2));
+
+ Rect result = svc.onScrollRequested(mTarget, scrollBounds, request);
+
+ Rect expectedResult = new Rect(request);
+ expectedResult.bottom -= 300; // bottom half clipped
+ assertRectEquals(expectedResult, result);
+ assertCapturedAreaPartiallyVisible(startScrollY, request, getVisibleRect(mContent));
+
+ }
+
+ @Test
+ @UiThreadTest
+ public void onPrepareForEnd() {
+ ScrollViewCaptureHelper svc = new ScrollViewCaptureHelper();
+ svc.onPrepareForEnd(mTarget);
+ }
+}
diff --git a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
index cd6b3af5fa6d..fe33cd80f735 100644
--- a/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
+++ b/core/tests/screenshothelpertests/src/com/android/internal/util/ScreenshotHelperTest.java
@@ -36,6 +36,7 @@ import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
+import android.view.WindowManager;
import androidx.test.runner.AndroidJUnit4;
@@ -91,7 +92,8 @@ public final class ScreenshotHelperTest {
public void testProvidedImageScreenshot() {
mScreenshotHelper.provideScreenshot(
Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), new Rect(),
- Insets.of(0, 0, 0, 0), 1, mHandler, null);
+ Insets.of(0, 0, 0, 0), 1,
+ WindowManager.ScreenshotSource.SCREENSHOT_OTHER, mHandler, null);
}
@Test
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 18086ec0313e..c5ac451a9539 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -283,6 +283,12 @@
"group": "WM_DEBUG_APP_TRANSITIONS",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
+ "-1517908912": {
+ "message": "requestScrollCapture: caught exception dispatching to window.token=%s",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"-1515151503": {
"message": ">>> OPEN TRANSACTION removeReplacedWindows",
"level": "INFO",
@@ -1441,6 +1447,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "646981048": {
+ "message": "Invalid displayId for requestScrollCapture: %d",
+ "level": "ERROR",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"662572728": {
"message": "Attempted to add a toast window with bad token %s. Aborting.",
"level": "WARN",
@@ -1597,6 +1609,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "1046922686": {
+ "message": "requestScrollCapture: caught exception dispatching callback: %s",
+ "level": "WARN",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1051545910": {
"message": "Exit animation finished in %s: remove=%b",
"level": "VERBOSE",
diff --git a/data/keyboards/Vendor_18d1_Product_0200.kcm b/data/keyboards/Vendor_18d1_Product_0200.kcm
new file mode 100644
index 000000000000..231fac6b48b7
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_0200.kcm
@@ -0,0 +1,48 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+type FULL
+
+key BUTTON_A {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_B {
+ base: fallback BACK
+}
+
+key BUTTON_X {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_Y {
+ base: fallback BACK
+}
+
+key BUTTON_THUMBL {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_THUMBR {
+ base: fallback DPAD_CENTER
+}
+
+key BUTTON_SELECT {
+ base: fallback MENU
+}
+
+key BUTTON_MODE {
+ base: fallback MENU
+}
+
diff --git a/data/keyboards/Vendor_18d1_Product_0200.kl b/data/keyboards/Vendor_18d1_Product_0200.kl
new file mode 100644
index 000000000000..d30bcc60e663
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_0200.kl
@@ -0,0 +1,71 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Keyboard map for the android virtual remote running as a gamepad
+#
+
+key 0x130 BUTTON_A
+key 0x131 BUTTON_B
+key 0x133 BUTTON_X
+key 0x134 BUTTON_Y
+
+key 0x136 BUTTON_L2
+key 0x137 BUTTON_R2
+key 0x138 BUTTON_L1
+key 0x139 BUTTON_R1
+
+key 0x13a BUTTON_SELECT
+key 0x13b BUTTON_START
+key 0x13c BUTTON_MODE
+
+key 0x13d BUTTON_THUMBL
+key 0x13e BUTTON_THUMBR
+
+key 103 DPAD_UP
+key 108 DPAD_DOWN
+key 105 DPAD_LEFT
+key 106 DPAD_RIGHT
+
+# Generic usage buttons
+key 0x2c0 BUTTON_1
+key 0x2c1 BUTTON_2
+key 0x2c2 BUTTON_3
+key 0x2c3 BUTTON_4
+key 0x2c4 BUTTON_5
+key 0x2c5 BUTTON_6
+key 0x2c6 BUTTON_7
+key 0x2c7 BUTTON_8
+key 0x2c8 BUTTON_9
+key 0x2c9 BUTTON_10
+key 0x2ca BUTTON_11
+key 0x2cb BUTTON_12
+key 0x2cc BUTTON_13
+key 0x2cd BUTTON_14
+key 0x2ce BUTTON_15
+key 0x2cf BUTTON_16
+
+# assistant buttons
+key 0x246 VOICE_ASSIST
+key 0x247 ASSIST
+
+axis 0x00 X
+axis 0x01 Y
+axis 0x02 Z
+axis 0x05 RZ
+axis 0x09 RTRIGGER
+axis 0x0a LTRIGGER
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index cd878c5a77cb..228d03a1dd10 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -120,7 +120,7 @@ public class SurfaceTexture {
/**
* Construct a new SurfaceTexture to stream images to a given OpenGL texture.
- *
+ * <p>
* In single buffered mode the application is responsible for serializing access to the image
* content buffer. Each time the image content is to be updated, the
* {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -143,7 +143,7 @@ public class SurfaceTexture {
/**
* Construct a new SurfaceTexture to stream images to a given OpenGL texture.
- *
+ * <p>
* In single buffered mode the application is responsible for serializing access to the image
* content buffer. Each time the image content is to be updated, the
* {@link #releaseTexImage()} method must be called before the image content producer takes
@@ -152,7 +152,7 @@ public class SurfaceTexture {
* must be called before each ANativeWindow_lock, or that call will fail. When producing
* image content with OpenGL ES, {@link #releaseTexImage()} must be called before the first
* OpenGL ES function call each frame.
- *
+ * <p>
* Unlike {@link #SurfaceTexture(int, boolean)}, which takes an OpenGL texture object name,
* this constructor creates the SurfaceTexture in detached mode. A texture name must be passed
* in using {@link #attachToGLContext} before calling {@link #releaseTexImage()} and producing
@@ -222,15 +222,15 @@ public class SurfaceTexture {
* method. Both video and camera based image producers do override the size. This method may
* be used to set the image size when producing images with {@link android.graphics.Canvas} (via
* {@link android.view.Surface#lockCanvas}), or OpenGL ES (via an EGLSurface).
- *
+ * <p>
* The new default buffer size will take effect the next time the image producer requests a
* buffer to fill. For {@link android.graphics.Canvas} this will be the next time {@link
* android.view.Surface#lockCanvas} is called. For OpenGL ES, the EGLSurface should be
* destroyed (via eglDestroySurface), made not-current (via eglMakeCurrent), and then recreated
- * (via eglCreateWindowSurface) to ensure that the new default size has taken effect.
- *
+ * (via {@code eglCreateWindowSurface}) to ensure that the new default size has taken effect.
+ * <p>
* The width and height parameters must be no greater than the minimum of
- * GL_MAX_VIEWPORT_DIMS and GL_MAX_TEXTURE_SIZE (see
+ * {@code GL_MAX_VIEWPORT_DIMS} and {@code GL_MAX_TEXTURE_SIZE} (see
* {@link javax.microedition.khronos.opengles.GL10#glGetIntegerv glGetIntegerv}).
* An error due to invalid dimensions might not be reported until
* updateTexImage() is called.
@@ -242,7 +242,7 @@ public class SurfaceTexture {
/**
* Update the texture image to the most recent frame from the image stream. This may only be
* called while the OpenGL ES context that owns the texture is current on the calling thread.
- * It will implicitly bind its texture to the GL_TEXTURE_EXTERNAL_OES texture target.
+ * It will implicitly bind its texture to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
*/
public void updateTexImage() {
nativeUpdateTexImage();
@@ -251,6 +251,7 @@ public class SurfaceTexture {
/**
* Releases the the texture content. This is needed in single buffered mode to allow the image
* content producer to take ownership of the image buffer.
+ * <p>
* For more information see {@link #SurfaceTexture(int, boolean)}.
*/
public void releaseTexImage() {
@@ -263,7 +264,7 @@ public class SurfaceTexture {
* ES texture object will be deleted as a result of this call. After calling this method all
* calls to {@link #updateTexImage} will throw an {@link java.lang.IllegalStateException} until
* a successful call to {@link #attachToGLContext} is made.
- *
+ * <p>
* This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
* contexts. Note, however, that the image contents are only accessible from one OpenGL ES
* context at a time.
@@ -279,8 +280,8 @@ public class SurfaceTexture {
* Attach the SurfaceTexture to the OpenGL ES context that is current on the calling thread. A
* new OpenGL ES texture object is created and populated with the SurfaceTexture image frame
* that was current at the time of the last call to {@link #detachFromGLContext}. This new
- * texture is bound to the GL_TEXTURE_EXTERNAL_OES texture target.
- *
+ * texture is bound to the {@code GL_TEXTURE_EXTERNAL_OES} texture target.
+ * <p>
* This can be used to access the SurfaceTexture image contents from multiple OpenGL ES
* contexts. Note, however, that the image contents are only accessible from one OpenGL ES
* context at a time.
@@ -297,16 +298,16 @@ public class SurfaceTexture {
/**
* Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set by
- * the most recent call to updateTexImage.
- *
+ * the most recent call to {@link #updateTexImage}.
+ * <p>
* This transform matrix maps 2D homogeneous texture coordinates of the form (s, t, 0, 1) with s
* and t in the inclusive range [0, 1] to the texture coordinate that should be used to sample
* that location from the texture. Sampling the texture outside of the range of this transform
* is undefined.
- *
+ * <p>
* The matrix is stored in column-major order so that it may be passed directly to OpenGL ES via
- * the glLoadMatrixf or glUniformMatrix4fv functions.
- *
+ * the {@code glLoadMatrixf} or {@code glUniformMatrix4fv} functions.
+ * <p>
* If the underlying buffer has a crop associated with it, the transformation will also include
* a slight scale to cut off a 1-texel border around the edge of the crop. This ensures that
* when the texture is bilinear sampled that no texels outside of the buffer's valid region
@@ -326,7 +327,7 @@ public class SurfaceTexture {
/**
* Retrieve the timestamp associated with the texture image set by the most recent call to
- * updateTexImage.
+ * {@link #updateTexImage}.
*
* <p>This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp
* should be unaffected by time-of-day adjustments. The specific meaning and zero point of the
@@ -337,8 +338,8 @@ public class SurfaceTexture {
*
* <p>For camera sources, timestamps should be strictly monotonic. Timestamps from MediaPlayer
* sources may be reset when the playback position is set. For EGL and Vulkan producers, the
- * timestamp is the desired present time set with the EGL_ANDROID_presentation_time or
- * VK_GOOGLE_display_timing extensions.</p>
+ * timestamp is the desired present time set with the {@code EGL_ANDROID_presentation_time} or
+ * {@code VK_GOOGLE_display_timing} extensions.</p>
*/
public long getTimestamp() {
@@ -346,16 +347,17 @@ public class SurfaceTexture {
}
/**
- * release() frees all the buffers and puts the SurfaceTexture into the
+ * {@code release()} frees all the buffers and puts the SurfaceTexture into the
* 'abandoned' state. Once put in this state the SurfaceTexture can never
* leave it. When in the 'abandoned' state, all methods of the
- * IGraphicBufferProducer interface will fail with the NO_INIT error.
- *
+ * {@code IGraphicBufferProducer} interface will fail with the {@code NO_INIT}
+ * error.
+ * <p>
* Note that while calling this method causes all the buffers to be freed
* from the perspective of the the SurfaceTexture, if there are additional
* references on the buffers (e.g. if a buffer is referenced by a client or
* by OpenGL ES as a texture) then those buffer will remain allocated.
- *
+ * <p>
* Always call this method when you are done with SurfaceTexture. Failing
* to do so may delay resource deallocation for a significant amount of
* time.
@@ -367,7 +369,7 @@ public class SurfaceTexture {
}
/**
- * Returns true if the SurfaceTexture was released.
+ * Returns {@code true} if the SurfaceTexture was released.
*
* @see #release()
*/
@@ -400,7 +402,7 @@ public class SurfaceTexture {
}
/**
- * Returns true if the SurfaceTexture is single-buffered
+ * Returns {@code true} if the SurfaceTexture is single-buffered.
* @hide
*/
public boolean isSingleBuffered() {
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 1db2f6357308..b351b3d77430 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -95,9 +95,7 @@ public abstract class IdentityCredential {
/**
* Sets whether to allow using an authentication key which use count has been exceeded if no
* other key is available. This must be called prior to calling
- * {@link #getEntries(byte[], Map, byte[], byte[])} or using a
- * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
- * object.
+ * {@link #getEntries(byte[], Map, byte[], byte[])}.
*
* By default this is set to true.
*
@@ -123,13 +121,14 @@ public abstract class IdentityCredential {
* entries.
*
* <p>It is the responsibility of the calling application to know if authentication is needed
- * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user
+ * and use e.g. {@link android.hardware.biometrics.BiometricPrompt} to make the user
* authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
* references this object. If needed, this must be done before calling
* {@link #getEntries(byte[], Map, byte[], byte[])}.
*
- * <p>If this method returns successfully (i.e. without throwing an exception), it must not be
- * called again on this instance.
+ * <p>It is permissible to call this method multiple times using the same instance but if this
+ * is done, the {@code sessionTranscript} parameter must be identical for each call. If this is
+ * not the case, the {@link SessionTranscriptMismatchException} exception is thrown.
*
* <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
* from the verifier. The content can be defined in the way appropriate for the credential, byt
@@ -141,6 +140,9 @@ public abstract class IdentityCredential {
* the example below.</li>
* </ul>
*
+ * <p>If these requirements are not met the {@link InvalidRequestMessageException} exception
+ * is thrown.
+ *
* <p>Here's an example of CBOR which conforms to this requirement:
* <pre>
* ItemsRequest = {
@@ -149,6 +151,8 @@ public abstract class IdentityCredential {
* ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
* }
*
+ * DocType = tstr
+ *
* NameSpaces = {
* + NameSpace => DataElements ; Requested data elements for each NameSpace
* }
@@ -172,16 +176,18 @@ public abstract class IdentityCredential {
* EReaderKeyBytes
* ]
*
- * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
- * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+ * DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement) ; Bytes of DeviceEngagement
+ * EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub) ; Bytes of EReaderKey.pub
+ *
+ * EReaderKey.Pub = COSE_Key ; Ephemeral public key provided by reader
* </pre>
*
- * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part
- * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear
- * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present
+ * <p>where a {@code COSE_Key} structure for the public part of the key-pair previously
+ * generated by {@link #createEphemeralKeyPair()} must appear somewhere in
+ * {@code DeviceEngagement} and the X and Y coordinates must both be present
* in uncompressed form.
*
- * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1
+ * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1}
* structure as defined in RFC 8152. For the payload nil shall be used and the
* detached payload is the ReaderAuthentication CBOR described below.
* <pre>
@@ -194,20 +200,23 @@ public abstract class IdentityCredential {
* ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest) ; Bytes of ItemsRequest
* </pre>
*
- * <p>The public key corresponding to the key used to made signature, can be
- * found in the {@code x5chain} unprotected header element of the COSE_Sign1
- * structure (as as described in 'draft-ietf-cose-x509-04'). There will be at
- * least one certificate in said element and there may be more (and if so,
+ * <p>where {@code ItemsRequestBytes} are the bytes in the {@code requestMessage} parameter.
+ *
+ * <p>The public key corresponding to the key used to make the signature, can be found in the
+ * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as
+ * described in
+ * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-04">draft-ietf-cose-x509-04</a>).
+ * There will be at least one certificate in said element and there may be more (and if so,
* each certificate must be signed by its successor).
*
- * <p>Data elements protected by reader authentication is returned if, and only if, they are
+ * <p>Data elements protected by reader authentication are returned if, and only if, they are
* mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most
- * certificate in {@code readerCertificateChain}, and the data element is configured
- * with an {@link AccessControlProfile} with a {@link X509Certificate} in
- * {@code readerCertificateChain}.
+ * certificate in the reader's certificate chain, and the data element is configured
+ * with an {@link AccessControlProfile} configured with an X.509 certificate which appears
+ * in the certificate chain.
*
* <p>Note that only items referenced in {@code entriesToRequest} are returned - the
- * {@code requestMessage} parameter is only used to for enforcing reader authentication.
+ * {@code requestMessage} parameter is used only for enforcing reader authentication.
*
* <p>The reason for having {@code requestMessage} and {@code entriesToRequest} as separate
* parameters is that the former represents a request from the remote verifier device
@@ -219,13 +228,12 @@ public abstract class IdentityCredential {
* @param entriesToRequest The entries to request, organized as a map of namespace
* names with each value being a collection of data elements
* in the given namespace.
- * @param readerSignature COSE_Sign1 structure as described above or {@code null}
- * if reader authentication is not being used.
+ * @param readerSignature A {@code COSE_Sign1} structure as described above or
+ * {@code null} if reader authentication is not being used.
* @return A {@link ResultData} object containing entry data organized by namespace and a
* cryptographically authenticated representation of the same data.
* @throws SessionTranscriptMismatchException Thrown when trying use multiple different
- * session transcripts in the same presentation
- * session.
+ * session transcripts.
* @throws NoAuthenticationKeyAvailableException if authentication keys were never
* provisioned, the method
* {@link #setAvailableAuthenticationKeys(int, int)}
@@ -255,8 +263,8 @@ public abstract class IdentityCredential {
* Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
* and the number of times each should be used.
*
- * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each
- * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s
+ * <p>The Identity Credential system will select the least-used dynamic authentication key each
+ * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. Identity Credentials
* for which this method has not been called behave as though it had been called wit
* {@code keyCount} 0 and {@code maxUsesPerKey} 1.
*
@@ -274,9 +282,10 @@ public abstract class IdentityCredential {
* <p>When there aren't enough certified dynamic authentication keys, either because the key
* count has been increased or because one or more keys have reached their usage count, this
* method will generate replacement keys and certificates and return them for issuer
- * certification. The issuer certificates and associated static authentication data must then
- * be provided back to the {@code IdentityCredential} using
- * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}.
+ * certification. The issuer certificates and associated static authentication data must then
+ * be provided back to the Identity Credential using
+ * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}. The private part of
+ * each authentication key never leaves secure hardware.
*
* <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
* can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
index a1dfc77adb29..4f834d2b87b5 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -78,17 +78,21 @@ public abstract class IdentityCredentialStore {
/**
* Specifies that the cipher suite that will be used to secure communications between the reader
- * is:
+ * and the prover is using the following primitives
*
* <ul>
- * <li>ECDHE with HKDF-SHA-256 for key agreement.</li>
- * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
- * for every message).</li>
- * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
- * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential}
- * for details on reader and prover signing keys.</li>
+ * <li>ECKA-DH (Elliptic Curve Key Agreement Algorithm - Diffie-Hellman, see BSI TR-03111).</li>
+ *
+ * <li>HKDF-SHA-256 (see RFC 5869).</li>
+ *
+ * <li>AES-256-GCM (see NIST SP 800-38D).</li>
+ *
+ * <li>HMAC-SHA-256 (see RFC 2104).</li>
* </ul>
*
+ * <p>The exact way these primitives are combined to derive the session key is specified in
+ * section 9.2.1.4 of ISO/IEC 18013-5 (see description of cipher suite '1').<p>
+ *
* <p>
* At present this is the only supported cipher suite.
*/
@@ -135,9 +139,20 @@ public abstract class IdentityCredentialStore {
/**
* Creates a new credential.
*
+ * <p>When a credential is created, a cryptographic key-pair - CredentialKey - is created which
+ * is used to authenticate the store to the Issuing Authority. The private part of this
+ * key-pair never leaves secure hardware and the public part can be obtained using
+ * {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])} on the
+ * returned object.
+ *
+ * <p>In addition, all of the Credential data content is imported and a certificate for the
+ * CredentialKey and a signature produced with the CredentialKey are created. These latter
+ * values may be checked by an issuing authority to verify that the data was imported into
+ * secure hardware and that it was imported unmodified.
+ *
* @param credentialName The name used to identify the credential.
* @param docType The document type for the credential.
- * @return A @{link WritableIdentityCredential} that can be used to create a new credential.
+ * @return A {@link WritableIdentityCredential} that can be used to create a new credential.
* @throws AlreadyPersonalizedException if a credential with the given name already exists.
* @throws DocTypeNotSupportedException if the given document type isn't supported by the store.
*/
@@ -148,6 +163,10 @@ public abstract class IdentityCredentialStore {
/**
* Retrieve a named credential.
*
+ * <p>The cipher suite used to communicate with the remote verifier must also be specified.
+ * Currently only a single cipher-suite is supported. Support for other cipher suites may be
+ * added in a future version of this API.
+ *
* @param credentialName the name of the credential to retrieve.
* @param cipherSuite the cipher suite to use for communicating with the verifier.
* @return The named credential, or null if not found.
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 13552d619e05..37de2c4a50ea 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -34,23 +34,23 @@ public abstract class ResultData {
/** Value was successfully retrieved. */
public static final int STATUS_OK = 0;
- /** Requested entry does not exist. */
+ /** The entry does not exist. */
public static final int STATUS_NO_SUCH_ENTRY = 1;
- /** Requested entry was not requested. */
+ /** The entry was not requested. */
public static final int STATUS_NOT_REQUESTED = 2;
- /** Requested entry wasn't in the request message. */
+ /** The entry wasn't in the request message. */
public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
- /** The requested entry was not retrieved because user authentication wasn't performed. */
+ /** The entry was not retrieved because user authentication failed. */
public static final int STATUS_USER_AUTHENTICATION_FAILED = 4;
- /** The requested entry was not retrieved because reader authentication wasn't performed. */
+ /** The entry was not retrieved because reader authentication failed. */
public static final int STATUS_READER_AUTHENTICATION_FAILED = 5;
/**
- * The requested entry was not retrieved because it was configured without any access
+ * The entry was not retrieved because it was configured without any access
* control profile.
*/
public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
@@ -88,11 +88,10 @@ public abstract class ResultData {
*
* DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
* EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
- *
* DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
* </pre>
*
- * where
+ * <p>where
*
* <pre>
* DeviceNameSpaces = {
@@ -116,15 +115,16 @@ public abstract class ResultData {
public abstract @NonNull byte[] getAuthenticatedData();
/**
- * Returns a message authentication code over the data returned by
- * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted
- * credential.
+ * Returns a message authentication code over the {@code DeviceAuthentication} CBOR
+ * specified in {@link #getAuthenticatedData()}, to prove to the reader that the data
+ * is from a trusted credential.
*
* <p>The MAC proves to the reader that the data is from a trusted credential. This code is
* produced by using the key agreement and key derivation function from the ciphersuite
* with the authentication private key and the reader ephemeral public key to compute a
* shared message authentication code (MAC) key, then using the MAC function from the
- * ciphersuite to compute a MAC of the authenticated data.
+ * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of
+ * ISO/IEC 18013-5 for details of this operation.
*
* <p>If the {@code sessionTranscript} parameter passed to
* {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null}
@@ -157,7 +157,7 @@ public abstract class ResultData {
/**
* Get the names of all entries.
*
- * This includes the name of entries that wasn't successfully retrieved.
+ * <p>This includes the name of entries that wasn't successfully retrieved.
*
* @param namespaceName the namespace name to get entries for.
* @return A collection of names or {@code null} if there are no entries for the given
@@ -168,7 +168,7 @@ public abstract class ResultData {
/**
* Get the names of all entries that was successfully retrieved.
*
- * This only return entries for which {@link #getStatus(String, String)} will return
+ * <p>This only return entries for which {@link #getStatus(String, String)} will return
* {@link #STATUS_OK}.
*
* @param namespaceName the namespace name to get entries for.
@@ -181,16 +181,15 @@ public abstract class ResultData {
/**
* Gets the status of an entry.
*
- * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
+ * <p>This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
* if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested,
* {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't
- * present in the request message,
- * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
+ * present in the request message, {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
* wasn't retrieved because the necessary user authentication wasn't performed,
- * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain
- * didn't match the set of certificates the entry was provisioned with, or
- * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any
- * access control profiles.
+ * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain didn't
+ * match the set of certificates the entry was provisioned with, or
+ * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any access
+ * control profiles.
*
* @param namespaceName the namespace name of the entry.
* @param name the name of the entry to get the value for.
@@ -201,7 +200,7 @@ public abstract class ResultData {
/**
* Gets the raw CBOR data for the value of an entry.
*
- * This should only be called on an entry for which the {@link #getStatus(String, String)}
+ * <p>This should only be called on an entry for which the {@link #getStatus(String, String)}
* method returns {@link #STATUS_OK}.
*
* @param namespaceName the namespace name of the entry.
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
index e2a389bfd4da..c7aa32855abc 100644
--- a/identity/java/android/security/identity/WritableIdentityCredential.java
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -41,15 +41,16 @@ public abstract class WritableIdentityCredential {
* <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a>
* attestation extension which describes the key and the security hardware in which it lives.
*
- * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates
- * it is an Identity Credential key (which can only sign/MAC very specific messages) and not
- * an Android Keystore key (which can be used to sign/MAC anything).
+ * <p>Additionally, the attestation extension will contain the tag Tag::IDENTITY_CREDENTIAL_KEY
+ * which indicates it is an Identity Credential key (which can only sign/MAC very specific
+ * messages) and not an Android Keystore key (which can be used to sign/MAC anything).
*
* <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not
- * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is
- * present, the passed in challenge is present, the device has verified boot enabled, that each
- * certificate in the chain is signed by its successor, that none of the certificates have been
- * revoked and so on.
+ * limited to) checking that the root certificate is well-known, the tag
+ * Tag::IDENTITY_CREDENTIAL_KEY present, the passed in challenge is present, the tag
+ * Tag::ATTESTATION_APPLICATION_ID is set to the expected Android application, the device
+ * has verified boot enabled, each certificate in the chain is signed by its successor,
+ * none of the certificates have been revoked, and so on.
*
* <p>It is not strictly necessary to use this method to provision a credential if the issuing
* authority doesn't care about the nature of the security hardware. If called, however, this
diff --git a/media/java/android/media/AudioPortEventHandler.java b/media/java/android/media/AudioPortEventHandler.java
index 14249cbe8945..8e8dfaf24b9a 100644
--- a/media/java/android/media/AudioPortEventHandler.java
+++ b/media/java/android/media/AudioPortEventHandler.java
@@ -78,7 +78,8 @@ class AudioPortEventHandler {
listeners.add((AudioManager.OnAudioPortUpdateListener)msg.obj);
}
} else {
- listeners = mListeners;
+ listeners = (ArrayList<AudioManager.OnAudioPortUpdateListener>)
+ mListeners.clone();
}
}
// reset audio port cache if the event corresponds to a change coming
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index dc06153ffa9f..a8b82ba401eb 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -24,11 +24,15 @@ import android.os.Bundle;
* @hide
*/
oneway interface IMediaRouter2 {
- void notifyRestoreRoute();
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
void notifySessionCreated(int requestId, in @nullable RoutingSessionInfo sessionInfo);
void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
void notifySessionReleased(in RoutingSessionInfo sessionInfo);
+ /**
+ * Gets hints of the new session for the given route.
+ * Call MediaRouterService#notifySessionHintsForCreatingSession to pass the result.
+ */
+ void getSessionHintsForCreatingSession(long uniqueRequestId, in MediaRoute2Info route);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 0d87736cfd47..52bac671cc6f 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -59,6 +59,8 @@ interface IMediaRouterService {
void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
in MediaRoute2Info route, in @nullable Bundle sessionHints);
+ void notifySessionHintsForCreatingSession(IMediaRouter2 router, long uniqueRequestId,
+ in MediaRoute2Info route, in @nullable Bundle sessionHints);
void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 97d32d88c7f2..c652628eb425 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -388,7 +388,7 @@ public final class MediaCas implements AutoCloseable {
@Override
public void onReclaimResources() {
synchronized (mSessionMap) {
- mSessionMap.forEach((casSession, sessionResourceId) -> casSession.close());
+ mSessionMap.forEach((casSession, sessionResourceHandle) -> casSession.close());
}
mEventHandler.sendMessage(mEventHandler.obtainMessage(
EventHandler.MSG_CAS_RESOURCE_LOST));
@@ -868,7 +868,7 @@ public final class MediaCas implements AutoCloseable {
}
}
- private int getSessionResourceId() throws MediaCasException {
+ private int getSessionResourceHandle() throws MediaCasException {
validateInternalStates();
int[] sessionResourceHandle = new int[1];
@@ -881,14 +881,14 @@ public final class MediaCas implements AutoCloseable {
"insufficient resource to Open Session");
}
}
- return sessionResourceHandle[0];
+ return sessionResourceHandle[0];
}
- private void addSessionToResourceMap(Session session, int sessionResourceId) {
+ private void addSessionToResourceMap(Session session, int sessionResourceHandle) {
- if (sessionResourceId != -1) {
+ if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
synchronized (mSessionMap) {
- mSessionMap.put(session, sessionResourceId);
+ mSessionMap.put(session, sessionResourceHandle);
}
}
}
@@ -918,13 +918,13 @@ public final class MediaCas implements AutoCloseable {
* @throws MediaCasStateException for CAS-specific state exceptions.
*/
public Session openSession() throws MediaCasException {
- int sessionResourceId = getSessionResourceId();
+ int sessionResourceHandle = getSessionResourceHandle();
try {
OpenSessionCallback cb = new OpenSessionCallback();
mICas.openSession(cb);
MediaCasException.throwExceptionIfNeeded(cb.mStatus);
- addSessionToResourceMap(cb.mSession, sessionResourceId);
+ addSessionToResourceMap(cb.mSession, sessionResourceHandle);
return cb.mSession;
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
@@ -952,7 +952,7 @@ public final class MediaCas implements AutoCloseable {
@Nullable
public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
throws MediaCasException {
- int sessionResourceId = getSessionResourceId();
+ int sessionResourceHandle = getSessionResourceHandle();
if (mICasV12 == null) {
Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
@@ -963,7 +963,7 @@ public final class MediaCas implements AutoCloseable {
OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb);
MediaCasException.throwExceptionIfNeeded(cb.mStatus);
- addSessionToResourceMap(cb.mSession, sessionResourceId);
+ addSessionToResourceMap(cb.mSession, sessionResourceHandle);
return cb.mSession;
} catch (RemoteException e) {
cleanupAndRethrowIllegalState();
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index cbf2364b50a4..1bfa9991e5d2 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -1312,7 +1312,7 @@ public final class MediaFormat {
}
/**
- * Returns the value of an long key, or the default value if the key is missing.
+ * Returns the value of a long key, or the default value if the key is missing.
*
* @return defaultValue if the key does not exist or the stored value for the key is null
* @throws ClassCastException if the stored value for the key is int, float, ByteBuffer or
@@ -1340,19 +1340,15 @@ public final class MediaFormat {
}
/**
- * Returns the value of an float key, or the default value if the key is missing.
+ * Returns the value of a float key, or the default value if the key is missing.
*
* @return defaultValue if the key does not exist or the stored value for the key is null
* @throws ClassCastException if the stored value for the key is int, long, ByteBuffer or
* String
*/
public final float getFloat(@NonNull String name, float defaultValue) {
- try {
- return getFloat(name);
- } catch (NullPointerException e) {
- /* no such field or field is null */
- return defaultValue;
- }
+ Object value = mMap.get(name);
+ return value != null ? (float) value : defaultValue;
}
/**
@@ -1366,7 +1362,7 @@ public final class MediaFormat {
}
/**
- * Returns the value of an string key, or the default value if the key is missing.
+ * Returns the value of a string key, or the default value if the key is missing.
*
* @return defaultValue if the key does not exist or the stored value for the key is null
* @throws ClassCastException if the stored value for the key is int, long, float or ByteBuffer
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index bd8fb9602656..0ea962493164 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -237,9 +237,9 @@ public final class MediaRouter2 {
} catch (RemoteException ex) {
Log.e(TAG, "Unable to unregister media router.", ex);
}
+ mStub = null;
}
mShouldUpdateRoutes = true;
- mStub = null;
}
}
@@ -690,6 +690,31 @@ public final class MediaRouter2 {
matchingController.releaseInternal(/* shouldReleaseSession= */ false);
}
+ void onGetControllerHintsForCreatingSessionOnHandler(long uniqueRequestId,
+ MediaRoute2Info route) {
+ OnGetControllerHintsListener listener = mOnGetControllerHintsListener;
+ Bundle controllerHints = null;
+ if (listener != null) {
+ controllerHints = listener.onGetControllerHints(route);
+ if (controllerHints != null) {
+ controllerHints = new Bundle(controllerHints);
+ }
+ }
+
+ MediaRouter2Stub stub;
+ synchronized (sRouterLock) {
+ stub = mStub;
+ }
+ if (stub != null) {
+ try {
+ mMediaRouterService.notifySessionHintsForCreatingSession(
+ stub, uniqueRequestId, route, controllerHints);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "getSessionHintsOnHandler: Unable to request.", ex);
+ }
+ }
+ }
+
private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
RouteDiscoveryPreference discoveryRequest) {
return routes.stream()
@@ -820,13 +845,14 @@ public final class MediaRouter2 {
*/
public interface OnGetControllerHintsListener {
/**
- * Called when the {@link MediaRouter2} is about to request
- * the media route provider service to create a controller with the given route.
+ * Called when the {@link MediaRouter2} or the system is about to request
+ * a media route provider service to create a controller with the given route.
* The {@link Bundle} returned here will be sent to media route provider service as a hint.
* <p>
- * To send hints when creating the controller, set the listener before calling
- * {@link #transferTo(MediaRoute2Info)}. The method will be called
- * on the same thread which calls {@link #transferTo(MediaRoute2Info)}.
+ * Since controller creation can be requested by the {@link MediaRouter2} and the system,
+ * set the listener as soon as possible after acquiring {@link MediaRouter2} instance.
+ * The method will be called on the same thread that calls
+ * {@link #transferTo(MediaRoute2Info)} or the main thread if it is requested by the system.
*
* @param route The route to create controller with
* @return An optional bundle of app-specific arguments to send to the provider,
@@ -1378,9 +1404,6 @@ public final class MediaRouter2 {
class MediaRouter2Stub extends IMediaRouter2.Stub {
@Override
- public void notifyRestoreRoute() throws RemoteException {}
-
- @Override
public void notifyRoutesAdded(List<MediaRoute2Info> routes) {
mHandler.sendMessage(obtainMessage(MediaRouter2::addRoutesOnHandler,
MediaRouter2.this, routes));
@@ -1415,5 +1438,13 @@ public final class MediaRouter2 {
mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
MediaRouter2.this, sessionInfo));
}
+
+ @Override
+ public void getSessionHintsForCreatingSession(long uniqueRequestId,
+ @NonNull MediaRoute2Info route) {
+ mHandler.sendMessage(obtainMessage(
+ MediaRouter2::onGetControllerHintsForCreatingSessionOnHandler,
+ MediaRouter2.this, uniqueRequestId, route));
+ }
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index b694fd059bfa..3b570b60ff24 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -147,14 +147,16 @@ public final class MediaRouter2Manager {
}
synchronized (sLock) {
- if (mCallbackRecords.size() == 0 && mClient != null) {
- try {
- mMediaRouterService.unregisterManager(mClient);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to unregister media router manager", ex);
+ if (mCallbackRecords.size() == 0) {
+ if (mClient != null) {
+ try {
+ mMediaRouterService.unregisterManager(mClient);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to unregister media router manager", ex);
+ }
+ mClient = null;
}
- //TODO: clear mRoutes?
- mClient = null;
+ mRoutes.clear();
mPreferredFeaturesMap.clear();
}
}
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
index adf4d3dcfa09..022cfeeb4e43 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
@@ -80,7 +80,8 @@ public class AudioVolumeGroupChangeHandler {
(AudioManager.VolumeGroupCallback) msg.obj);
}
} else {
- listeners = mListeners;
+ listeners = (ArrayList<AudioManager.VolumeGroupCallback>)
+ mListeners.clone();
}
}
if (listeners.isEmpty()) {
diff --git a/media/java/android/media/tv/ITvRemoteServiceInput.aidl b/media/java/android/media/tv/ITvRemoteServiceInput.aidl
index a0b6c9bfc8d8..0e6563a1ab13 100644
--- a/media/java/android/media/tv/ITvRemoteServiceInput.aidl
+++ b/media/java/android/media/tv/ITvRemoteServiceInput.aidl
@@ -39,4 +39,10 @@ oneway interface ITvRemoteServiceInput {
void sendPointerUp(IBinder token, int pointerId);
@UnsupportedAppUsage
void sendPointerSync(IBinder token);
-} \ No newline at end of file
+
+ // API specific to gamepads. Close gamepads with closeInputBridge
+ void openGamepadBridge(IBinder token, String name);
+ void sendGamepadKeyDown(IBinder token, int keyCode);
+ void sendGamepadKeyUp(IBinder token, int keyCode);
+ void sendGamepadAxisValue(IBinder token, int axis, float value);
+}
diff --git a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 7077cd1b76a0..487b444eb627 100644
--- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -218,11 +218,11 @@ interface ITunerResourceManager {
* <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request.
*
* @param request {@link CasSessionRequest} information of the current request.
- * @param sessionResourceId a one-element array to return the granted cas session id.
+ * @param casSessionHandle a one-element array to return the granted cas session handle.
*
* @return true if there is CAS session granted.
*/
- boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId);
+ boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle);
/*
* This API is used by the Tuner framework to request an available Lnb from the TunerHAL.
@@ -276,7 +276,7 @@ interface ITunerResourceManager {
*
* <p>Client must call this whenever it releases a descrambler.
*
- * @param demuxHandle the handle of the released Tuner Descrambler.
+ * @param descramblerHandle the handle of the released Tuner Descrambler.
* @param clientId the id of the client that is releasing the descrambler.
*/
void releaseDescrambler(in int descramblerHandle, int clientId);
@@ -288,10 +288,10 @@ interface ITunerResourceManager {
*
* <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release.
*
- * @param sessionResourceId the id of the released CAS session.
+ * @param casSessionHandle the handle of the released CAS session.
* @param clientId the id of the client that is releasing the cas session.
*/
- void releaseCasSession(in int sessionResourceId, int clientId);
+ void releaseCasSession(in int casSessionHandle, int clientId);
/*
* Notifies the TRM that the Lnb with the given handle was released.
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index b4dcc5d8b3df..be102d8acc10 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -362,17 +362,16 @@ public class TunerResourceManager {
* request.
*
* @param request {@link CasSessionRequest} information of the current request.
- * @param sessionResourceId a one-element array to return the granted cas session id.
- * If no CAS granted, this will return
- * {@link #INVALID_CAS_SESSION_RESOURCE_ID}.
+ * @param casSessionHandle a one-element array to return the granted cas session handel.
+ * If no CAS granted, this will return {@link #INVALID_RESOURCE_HANDLE}.
*
* @return true if there is CAS session granted.
*/
public boolean requestCasSession(@NonNull CasSessionRequest request,
- @NonNull int[] sessionResourceId) {
+ @NonNull int[] casSessionHandle) {
boolean result = false;
try {
- result = mService.requestCasSession(request, sessionResourceId);
+ result = mService.requestCasSession(request, casSessionHandle);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -471,12 +470,12 @@ public class TunerResourceManager {
* <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
* release.
*
- * @param sessionResourceId the id of the released CAS session.
+ * @param casSessionHandle the handle of the released CAS session.
* @param clientId the id of the client that is releasing the cas session.
*/
- public void releaseCasSession(int sessionResourceId, int clientId) {
+ public void releaseCasSession(int casSessionHandle, int clientId) {
try {
- mService.releaseCasSession(sessionResourceId, clientId);
+ mService.releaseCasSession(casSessionHandle, clientId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java
index 0bf0f97d2c5e..b97ac26bb915 100644
--- a/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java
+++ b/media/lib/tvremote/java/com/android/media/tv/remoteprovider/TvRemoteProvider.java
@@ -16,6 +16,8 @@
package com.android.media.tv.remoteprovider;
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
import android.content.Context;
import android.media.tv.ITvRemoteProvider;
import android.media.tv.ITvRemoteServiceInput;
@@ -24,6 +26,7 @@ import android.os.RemoteException;
import android.util.Log;
import java.util.LinkedList;
+import java.util.Objects;
/**
* Base class for emote providers implemented in unbundled service.
@@ -124,27 +127,75 @@ public abstract class TvRemoteProvider {
* @param maxPointers Maximum supported pointers
* @throws RuntimeException
*/
- public void openRemoteInputBridge(IBinder token, String name, int width, int height,
- int maxPointers) throws RuntimeException {
+ public void openRemoteInputBridge(
+ IBinder token, String name, int width, int height, int maxPointers)
+ throws RuntimeException {
+ final IBinder finalToken = Objects.requireNonNull(token);
+ final String finalName = Objects.requireNonNull(name);
+
synchronized (mOpenBridgeRunnables) {
if (mRemoteServiceInput == null) {
- Log.d(TAG, "Delaying openRemoteInputBridge() for " + name);
+ Log.d(TAG, "Delaying openRemoteInputBridge() for " + finalName);
mOpenBridgeRunnables.add(() -> {
try {
mRemoteServiceInput.openInputBridge(
- token, name, width, height, maxPointers);
- Log.d(TAG, "Delayed openRemoteInputBridge() for " + name + ": success");
+ finalToken, finalName, width, height, maxPointers);
+ Log.d(TAG, "Delayed openRemoteInputBridge() for " + finalName
+ + ": success");
+ } catch (RemoteException re) {
+ Log.e(TAG, "Delayed openRemoteInputBridge() for " + finalName
+ + ": failure", re);
+ }
+ });
+ return;
+ }
+ }
+ try {
+ mRemoteServiceInput.openInputBridge(finalToken, finalName, width, height, maxPointers);
+ Log.d(TAG, "openRemoteInputBridge() for " + finalName + ": success");
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Opens an input bridge as a gamepad device.
+ * Clients should pass in a token that can be used to match this request with a token that
+ * will be returned by {@link TvRemoteProvider#onInputBridgeConnected(IBinder token)}
+ * <p>
+ * The token should be used for subsequent calls.
+ * </p>
+ *
+ * @param token Identifier for this connection
+ * @param name Device name
+ * @throws RuntimeException
+ *
+ * @hide
+ */
+ public void openGamepadBridge(@NonNull IBinder token, @NonNull String name)
+ throws RuntimeException {
+ final IBinder finalToken = Objects.requireNonNull(token);
+ final String finalName = Objects.requireNonNull(name);
+ synchronized (mOpenBridgeRunnables) {
+ if (mRemoteServiceInput == null) {
+ Log.d(TAG, "Delaying openGamepadBridge() for " + finalName);
+
+ mOpenBridgeRunnables.add(() -> {
+ try {
+ mRemoteServiceInput.openGamepadBridge(finalToken, finalName);
+ Log.d(TAG, "Delayed openGamepadBridge() for " + finalName + ": success");
} catch (RemoteException re) {
- Log.e(TAG, "Delayed openRemoteInputBridge() for " + name + ": failure", re);
+ Log.e(TAG, "Delayed openGamepadBridge() for " + finalName + ": failure",
+ re);
}
});
return;
}
}
try {
- mRemoteServiceInput.openInputBridge(token, name, width, height, maxPointers);
- Log.d(TAG, "openRemoteInputBridge() for " + name + ": success");
+ mRemoteServiceInput.openGamepadBridge(token, finalName);
+ Log.d(TAG, "openGamepadBridge() for " + finalName + ": success");
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -157,6 +208,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void closeInputBridge(IBinder token) throws RuntimeException {
+ Objects.requireNonNull(token);
try {
mRemoteServiceInput.closeInputBridge(token);
} catch (RemoteException re) {
@@ -173,6 +225,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void clearInputBridge(IBinder token) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "clearInputBridge() token " + token);
try {
mRemoteServiceInput.clearInputBridge(token);
@@ -190,6 +243,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendTimestamp(IBinder token, long timestamp) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendTimestamp() token: " + token +
", timestamp: " + timestamp);
try {
@@ -207,6 +261,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendKeyUp(IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendKeyUp() token: " + token + ", keyCode: " + keyCode);
try {
mRemoteServiceInput.sendKeyUp(token, keyCode);
@@ -223,6 +278,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendKeyDown(IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendKeyDown() token: " + token +
", keyCode: " + keyCode);
try {
@@ -241,6 +297,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendPointerUp(IBinder token, int pointerId) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendPointerUp() token: " + token +
", pointerId: " + pointerId);
try {
@@ -262,6 +319,7 @@ public abstract class TvRemoteProvider {
*/
public void sendPointerDown(IBinder token, int pointerId, int x, int y)
throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendPointerDown() token: " + token +
", pointerId: " + pointerId);
try {
@@ -278,6 +336,7 @@ public abstract class TvRemoteProvider {
* @throws RuntimeException
*/
public void sendPointerSync(IBinder token) throws RuntimeException {
+ Objects.requireNonNull(token);
if (DEBUG_KEYS) Log.d(TAG, "sendPointerSync() token: " + token);
try {
mRemoteServiceInput.sendPointerSync(token);
@@ -286,6 +345,94 @@ public abstract class TvRemoteProvider {
}
}
+ /**
+ * Send a notification that a gamepad key was pressed.
+ *
+ * Supported buttons are:
+ * <ul>
+ * <li> Right-side buttons: BUTTON_A, BUTTON_B, BUTTON_X, BUTTON_Y
+ * <li> Digital Triggers and bumpers: BUTTON_L1, BUTTON_R1, BUTTON_L2, BUTTON_R2
+ * <li> Thumb buttons: BUTTON_THUMBL, BUTTON_THUMBR
+ * <li> DPad buttons: DPAD_UP, DPAD_DOWN, DPAD_LEFT, DPAD_RIGHT
+ * <li> Gamepad buttons: BUTTON_SELECT, BUTTON_START, BUTTON_MODE
+ * <li> Generic buttons: BUTTON_1, BUTTON_2, ...., BUTTON16
+ * <li> Assistant: ASSIST, VOICE_ASSIST
+ * </ul>
+ *
+ * @param token identifier for the device
+ * @param keyCode the gamepad key that was pressed (like BUTTON_A)
+ *
+ * @hide
+ */
+ public void sendGamepadKeyDown(@NonNull IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
+ if (DEBUG_KEYS) {
+ Log.d(TAG, "sendGamepadKeyDown() token: " + token);
+ }
+
+ try {
+ mRemoteServiceInput.sendGamepadKeyDown(token, keyCode);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send a notification that a gamepad key was released.
+ *
+ * @see sendGamepadKeyDown for supported key codes.
+ *
+ * @param token identifier for the device
+ * @param keyCode the gamepad key that was pressed
+ *
+ * @hide
+ */
+ public void sendGamepadKeyUp(@NonNull IBinder token, int keyCode) throws RuntimeException {
+ Objects.requireNonNull(token);
+ if (DEBUG_KEYS) {
+ Log.d(TAG, "sendGamepadKeyUp() token: " + token);
+ }
+
+ try {
+ mRemoteServiceInput.sendGamepadKeyUp(token, keyCode);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Send a gamepad axis value.
+ *
+ * Supported axes:
+ * <li> Left Joystick: AXIS_X, AXIS_Y
+ * <li> Right Joystick: AXIS_Z, AXIS_RZ
+ * <li> Triggers: AXIS_LTRIGGER, AXIS_RTRIGGER
+ * <li> DPad: AXIS_HAT_X, AXIS_HAT_Y
+ *
+ * For non-trigger axes, the range of acceptable values is [-1, 1]. The trigger axes support
+ * values [0, 1].
+ *
+ * @param token identifier for the device
+ * @param axis MotionEvent axis
+ * @param value the value to send
+ *
+ * @hide
+ */
+ public void sendGamepadAxisValue(
+ @NonNull IBinder token, int axis, @FloatRange(from = -1.0f, to = 1.0f) float value)
+ throws RuntimeException {
+ Objects.requireNonNull(token);
+ if (DEBUG_KEYS) {
+ Log.d(TAG, "sendGamepadAxisValue() token: " + token);
+ }
+
+ try {
+ mRemoteServiceInput.sendGamepadAxisValue(token, axis, value);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
private final class ProviderStub extends ITvRemoteProvider.Stub {
@Override
public void setRemoteServiceInputSink(ITvRemoteServiceInput tvServiceInput) {
diff --git a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
index c9ce56138217..e6e39390962e 100644
--- a/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
+++ b/media/lib/tvremote/tests/src/com/android/media/tv/remoteprovider/TvRemoteProviderTest.java
@@ -83,4 +83,52 @@ public class TvRemoteProviderTest extends AndroidTestCase {
assertTrue(tvProvider.verifyTokens());
}
+
+ @SmallTest
+ public void testOpenGamepadRemoteInputBridge() throws Exception {
+ Binder tokenA = new Binder();
+ Binder tokenB = new Binder();
+ Binder tokenC = new Binder();
+
+ class LocalTvRemoteProvider extends TvRemoteProvider {
+ private final ArrayList<IBinder> mTokens = new ArrayList<IBinder>();
+
+ LocalTvRemoteProvider(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onInputBridgeConnected(IBinder token) {
+ mTokens.add(token);
+ }
+
+ public boolean verifyTokens() {
+ return mTokens.size() == 3 && mTokens.contains(tokenA) && mTokens.contains(tokenB)
+ && mTokens.contains(tokenC);
+ }
+ }
+
+ LocalTvRemoteProvider tvProvider = new LocalTvRemoteProvider(getContext());
+ ITvRemoteProvider binder = (ITvRemoteProvider) tvProvider.getBinder();
+
+ ITvRemoteServiceInput tvServiceInput = mock(ITvRemoteServiceInput.class);
+ doAnswer((i) -> {
+ binder.onInputBridgeConnected(i.getArgument(0));
+ return null;
+ })
+ .when(tvServiceInput)
+ .openGamepadBridge(any(), any());
+
+ tvProvider.openGamepadBridge(tokenA, "A");
+ tvProvider.openGamepadBridge(tokenB, "B");
+ binder.setRemoteServiceInputSink(tvServiceInput);
+ tvProvider.openGamepadBridge(tokenC, "C");
+
+ verify(tvServiceInput).openGamepadBridge(tokenA, "A");
+ verify(tvServiceInput).openGamepadBridge(tokenB, "B");
+ verify(tvServiceInput).openGamepadBridge(tokenC, "C");
+ verifyNoMoreInteractions(tvServiceInput);
+
+ assertTrue(tvProvider.verifyTokens());
+ }
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 6ca564fb34cc..6a1e9656cf2c 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -36,6 +36,7 @@ import static com.android.mediaroutertest.StubMediaRoute2ProviderService.VOLUME_
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -47,6 +48,7 @@ import android.media.MediaRouter2Manager;
import android.media.MediaRouter2Utils;
import android.media.RouteDiscoveryPreference;
import android.media.RoutingSessionInfo;
+import android.os.Bundle;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -73,6 +75,8 @@ public class MediaRouter2ManagerTest {
private static final String TAG = "MediaRouter2ManagerTest";
private static final int WAIT_TIME_MS = 2000;
private static final int TIMEOUT_MS = 5000;
+ private static final String TEST_KEY = "test_key";
+ private static final String TEST_VALUE = "test_value";
private Context mContext;
private MediaRouter2Manager mManager;
@@ -160,6 +164,7 @@ public class MediaRouter2ManagerTest {
});
MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
+ assertNotNull(routeToRemove);
StubMediaRoute2ProviderService sInstance =
StubMediaRoute2ProviderService.getInstance();
@@ -171,6 +176,52 @@ public class MediaRouter2ManagerTest {
assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
}
+ @Test
+ public void testGetRoutes_removedRoute_returnsCorrectRoutes() throws Exception {
+ CountDownLatch addedLatch = new CountDownLatch(1);
+ CountDownLatch removedLatch = new CountDownLatch(1);
+
+ RouteCallback routeCallback = new RouteCallback() {
+ // Used to ensure the removed route is added.
+ @Override
+ public void onRoutesAdded(List<MediaRoute2Info> routes) {
+ if (removedLatch.getCount() > 0) {
+ return;
+ }
+ addedLatch.countDown();
+ }
+
+ @Override
+ public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+ removedLatch.countDown();
+ }
+ };
+
+ mRouter2.registerRouteCallback(mExecutor, routeCallback,
+ new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+ mRouteCallbacks.add(routeCallback);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ MediaRoute2Info routeToRemove = routes.get(ROUTE_ID2);
+ assertNotNull(routeToRemove);
+
+ StubMediaRoute2ProviderService sInstance =
+ StubMediaRoute2ProviderService.getInstance();
+ assertNotNull(sInstance);
+ sInstance.removeRoute(ROUTE_ID2);
+
+ // Wait until the route is removed.
+ assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ Map<String, MediaRoute2Info> newRoutes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ assertNull(newRoutes.get(ROUTE_ID2));
+
+ // Revert the removal.
+ sInstance.addRoute(routeToRemove);
+ assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ mRouter2.unregisterRouteCallback(routeCallback);
+ }
+
/**
* Tests if we get proper routes for application that has special route feature.
*/
@@ -465,6 +516,56 @@ public class MediaRouter2ManagerTest {
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
+ @Test
+ public void testRouter2SetOnGetControllerHintsListener() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
+ addRouterCallback(new RouteCallback() {});
+
+ MediaRoute2Info route = routes.get(ROUTE_ID1);
+ assertNotNull(route);
+
+ final Bundle controllerHints = new Bundle();
+ controllerHints.putString(TEST_KEY, TEST_VALUE);
+ final CountDownLatch hintLatch = new CountDownLatch(1);
+ final MediaRouter2.OnGetControllerHintsListener listener =
+ route1 -> {
+ hintLatch.countDown();
+ return controllerHints;
+ };
+
+ final CountDownLatch successLatch = new CountDownLatch(1);
+ final CountDownLatch failureLatch = new CountDownLatch(1);
+
+ addManagerCallback(new MediaRouter2Manager.Callback() {
+ @Override
+ public void onTransferred(RoutingSessionInfo oldSession,
+ RoutingSessionInfo newSession) {
+ assertTrue(newSession.getSelectedRoutes().contains(route.getId()));
+ // The StubMediaRoute2ProviderService is supposed to set control hints
+ // with the given controllerHints.
+ Bundle controlHints = newSession.getControlHints();
+ assertNotNull(controlHints);
+ assertTrue(controlHints.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
+
+ successLatch.countDown();
+ }
+
+ @Override
+ public void onTransferFailed(RoutingSessionInfo session,
+ MediaRoute2Info requestedRoute) {
+ failureLatch.countDown();
+ }
+ });
+
+ mRouter2.setOnGetControllerHintsListener(listener);
+ mManager.selectRoute(mPackageName, route);
+ assertTrue(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertFalse(failureLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+ }
+
Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
throws Exception {
CountDownLatch addedLatch = new CountDownLatch(1);
@@ -475,8 +576,8 @@ public class MediaRouter2ManagerTest {
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
- for (int i = 0; i < routes.size(); i++) {
- if (!routes.get(i).isSystemRoute()) {
+ for (MediaRoute2Info route : routes) {
+ if (!route.isSystemRoute()) {
addedLatch.countDown();
break;
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
index 6d46ba582ddc..4e398f26366a 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/StubMediaRoute2ProviderService.java
@@ -65,9 +65,9 @@ public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
public static final String FEATURE_SAMPLE =
- "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ "com.android.mediaroutertest.FEATURE_SAMPLE";
public static final String FEATURE_SPECIAL =
- "com.android.mediarouteprovider.FEATURE_SPECIAL";
+ "com.android.mediaroutertest..FEATURE_SPECIAL";
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
Map<String, String> mRouteIdToSessionId = new HashMap<>();
diff --git a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
index 7004fb64ba06..8b235e6f246e 100644
--- a/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/CarSystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -29,8 +29,6 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
- androidprv:layout_maxWidth="@dimen/keyguard_security_width"
- androidprv:layout_maxHeight="@dimen/keyguard_security_height"
android:gravity="center">
<include layout="@layout/keyguard_message_area" />
diff --git a/packages/CarSystemUI/res/layout/car_qs_footer.xml b/packages/CarSystemUI/res/layout/car_qs_footer.xml
deleted file mode 100644
index bf96c00e3f0d..000000000000
--- a/packages/CarSystemUI/res/layout/car_qs_footer.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-<?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 RelativeLayout -->
-<com.android.systemui.qs.car.CarQSFooter
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/qs_footer"
- android:layout_width="match_parent"
- android:layout_height="@dimen/car_qs_footer_height"
- android:baselineAligned="false"
- android:clickable="false"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:paddingBottom="@dimen/car_qs_footer_padding_bottom"
- android:paddingTop="@dimen/car_qs_footer_padding_top"
- android:paddingEnd="@dimen/car_qs_footer_padding_end"
- android:paddingStart="@dimen/car_qs_footer_padding_start"
- android:gravity="center_vertical">
-
- <com.android.systemui.statusbar.phone.MultiUserSwitch
- android:id="@+id/multi_user_switch"
- android:layout_alignParentStart="true"
- android:layout_centerVertical="true"
- android:layout_width="@dimen/car_qs_footer_icon_width"
- android:layout_height="@dimen/car_qs_footer_icon_height"
- android:background="?android:attr/selectableItemBackground"
- android:focusable="true">
-
- <ImageView
- android:id="@+id/multi_user_avatar"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- android:scaleType="fitCenter"/>
- </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
- <ImageView
- android:id="@+id/user_switch_expand_icon"
- android:layout_height="match_parent"
- android:layout_width="@dimen/car_qs_footer_user_switch_icon_width"
- android:layout_centerVertical="true"
- android:layout_toEndOf="@+id/multi_user_switch"
- android:layout_marginLeft="@dimen/car_qs_footer_user_switch_icon_margin"
- android:layout_marginRight="@dimen/car_qs_footer_user_switch_icon_margin"
- android:src="@drawable/car_ic_arrow_drop_up"
- android:scaleType="fitCenter">
- </ImageView>
-
- <TextView android:id="@+id/user_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="@dimen/car_qs_footer_user_name_text_size"
- android:textColor="@color/car_qs_footer_user_name_color"
- android:gravity="start|center_vertical"
- android:layout_centerVertical="true"
- android:layout_toEndOf="@id/user_switch_expand_icon" />
-
- <com.android.systemui.statusbar.phone.SettingsButton
- android:id="@+id/settings_button"
- android:layout_alignParentEnd="true"
- android:layout_centerVertical="true"
- android:layout_width="@dimen/car_qs_footer_icon_width"
- android:layout_height="@dimen/car_qs_footer_icon_height"
- android:background="@drawable/ripple_drawable"
- android:contentDescription="@string/accessibility_quick_settings_settings"
- android:scaleType="centerCrop"
- android:src="@drawable/ic_settings_16dp"
- android:tint="?android:attr/colorForeground"
- style="@android:style/Widget.Material.Button.Borderless" />
-
-</com.android.systemui.qs.car.CarQSFooter>
diff --git a/packages/CarSystemUI/res/layout/car_qs_panel.xml b/packages/CarSystemUI/res/layout/car_qs_panel.xml
deleted file mode 100644
index 0c6f322ca261..000000000000
--- a/packages/CarSystemUI/res/layout/car_qs_panel.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?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/quick_settings_container"
- android:clipChildren="false"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/car_qs_background_primary"
- android:orientation="vertical"
- android:elevation="4dp">
-
- <include layout="@layout/car_status_bar_header"/>
- <include layout="@layout/car_qs_footer"/>
-
- <RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/user_switcher_container"
- android:clipChildren="false"
- android:layout_width="match_parent"
- android:layout_height="@dimen/car_user_switcher_container_height">
-
- <com.android.systemui.car.userswitcher.UserGridRecyclerView
- android:id="@+id/user_grid"
- android:layout_width="match_parent"
- android:layout_height="match_parent"/>
-
- </RelativeLayout>
-
-</LinearLayout>
diff --git a/packages/CarSystemUI/res/layout/car_status_bar_header.xml b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
index 81c7108a4cb2..12c9f11b3064 100644
--- a/packages/CarSystemUI/res/layout/car_status_bar_header.xml
+++ b/packages/CarSystemUI/res/layout/car_status_bar_header.xml
@@ -15,7 +15,7 @@
~ limitations under the License
-->
<!-- Extends LinearLayout -->
-<com.android.systemui.qs.car.CarStatusBarHeader
+<com.android.systemui.car.userswitcher.CarStatusBarHeader
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/header"
android:layout_width="match_parent"
@@ -27,4 +27,4 @@
android:layout_height="match_parent"
android:layout_weight="1"
/>
-</com.android.systemui.qs.car.CarStatusBarHeader>
+</com.android.systemui.car.userswitcher.CarStatusBarHeader>
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index ce0d31c6cc47..a7347f22f2e5 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -45,7 +45,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
- <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ <com.android.systemui.car.hvac.AnimatedTemperatureView
android:id="@+id/lefttext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -127,7 +127,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
- <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ <com.android.systemui.car.hvac.AnimatedTemperatureView
android:id="@+id/righttext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
index a71567c48eaf..aa0a8c584896 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -45,7 +45,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
- <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ <com.android.systemui.car.hvac.AnimatedTemperatureView
android:id="@+id/lefttext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -123,7 +123,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
- <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ <com.android.systemui.car.hvac.AnimatedTemperatureView
android:id="@+id/righttext"
android:layout_width="wrap_content"
android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 7972e09869d3..3e44721848a1 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -20,6 +20,7 @@
<color name="car_user_switcher_background_color">#000000</color>
<color name="car_user_switcher_name_text_color">@*android:color/car_body1_light</color>
<color name="car_user_switcher_add_user_background_color">#131313</color>
+ <color name="car_user_switcher_add_user_add_sign_color">@*android:color/car_body1_light</color>
<color name="car_nav_icon_fill_color">#8Fffffff</color>
<color name="car_nav_icon_fill_color_selected">#ffffff</color>
<!-- colors for seekbar -->
diff --git a/packages/CarSystemUI/res/values/colors_car.xml b/packages/CarSystemUI/res/values/colors_car.xml
deleted file mode 100644
index 5f33f8f94a9a..000000000000
--- a/packages/CarSystemUI/res/values/colors_car.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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>
- <color name="car_qs_background_primary">#263238</color> <!-- Blue Gray 900 -->
- <color name="car_qs_footer_user_name_color">@*android:color/car_grey_50</color>
-
- <!-- colors for user switcher -->
- <color name="car_user_switcher_background_color">@*android:color/car_card_dark</color>
- <color name="car_user_switcher_name_text_color">@*android:color/car_body1_light</color>
- <color name="car_user_switcher_add_user_background_color">@*android:color/car_dark_blue_grey_600</color>
- <color name="car_user_switcher_add_user_add_sign_color">@*android:color/car_body1_light</color>
-</resources>
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index e6fb501d02e1..aeff35d88e23 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -86,29 +86,29 @@
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.keyguard.KeyguardViewMediator</item>
- <item>com.android.systemui.recents.Recents</item>
+<!-- <item>com.android.systemui.recents.Recents</item>-->
<item>com.android.systemui.volume.VolumeUI</item>
- <item>com.android.systemui.stackdivider.Divider</item>
+<!-- <item>com.android.systemui.stackdivider.Divider</item>-->
<!-- <item>com.android.systemui.statusbar.phone.StatusBar</item>-->
<item>com.android.systemui.usb.StorageNotification</item>
<item>com.android.systemui.power.PowerUI</item>
<item>com.android.systemui.media.RingtonePlayer</item>
- <item>com.android.systemui.keyboard.KeyboardUI</item>
- <item>com.android.systemui.pip.PipUI</item>
- <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
+<!-- <item>com.android.systemui.keyboard.KeyboardUI</item>-->
+<!-- <item>com.android.systemui.pip.PipUI</item>-->
+<!-- <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>-->
<item>@string/config_systemUIVendorServiceComponent</item>
<item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
- <item>com.android.systemui.LatencyTester</item>
- <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
+<!-- <item>com.android.systemui.LatencyTester</item>-->
+<!-- <item>com.android.systemui.globalactions.GlobalActionsComponent</item>-->
<item>com.android.systemui.ScreenDecorations</item>
<item>com.android.systemui.biometrics.AuthController</item>
- <item>com.android.systemui.SliceBroadcastRelayHandler</item>
+<!-- <item>com.android.systemui.SliceBroadcastRelayHandler</item>-->
<item>com.android.systemui.SizeCompatModeActivityController</item>
- <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
+<!-- <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>-->
<item>com.android.systemui.theme.ThemeOverlayController</item>
<item>com.android.systemui.navigationbar.car.CarNavigationBar</item>
<item>com.android.systemui.toast.ToastUI</item>
- <item>com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier</item>
+ <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item>
<item>com.android.systemui.window.SystemUIOverlayWindowManager</item>
</string-array>
</resources>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index f68d0349e6a0..9014eb15d6cf 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -22,16 +22,11 @@
<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">@*android:dimen/car_primary_icon_size</dimen>
- <!-- dimensions for the car user switcher -->
- <dimen name="car_user_switcher_name_text_size">@dimen/car_body1_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>
@@ -90,9 +85,6 @@
<!-- The width of panel holding the notification card. -->
<dimen name="notification_panel_width">522dp</dimen>
- <!-- The width of the quick settings panel. -1 for match_parent. -->
- <dimen name="qs_panel_width">-1px</dimen>
-
<!-- Height of a small notification in the status bar-->
<dimen name="notification_min_height">192dp</dimen>
@@ -149,7 +141,19 @@
child closer so there is less wasted space. -->
<dimen name="notification_children_container_margin_top">68dp</dimen>
- <!-- The height of the quick settings footer that holds the user switcher, settings icon,
- etc. in the car setting.-->
- <dimen name="qs_footer_height">74dp</dimen>
+ <!-- dimensions for the car user switcher -->
+ <dimen name="car_user_switcher_name_text_size">@*android:dimen/car_body1_size</dimen>
+ <dimen name="car_user_switcher_image_avatar_size">@*android:dimen/car_large_avatar_size</dimen>
+ <dimen name="car_user_switcher_vertical_spacing_between_users">@*android:dimen/car_padding_5</dimen>
+ <dimen name="car_user_switcher_vertical_spacing_between_name_and_avatar">@*android:dimen/car_padding_4</dimen>
+ <dimen name="car_user_switcher_margin_top">@*android:dimen/car_padding_4</dimen>
+
+ <dimen name="car_navigation_button_width">64dp</dimen>
+ <dimen name="car_navigation_bar_width">760dp</dimen>
+ <dimen name="car_left_navigation_bar_width">96dp</dimen>
+ <dimen name="car_right_navigation_bar_width">96dp</dimen>
+
+ <dimen name="car_user_switcher_container_height">420dp</dimen>
+ <!-- This must be the negative of car_user_switcher_container_height for the animation. -->
+ <dimen name="car_user_switcher_container_anim_height">-420dp</dimen>
</resources>
diff --git a/packages/CarSystemUI/res/values/dimens_car.xml b/packages/CarSystemUI/res/values/dimens_car.xml
deleted file mode 100644
index e7ecf7fafc9f..000000000000
--- a/packages/CarSystemUI/res/values/dimens_car.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?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>
- <!-- dimensions for the car user switcher -->
- <dimen name="car_user_switcher_name_text_size">@*android:dimen/car_body1_size</dimen>
- <dimen name="car_user_switcher_image_avatar_size">@*android:dimen/car_large_avatar_size</dimen>
- <dimen name="car_user_switcher_vertical_spacing_between_users">@*android:dimen/car_padding_5</dimen>
- <dimen name="car_user_switcher_vertical_spacing_between_name_and_avatar">@*android:dimen/car_padding_4</dimen>
- <dimen name="car_user_switcher_margin_top">@*android:dimen/car_padding_4</dimen>
-
- <dimen name="car_navigation_button_width">64dp</dimen>
- <dimen name="car_navigation_bar_width">760dp</dimen>
- <dimen name="car_left_navigation_bar_width">96dp</dimen>
- <dimen name="car_right_navigation_bar_width">96dp</dimen>
-
- <dimen name="car_qs_footer_height">112dp</dimen>
- <dimen name="car_qs_footer_padding_bottom">16dp</dimen>
- <dimen name="car_qs_footer_padding_top">16dp</dimen>
- <dimen name="car_qs_footer_padding_end">46dp</dimen>
- <dimen name="car_qs_footer_padding_start">46dp</dimen>
- <dimen name="car_qs_footer_icon_width">56dp</dimen>
- <dimen name="car_qs_footer_icon_height">56dp</dimen>
- <dimen name="car_qs_footer_user_switch_icon_margin">5dp</dimen>
- <dimen name="car_qs_footer_user_switch_icon_width">36dp</dimen>
- <dimen name="car_qs_footer_user_name_text_size">@*android:dimen/car_body2_size</dimen>
-
- <dimen name="car_user_switcher_container_height">420dp</dimen>
- <!-- This must be the negative of car_user_switcher_container_height for the animation. -->
- <dimen name="car_user_switcher_container_anim_height">-420dp</dimen>
-</resources>
diff --git a/packages/CarSystemUI/res/values/ids_car.xml b/packages/CarSystemUI/res/values/ids.xml
index 27ed2e250d9f..27ed2e250d9f 100644
--- a/packages/CarSystemUI/res/values/ids_car.xml
+++ b/packages/CarSystemUI/res/values/ids.xml
diff --git a/packages/CarSystemUI/res/values/integers.xml b/packages/CarSystemUI/res/values/integers.xml
index 8b87c740425f..5ae5555a8092 100644
--- a/packages/CarSystemUI/res/values/integers.xml
+++ b/packages/CarSystemUI/res/values/integers.xml
@@ -16,5 +16,19 @@
-->
<resources>
- <integer name="user_fullscreen_switcher_num_col">2</integer>
+ <!-- Full screen user switcher column number -->
+ <integer name="user_fullscreen_switcher_num_col">3</integer>
+
+ <!--Percentage of the screen height, from the bottom, that a notification panel being
+ partially closed at will result in it remaining open if released-->
+ <integer name="notification_settle_open_percentage">20</integer>
+ <!--Percentage of the screen height, from the bottom, that a notification panel being peeked
+ at will result in remaining closed the panel if released-->
+ <integer name="notification_settle_close_percentage">80</integer>
+
+ <!-- Timeout values in milliseconds for displaying volume dialog-->
+ <integer name="car_volume_dialog_display_normal_timeout">3000</integer>
+ <integer name="car_volume_dialog_display_hovering_timeout">16000</integer>
+ <integer name="car_volume_dialog_display_expanded_normal_timeout">6000</integer>
+ <integer name="car_volume_dialog_display_expanded_hovering_timeout">32000</integer>
</resources>
diff --git a/packages/CarSystemUI/res/values/integers_car.xml b/packages/CarSystemUI/res/values/integers_car.xml
deleted file mode 100644
index db8ce9544705..000000000000
--- a/packages/CarSystemUI/res/values/integers_car.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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">
- <!-- Full screen user switcher column number TODO: move to support library-->
- <integer name="user_fullscreen_switcher_num_col">3</integer>
-
- <!--Percentage of the screen height, from the bottom, that a notification panel being
- partially closed at will result in it remaining open if released-->
- <integer name="notification_settle_open_percentage">20</integer>
- <!--Percentage of the screen height, from the bottom, that a notification panel being peeked
- at will result in remaining closed the panel if released-->
- <integer name="notification_settle_close_percentage">80</integer>
-
- <!-- Timeout values in milliseconds for displaying volume dialog-->
- <integer name="car_volume_dialog_display_normal_timeout">3000</integer>
- <integer name="car_volume_dialog_display_hovering_timeout">16000</integer>
- <integer name="car_volume_dialog_display_expanded_normal_timeout">6000</integer>
- <integer name="car_volume_dialog_display_expanded_hovering_timeout">32000</integer>
-</resources>
diff --git a/packages/CarSystemUI/res/values/strings.xml b/packages/CarSystemUI/res/values/strings.xml
index 9ea7ed027d34..881e746d633d 100644
--- a/packages/CarSystemUI/res/values/strings.xml
+++ b/packages/CarSystemUI/res/values/strings.xml
@@ -22,4 +22,16 @@
<string name="hvac_max_text">Max</string>
<!-- Text for voice recognition toast. [CHAR LIMIT=60] -->
<string name="voice_recognition_toast">Voice recognition now handled by connected Bluetooth device</string>
+ <!-- Name of Guest Profile. [CHAR LIMIT=30] -->
+ <string name="car_guest">Guest</string>
+ <!-- Title for button that starts a guest session. [CHAR LIMIT=30] -->
+ <string name="start_guest_session">Guest</string>
+ <!-- Title for button that adds a new user. [CHAR LIMIT=30] -->
+ <string name="car_add_user">Add User</string>
+ <!-- Default name of the new user created. [CHAR LIMIT=30] -->
+ <string name="car_new_user">New User</string>
+ <!-- Message to inform user that creation of new user requires that user to set up their space. [CHAR LIMIT=100] -->
+ <string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
+ <!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
+ <string name="user_add_user_message_update">Any user can update apps for all other users.</string>
</resources>
diff --git a/packages/CarSystemUI/res/values/strings_car.xml b/packages/CarSystemUI/res/values/strings_car.xml
deleted file mode 100644
index 83e91c57ccc3..000000000000
--- a/packages/CarSystemUI/res/values/strings_car.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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>
- <!-- Name of Guest Profile. [CHAR LIMIT=30] -->
- <string name="car_guest">Guest</string>
- <!-- Title for button that starts a guest session. [CHAR LIMIT=30] -->
- <string name="start_guest_session">Guest</string>
- <!-- Title for button that adds a new user. [CHAR LIMIT=30] -->
- <string name="car_add_user">Add User</string>
- <!-- Default name of the new user created. [CHAR LIMIT=30] -->
- <string name="car_new_user">New User</string>
- <!-- Message to inform user that creation of new user requires that user to set up their space. [CHAR LIMIT=100] -->
- <string name="user_add_user_message_setup">When you add a new user, that person needs to set up their space.</string>
- <!-- Message to inform user that the newly created user will have permissions to update apps for all other users. [CHAR LIMIT=100] -->
- <string name="user_add_user_message_update">Any user can update apps for all other users.</string>
-</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 59fa9d09c9ee..a69c92f0bd05 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -19,6 +19,7 @@ package com.android.systemui;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.car.notification.CarNotificationModule;
+import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -38,7 +39,6 @@ import com.android.systemui.statusbar.tv.TvStatusBar;
import com.android.systemui.theme.ThemeOverlayController;
import com.android.systemui.toast.ToastUI;
import com.android.systemui.util.leak.GarbageMonitor;
-import com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier;
import com.android.systemui.volume.VolumeUI;
import com.android.systemui.window.OverlayWindowModule;
import com.android.systemui.window.SystemUIOverlayWindowManager;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
index 908aaad71893..a7294317f46c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.statusbar.hvac;
+package com.android.systemui.car.hvac;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
@@ -35,7 +35,6 @@ import android.widget.TextSwitcher;
import android.widget.TextView;
import com.android.systemui.R;
-import com.android.systemui.navigationbar.car.hvac.TemperatureView;
/**
* Simple text display of HVAC properties, It is designed to show mTemperature and is configured in
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
index fd9c488278ba..af8ddb6a8180 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java
index 3c6d623c8ff7..a4c45730a9c2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,15 +11,15 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.statusbar.hvac;
+package com.android.systemui.car.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 static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isTop;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isVertical;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java
index a40ffaf850c5..9a7b0b9819c5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,10 +11,10 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.statusbar.hvac;
+package com.android.systemui.car.hvac;
import android.graphics.Color;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java
index 8ee5ef6badc3..74d970464108 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,13 +11,13 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
-package com.android.systemui.statusbar.hvac;
+package com.android.systemui.car.hvac;
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft;
import android.annotation.NonNull;
import android.view.animation.AccelerateDecelerateInterpolator;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java
index ad4fcd9b67da..521a665da5f6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
import android.content.Context;
import android.content.res.TypedArray;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java
index 963f3184c40d..6b903fad505c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
/**
* Interface for Views that display temperature HVAC properties
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 60ee19eec25f..4fde30987e50 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -205,6 +205,9 @@ public class CarKeyguardViewController extends OverlayViewController implements
@Override
public void onCancelClicked() {
+ getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
+ getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
+
mBouncer.hide(/* destroyView= */ true);
mKeyguardCancelClickedListener.onCancelClicked();
}
@@ -226,7 +229,8 @@ public class CarKeyguardViewController extends OverlayViewController implements
@Override
public void setNeedsInput(boolean needsInput) {
- getLayout().setFocusable(needsInput);
+ getOverlayViewGlobalStateController().setWindowFocusable(needsInput);
+ getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput);
}
/**
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
index 53e5d9fe91ec..20fcca0d0220 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
@@ -30,7 +30,6 @@ import com.android.car.notification.R;
import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.car.CarStatusBar;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -43,7 +42,7 @@ import dagger.Lazy;
@Singleton
public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer {
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
- private final Lazy<CarStatusBar> mCarStatusBarLazy;
+ private final Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
private final ViewGroup mWindow;
private final FrameLayout mHeadsUpContentFrame;
@@ -55,10 +54,9 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica
@Main Resources resources,
CarDeviceProvisionedController deviceProvisionedController,
WindowManager windowManager,
- // TODO: Remove dependency on CarStatusBar
- Lazy<CarStatusBar> carStatusBarLazy) {
+ Lazy<NotificationPanelViewController> notificationPanelViewControllerLazy) {
mCarDeviceProvisionedController = deviceProvisionedController;
- mCarStatusBarLazy = carStatusBarLazy;
+ mNotificationPanelViewControllerLazy = notificationPanelViewControllerLazy;
boolean showOnBottom = resources.getBoolean(R.bool.config_showHeadsUpNotificationOnBottom);
@@ -87,7 +85,8 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica
private void animateShow() {
if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen
- || !mCarStatusBarLazy.get().isPanelExpanded()) && isCurrentUserSetup()) {
+ || !mNotificationPanelViewControllerLazy.get().isPanelExpanded())
+ && mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
mWindow.setVisibility(View.VISIBLE);
}
}
@@ -114,9 +113,4 @@ public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotifica
public boolean isVisible() {
return mWindow.getVisibility() == View.VISIBLE;
}
-
- private boolean isCurrentUserSetup() {
- return mCarDeviceProvisionedController.isCurrentUserSetup()
- && !mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
- }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java
index c0dbb5879d7d..f145b148eaf7 100644
--- a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.sideloaded.car;
+package com.android.systemui.car.sideloaded;
import android.annotation.NonNull;
import android.app.ActivityManager;
diff --git a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarStatusBarHeader.java
index 4ef926fae816..bab67154e75d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarStatusBarHeader.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/CarStatusBarHeader.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.qs.car;
+package com.android.systemui.car.userswitcher;
import android.content.Context;
import android.graphics.Color;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
index 58add179886c..2ff667093e58 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/userswitcher/UserGridRecyclerView.java
@@ -368,7 +368,7 @@ public class UserGridRecyclerView extends RecyclerView {
private void applyCarSysUIDialogFlags(AlertDialog dialog) {
final Window window = dialog.getWindow();
- window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ window.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
window.getAttributes().setFitInsetsTypes(
diff --git a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
index 2f79f960f951..c054d204af98 100644
--- a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.voicerecognition.car;
+package com.android.systemui.car.voicerecognition;
import android.bluetooth.BluetoothHeadsetClient;
import android.content.BroadcastReceiver;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 9fdfc0fff307..2c2aec21ea4f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -52,7 +52,6 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -80,7 +79,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
private final Handler mMainHandler;
private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
- private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private final ButtonSelectionStateController mButtonSelectionStateController;
private final PhoneStatusBarPolicy mIconPolicy;
private final StatusBarIconController mIconController;
@@ -127,7 +125,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
@Main Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
Lazy<NavigationBarController> navigationBarControllerLazy,
- SuperStatusBarViewFactory superStatusBarViewFactory,
ButtonSelectionStateController buttonSelectionStateController,
PhoneStatusBarPolicy iconPolicy,
StatusBarIconController iconController
@@ -143,7 +140,6 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
mMainHandler = mainHandler;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
mNavigationBarControllerLazy = navigationBarControllerLazy;
- mSuperStatusBarViewFactory = superStatusBarViewFactory;
mButtonSelectionStateController = buttonSelectionStateController;
mIconPolicy = iconPolicy;
mIconController = iconController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
index 37a82255929a..8f3ae1a25441 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
@@ -24,7 +24,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.systemui.R;
-import com.android.systemui.navigationbar.car.hvac.HvacController;
+import com.android.systemui.car.hvac.HvacController;
import javax.inject.Inject;
import javax.inject.Singleton;
diff --git a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFooter.java b/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
deleted file mode 100644
index b74f1998bf9e..000000000000
--- a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFooter.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.qs.car;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.qs.QSFooter;
-import com.android.systemui.qs.QSPanel;
-import com.android.systemui.statusbar.phone.MultiUserSwitch;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-
-/**
- * The footer view that displays below the status bar in the auto use-case. This view shows the
- * user switcher and access to settings.
- */
-public class CarQSFooter extends RelativeLayout implements QSFooter,
- UserInfoController.OnUserInfoChangedListener {
- private static final String TAG = "CarQSFooter";
-
- private UserInfoController mUserInfoController;
-
- private MultiUserSwitch mMultiUserSwitch;
- private TextView mUserName;
- private ImageView mMultiUserAvatar;
- private CarQSFragment.UserSwitchCallback mUserSwitchCallback;
-
- public CarQSFooter(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mMultiUserSwitch = findViewById(R.id.multi_user_switch);
- mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
- mUserName = findViewById(R.id.user_name);
-
- mUserInfoController = Dependency.get(UserInfoController.class);
-
- mMultiUserSwitch.setOnClickListener(v -> {
- if (mUserSwitchCallback == null) {
- Log.e(TAG, "CarQSFooter not properly set up; cannot display user switcher.");
- return;
- }
-
- if (!mUserSwitchCallback.isShowing()) {
- mUserSwitchCallback.show();
- } else {
- mUserSwitchCallback.hide();
- }
- });
-
- findViewById(R.id.settings_button).setOnClickListener(v -> {
- ActivityStarter activityStarter = Dependency.get(ActivityStarter.class);
-
- if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
- // If user isn't setup just unlock the device and dump them back at SUW.
- activityStarter.postQSRunnableDismissingKeyguard(() -> { });
- return;
- }
-
- activityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
- true /* dismissShade */);
- });
- }
-
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- mMultiUserAvatar.setImageDrawable(picture);
- mUserName.setText(name);
- }
-
- @Override
- public void setQSPanel(@Nullable QSPanel panel) {
- if (panel != null) {
- mMultiUserSwitch.setQsPanel(panel);
- }
- }
-
- public void setUserSwitchCallback(CarQSFragment.UserSwitchCallback callback) {
- mUserSwitchCallback = callback;
- }
-
- @Override
- public void setListening(boolean listening) {
- if (listening) {
- mUserInfoController.addCallback(this);
- } else {
- mUserInfoController.removeCallback(this);
- }
- }
-
- @Override
- public void setExpandClickListener(OnClickListener onClickListener) {
- // No view that should expand/collapse the quick settings.
- }
-
- @Override
- public void setExpanded(boolean expanded) {
- // Do nothing because the quick settings cannot be expanded.
- }
-
- @Override
- public void setExpansion(float expansion) {
- // Do nothing because the quick settings cannot be expanded.
- }
-
- @Override
- public void setKeyguardShowing(boolean keyguardShowing) {
- // Do nothing because the footer will not be shown when the keyguard is up.
- }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java b/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
deleted file mode 100644
index 31965c5fc022..000000000000
--- a/packages/CarSystemUI/src/com/android/systemui/qs/car/CarQSFragment.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * 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.qs.car;
-
-import android.animation.Animator;
-import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.app.Fragment;
-import android.content.Context;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.recyclerview.widget.GridLayoutManager;
-
-import com.android.systemui.R;
-import com.android.systemui.car.userswitcher.UserGridRecyclerView;
-import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.QSFooter;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A quick settings fragment for the car. For auto, there is no row for quick settings or ability
- * to expand the quick settings panel. Instead, the only thing is that displayed is the
- * status bar, and a static row with access to the user switcher and settings.
- */
-public class CarQSFragment extends Fragment implements QS {
- private View mHeader;
- private View mUserSwitcherContainer;
- private CarQSFooter mFooter;
- private View mFooterUserName;
- private View mFooterExpandIcon;
- private UserGridRecyclerView mUserGridView;
- private AnimatorSet mAnimatorSet;
- private UserSwitchCallback mUserSwitchCallback;
-
- @Override
- public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.car_qs_panel, container, false);
- }
-
- @Override
- public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mHeader = view.findViewById(R.id.header);
- mFooter = view.findViewById(R.id.qs_footer);
- mFooterUserName = mFooter.findViewById(R.id.user_name);
- mFooterExpandIcon = mFooter.findViewById(R.id.user_switch_expand_icon);
-
- mUserSwitcherContainer = view.findViewById(R.id.user_switcher_container);
-
- updateUserSwitcherHeight(0);
-
- Context context = getContext();
- mUserGridView = mUserSwitcherContainer.findViewById(R.id.user_grid);
- GridLayoutManager layoutManager = new GridLayoutManager(context,
- context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col));
- mUserGridView.setLayoutManager(layoutManager);
- mUserGridView.buildAdapter();
-
- mUserSwitchCallback = new UserSwitchCallback();
- mFooter.setUserSwitchCallback(mUserSwitchCallback);
- }
-
- @Override
- public void hideImmediately() {
- getView().setVisibility(View.INVISIBLE);
- }
-
- @Override
- public void setQsExpansion(float qsExpansionFraction, float headerTranslation) {
- // If the header is to be completed translated down, then set it to be visible.
- getView().setVisibility(headerTranslation == 0 ? View.VISIBLE : View.INVISIBLE);
- }
-
- @Override
- public View getHeader() {
- return mHeader;
- }
-
- @VisibleForTesting
- QSFooter getFooter() {
- return mFooter;
- }
-
- @Override
- public void setHeaderListening(boolean listening) {
- mFooter.setListening(listening);
- }
-
- @Override
- public void setListening(boolean listening) {
- mFooter.setListening(listening);
- }
-
- @Override
- public int getQsMinExpansionHeight() {
- return getView().getHeight();
- }
-
- @Override
- public int getDesiredHeight() {
- return getView().getHeight();
- }
-
- @Override
- public void setPanelView(HeightListener notificationPanelView) {
- // No quick settings panel.
- }
-
- @Override
- public void setHeightOverride(int desiredHeight) {
- // No ability to expand quick settings.
- }
-
- @Override
- public void setHeaderClickable(boolean qsExpansionEnabled) {
- // Usually this sets the expand button to be clickable, but there is no quick settings to
- // expand.
- }
-
- @Override
- public boolean isCustomizing() {
- // No ability to customize the quick settings.
- return false;
- }
-
- @Override
- public void setOverscrolling(boolean overscrolling) {
- // No overscrolling to reveal quick settings.
- }
-
- @Override
- public void setExpanded(boolean qsExpanded) {
- // No quick settings to expand
- }
-
- @Override
- public boolean isShowingDetail() {
- // No detail panel to close.
- return false;
- }
-
- @Override
- public void closeDetail() {
- // No detail panel to close.
- }
-
- @Override
- public void animateHeaderSlidingIn(long delay) {
- // No header to animate.
- }
-
- @Override
- public void animateHeaderSlidingOut() {
- // No header to animate.
- }
-
- @Override
- public void notifyCustomizeChanged() {
- // There is no ability to customize quick settings.
- }
-
- @Override
- public void setContainer(ViewGroup container) {
- // No quick settings, so no container to set.
- }
-
- @Override
- public void setExpandClickListener(OnClickListener onClickListener) {
- // No ability to expand the quick settings.
- }
-
- public class UserSwitchCallback {
- private boolean mShowing;
-
- public boolean isShowing() {
- return mShowing;
- }
-
- public void show() {
- mShowing = true;
- animateHeightChange(true /* opening */);
- }
-
- public void hide() {
- mShowing = false;
- animateHeightChange(false /* opening */);
- }
- }
-
- private void updateUserSwitcherHeight(int height) {
- ViewGroup.LayoutParams layoutParams = mUserSwitcherContainer.getLayoutParams();
- layoutParams.height = height;
- mUserSwitcherContainer.requestLayout();
- }
-
- private void animateHeightChange(boolean opening) {
- // Animation in progress; cancel it to avoid contention.
- if (mAnimatorSet != null) {
- mAnimatorSet.cancel();
- }
-
- List<Animator> allAnimators = new ArrayList<>();
- ValueAnimator heightAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getContext(),
- opening ? R.anim.car_user_switcher_open_animation
- : R.anim.car_user_switcher_close_animation);
- heightAnimator.addUpdateListener(valueAnimator -> {
- updateUserSwitcherHeight((Integer) valueAnimator.getAnimatedValue());
- });
- allAnimators.add(heightAnimator);
-
- Animator nameAnimator = AnimatorInflater.loadAnimator(getContext(),
- opening ? R.anim.car_user_switcher_open_name_animation
- : R.anim.car_user_switcher_close_name_animation);
- nameAnimator.setTarget(mFooterUserName);
- allAnimators.add(nameAnimator);
-
- Animator iconAnimator = AnimatorInflater.loadAnimator(getContext(),
- opening ? R.anim.car_user_switcher_open_icon_animation
- : R.anim.car_user_switcher_close_icon_animation);
- iconAnimator.setTarget(mFooterExpandIcon);
- allAnimators.add(iconAnimator);
-
- mAnimatorSet = new AnimatorSet();
- mAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimatorSet = null;
- }
- });
- mAnimatorSet.playTogether(allAnimators.toArray(new Animator[0]));
-
- // Setup all values to the start values in the animations, since there are delays, but need
- // to have all values start at the beginning.
- setupInitialValues(mAnimatorSet);
-
- mAnimatorSet.start();
- }
-
- private void setupInitialValues(Animator anim) {
- if (anim instanceof AnimatorSet) {
- for (Animator a : ((AnimatorSet) anim).getChildAnimations()) {
- setupInitialValues(a);
- }
- } else if (anim instanceof ObjectAnimator) {
- ((ObjectAnimator) anim).setCurrentFraction(0.0f);
- }
- }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index d8111d04348b..ec1dabc1bd72 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -54,7 +54,6 @@ import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.qs.car.CarQSFragment;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
@@ -407,7 +406,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt
@Override
protected QS createDefaultQSFragment() {
- return new CarQSFragment();
+ return null;
}
private BatteryController createBatteryController() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
index 402d742cb949..5fe03f17919b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
@@ -123,6 +123,12 @@ public class OverlayViewGlobalStateController {
mSystemUIOverlayWindowController.setWindowFocusable(focusable);
}
+ /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
+ * sysui overlay window */
+ public void setWindowNeedsInput(boolean needsInput) {
+ mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
+ }
+
/** Returns {@code true} if the window is focusable. */
public boolean isWindowFocusable() {
return mSystemUIOverlayWindowController.isWindowFocusable();
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
index 0dbe1a3ea1dd..5df5d6e98f18 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
@@ -90,7 +90,7 @@ public class SystemUIOverlayWindowController implements
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+ WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
@@ -132,6 +132,16 @@ public class SystemUIOverlayWindowController implements
updateWindow();
}
+ /** Sets the window to enable IME. */
+ public void setWindowNeedsInput(boolean needsInput) {
+ if (needsInput) {
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ } else {
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+ }
+ updateWindow();
+ }
+
/** Returns {@code true} if the window is visible */
public boolean isWindowVisible() {
return mVisible;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
index a71d1db3ee70..7996170ba7d6 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
index 05b8e6a54099..6ac72a681bfe 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
@@ -31,7 +31,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
-import com.android.systemui.statusbar.car.CarStatusBar;
import org.junit.Before;
import org.junit.Test;
@@ -48,7 +47,7 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
@Mock
private CarDeviceProvisionedController mCarDeviceProvisionedController;
@Mock
- private CarStatusBar mCarStatusBar;
+ private NotificationPanelViewController mNotificationPanelViewController;
@Mock
private WindowManager mWindowManager;
@@ -61,7 +60,7 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mCarStatusBar.isPanelExpanded()).thenReturn(false);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(false);
@@ -72,14 +71,14 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
mDefaultController = new CarHeadsUpNotificationSystemContainer(mContext,
testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
- () -> mCarStatusBar);
+ () -> mNotificationPanelViewController);
testableResources.addOverride(
R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, true);
mOverrideEnabledController = new CarHeadsUpNotificationSystemContainer(mContext,
testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
- () -> mCarStatusBar);
+ () -> mNotificationPanelViewController);
}
@Test
@@ -120,14 +119,14 @@ public class CarHeadsUpNotificationSystemContainerTest extends SysuiTestCase {
@Test
public void testDisplayNotification_notificationPanelExpanded_isInvisible() {
- when(mCarStatusBar.isPanelExpanded()).thenReturn(true);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
mDefaultController.displayNotification(mNotificationView);
assertThat(mDefaultController.isVisible()).isFalse();
}
@Test
public void testDisplayNotification_notificationPanelExpandedEnabledHUNWhenOpen_isVisible() {
- when(mCarStatusBar.isPanelExpanded()).thenReturn(true);
+ when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
mOverrideEnabledController.displayNotification(mNotificationView);
assertThat(mOverrideEnabledController.isVisible()).isTrue();
}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java
index aebb0e005019..80f3d1ee5dec 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.sideloaded.car;
+package com.android.systemui.car.sideloaded;
import static com.google.common.truth.Truth.assertThat;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
index 38b47d0aea5d..eca51e34995c 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package com.android.systemui.voicerecognition.car;
+package com.android.systemui.car.voicerecognition;
-import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE;
-import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED;
+import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE;
+import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
index bbcd0d4eff81..28c69c776f17 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
@@ -31,7 +31,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.navigationbar.car.hvac.HvacController;
+import com.android.systemui.car.hvac.HvacController;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.phone.StatusBarIconController;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index 3ecb29f95092..6da34d4dddc0 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -38,7 +38,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -75,8 +74,6 @@ public class CarNavigationBarTest extends SysuiTestCase {
@Mock
private NavigationBarController mNavigationBarController;
@Mock
- private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
- @Mock
private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
private PhoneStatusBarPolicy mIconPolicy;
@@ -92,8 +89,7 @@ public class CarNavigationBarTest extends SysuiTestCase {
mCarNavigationBarController, mWindowManager, mDeviceProvisionedController,
new CommandQueue(mContext), mAutoHideController, mButtonSelectionStateListener,
mHandler, () -> mKeyguardStateController, () -> mNavigationBarController,
- mSuperStatusBarViewFactory, mButtonSelectionStateController, mIconPolicy,
- mIconController);
+ mButtonSelectionStateController, mIconPolicy, mIconController);
}
@Test
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index 587109d5dae9..be790106f42d 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -69,6 +69,15 @@ android_app {
// Explicitly uncompress native libs rather than letting the build system doing it and destroy the
// v2/v3 signature.
use_embedded_native_libs: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.apex.cts.shim.v1",
+ "com.android.apex.cts.shim.v2",
+ "com.android.apex.cts.shim.v2_no_hashtree",
+ "com.android.apex.cts.shim.v2_legacy",
+ "com.android.apex.cts.shim.v2_sdk_target_p",
+ "com.android.apex.cts.shim.v3",
+ ],
}
//##########################################################
@@ -110,7 +119,11 @@ android_app {
dex_preopt: {
enabled: false,
},
- manifest: "shim/AndroidManifestTargetPSdk.xml"
+ manifest: "shim/AndroidManifestTargetPSdk.xml",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.apex.cts.shim.v2_apk_in_apex_sdk_target_p",
+ ],
}
//##########################################################
@@ -128,4 +141,13 @@ android_app {
},
manifest: "shim/AndroidManifest.xml",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.apex.cts.shim.v1",
+ "com.android.apex.cts.shim.v2",
+ "com.android.apex.cts.shim.v2_no_hashtree",
+ "com.android.apex.cts.shim.v2_legacy",
+ "com.android.apex.cts.shim.v2_sdk_target_p",
+ "com.android.apex.cts.shim.v3",
+ ],
}
diff --git a/packages/CtsShim/build/jni/Android.bp b/packages/CtsShim/build/jni/Android.bp
index ea15b43416b4..7a5b07e61e9d 100644
--- a/packages/CtsShim/build/jni/Android.bp
+++ b/packages/CtsShim/build/jni/Android.bp
@@ -18,4 +18,13 @@ cc_library_shared {
name: "libshim_jni",
srcs: ["Shim.c"],
sdk_version: "24",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.apex.cts.shim.v1",
+ "com.android.apex.cts.shim.v2",
+ "com.android.apex.cts.shim.v2_no_hashtree",
+ "com.android.apex.cts.shim.v2_legacy",
+ "com.android.apex.cts.shim.v2_sdk_target_p",
+ "com.android.apex.cts.shim.v3",
+ ],
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
index e551b69e024a..ee8fb38ef08c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java
@@ -38,7 +38,7 @@ public class BluetoothMediaDevice extends MediaDevice {
BluetoothMediaDevice(Context context, CachedBluetoothDevice device,
MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) {
- super(context, MediaDeviceType.TYPE_BLUETOOTH_DEVICE, routerManager, info, packageName);
+ super(context, routerManager, info, packageName);
mCachedDevice = device;
initDeviceRecord();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
index 85fa988a866e..83a96716e284 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaDevice.java
@@ -38,7 +38,7 @@ public class InfoMediaDevice extends MediaDevice {
InfoMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
String packageName) {
- super(context, MediaDeviceType.TYPE_CAST_DEVICE, routerManager, info, packageName);
+ super(context, routerManager, info, packageName);
initDeviceRecord();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
index 39e6a129a992..6aff301f57d4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java
@@ -15,6 +15,16 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_GROUP;
+import static android.media.MediaRoute2Info.TYPE_HEARING_AID;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_TV;
+import static android.media.MediaRoute2Info.TYPE_UNKNOWN;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
+
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
@@ -38,13 +48,21 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
private static final String TAG = "MediaDevice";
@Retention(RetentionPolicy.SOURCE)
- @IntDef({MediaDeviceType.TYPE_CAST_DEVICE,
+ @IntDef({MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE,
+ MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE,
+ MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE,
MediaDeviceType.TYPE_BLUETOOTH_DEVICE,
+ MediaDeviceType.TYPE_CAST_DEVICE,
+ MediaDeviceType.TYPE_CAST_GROUP_DEVICE,
MediaDeviceType.TYPE_PHONE_DEVICE})
public @interface MediaDeviceType {
- int TYPE_PHONE_DEVICE = 1;
- int TYPE_CAST_DEVICE = 2;
- int TYPE_BLUETOOTH_DEVICE = 3;
+ int TYPE_USB_C_AUDIO_DEVICE = 1;
+ int TYPE_3POINT5_MM_AUDIO_DEVICE = 2;
+ int TYPE_FAST_PAIR_BLUETOOTH_DEVICE = 3;
+ int TYPE_BLUETOOTH_DEVICE = 4;
+ int TYPE_CAST_DEVICE = 5;
+ int TYPE_CAST_GROUP_DEVICE = 6;
+ int TYPE_PHONE_DEVICE = 7;
}
@VisibleForTesting
@@ -58,13 +76,43 @@ public abstract class MediaDevice implements Comparable<MediaDevice> {
protected final MediaRouter2Manager mRouterManager;
protected final String mPackageName;
- MediaDevice(Context context, @MediaDeviceType int type, MediaRouter2Manager routerManager,
- MediaRoute2Info info, String packageName) {
- mType = type;
+ MediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
+ String packageName) {
mContext = context;
mRouteInfo = info;
mRouterManager = routerManager;
mPackageName = packageName;
+ setType(info);
+ }
+
+ private void setType(MediaRoute2Info info) {
+ if (info == null) {
+ mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+ return;
+ }
+
+ switch (info.getType()) {
+ case TYPE_GROUP:
+ mType = MediaDeviceType.TYPE_CAST_GROUP_DEVICE;
+ break;
+ case TYPE_BUILTIN_SPEAKER:
+ mType = MediaDeviceType.TYPE_PHONE_DEVICE;
+ break;
+ case TYPE_WIRED_HEADSET:
+ case TYPE_WIRED_HEADPHONES:
+ mType = MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE;
+ break;
+ case TYPE_HEARING_AID:
+ case TYPE_BLUETOOTH_A2DP:
+ mType = MediaDeviceType.TYPE_BLUETOOTH_DEVICE;
+ break;
+ case TYPE_UNKNOWN:
+ case TYPE_REMOTE_TV:
+ case TYPE_REMOTE_SPEAKER:
+ default:
+ mType = MediaDeviceType.TYPE_CAST_DEVICE;
+ break;
+ }
}
void initDeviceRecord() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index af88723c6249..c6c5ade90eb5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -42,7 +42,7 @@ public class PhoneMediaDevice extends MediaDevice {
PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info,
String packageName) {
- super(context, MediaDeviceType.TYPE_PHONE_DEVICE, routerManager, info, packageName);
+ super(context, routerManager, info, packageName);
initDeviceRecord();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 653c8addba98..deccde593315 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -33,6 +33,7 @@ import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -395,8 +396,12 @@ public class EnableZenModeDialog {
button1.setAlpha(button1.isEnabled() ? 1f : .5f);
button2.setAlpha(button2.isEnabled() ? 1f : .5f);
} else {
- button1.setVisibility(View.GONE);
- button2.setVisibility(View.GONE);
+ if (button1 != null) {
+ ((ViewGroup) row).removeView(button1);
+ }
+ if (button2 != null) {
+ ((ViewGroup) row).removeView(button2);
+ }
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index c713d7813a54..d7e76a14c768 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -133,6 +133,35 @@ public class WifiStatusTracker {
}
}
+ /**
+ * Fetches initial state as if a WifiManager.NETWORK_STATE_CHANGED_ACTION have been received.
+ * This replaces the dependency on the initial sticky broadcast.
+ */
+ public void fetchInitialState() {
+ if (mWifiManager == null) {
+ return;
+ }
+ updateWifiState();
+ final NetworkInfo networkInfo =
+ mConnectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ connected = networkInfo != null && networkInfo.isConnected();
+ mWifiInfo = null;
+ ssid = null;
+ if (connected) {
+ mWifiInfo = mWifiManager.getConnectionInfo();
+ if (mWifiInfo != null) {
+ if (mWifiInfo.isPasspointAp() || mWifiInfo.isOsuAp()) {
+ ssid = mWifiInfo.getPasspointProviderFriendlyName();
+ } else {
+ ssid = getValidSsid(mWifiInfo);
+ }
+ updateRssi(mWifiInfo.getRssi());
+ maybeRequestNetworkScore();
+ }
+ }
+ updateStatusLabel();
+ }
+
public void handleBroadcast(Intent intent) {
if (mWifiManager == null) {
return;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
index 4b08387275be..db05b768f5db 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java
@@ -15,6 +15,10 @@
*/
package com.android.settingslib.media;
+import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -144,12 +148,19 @@ public class MediaDeviceTest {
when(mCachedDevice2.isConnected()).thenReturn(true);
when(mCachedDevice3.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(mCachedDevice3.isConnected()).thenReturn(true);
+ when(mBluetoothRouteInfo1.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+ when(mBluetoothRouteInfo2.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
+ when(mBluetoothRouteInfo3.getType()).thenReturn(TYPE_BLUETOOTH_A2DP);
when(mRouteInfo1.getId()).thenReturn(ROUTER_ID_1);
when(mRouteInfo2.getId()).thenReturn(ROUTER_ID_2);
when(mRouteInfo3.getId()).thenReturn(ROUTER_ID_3);
when(mRouteInfo1.getName()).thenReturn(DEVICE_NAME_1);
when(mRouteInfo2.getName()).thenReturn(DEVICE_NAME_2);
when(mRouteInfo3.getName()).thenReturn(DEVICE_NAME_3);
+ when(mRouteInfo1.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(mRouteInfo2.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(mRouteInfo3.getType()).thenReturn(TYPE_REMOTE_SPEAKER);
+ when(mPhoneRouteInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile);
when(mProfileManager.getHearingAidProfile()).thenReturn(mHapProfile);
@@ -271,12 +282,12 @@ public class MediaDeviceTest {
@Test
public void compareTo_info_bluetooth_infoFirst() {
- mMediaDevices.add(mBluetoothMediaDevice1);
mMediaDevices.add(mInfoMediaDevice1);
+ mMediaDevices.add(mBluetoothMediaDevice1);
- assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
- Collections.sort(mMediaDevices, COMPARATOR);
assertThat(mMediaDevices.get(0)).isEqualTo(mInfoMediaDevice1);
+ Collections.sort(mMediaDevices, COMPARATOR);
+ assertThat(mMediaDevices.get(0)).isEqualTo(mBluetoothMediaDevice1);
}
@Test
@@ -327,7 +338,7 @@ public class MediaDeviceTest {
// 5.mBluetoothMediaDevice2: * 2 times usage
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
- // Order: 7 -> 2 -> 1 -> 3 -> 5 -> 4 -> 6
+ // Order: 7 -> 2 -> 1 -> 5 -> 3 -> 6 -> 4
@Test
public void compareTo_mixedDevices_carKitFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -352,10 +363,10 @@ public class MediaDeviceTest {
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(1)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice1);
- assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice2);
- assertThat(mMediaDevices.get(4)).isEqualTo(mBluetoothMediaDevice2);
- assertThat(mMediaDevices.get(5)).isEqualTo(mInfoMediaDevice3);
- assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice2);
+ assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice2);
+ assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(6)).isEqualTo(mInfoMediaDevice3);
}
// 1.mInfoMediaDevice1: Last Selected device
@@ -365,7 +376,7 @@ public class MediaDeviceTest {
// 5.mBluetoothMediaDevice2: * 4 times usage not connected
// 6.mBluetoothMediaDevice3: * 1 time usage
// 7.mPhoneMediaDevice: * 0 time usage
- // Order: 7 -> 1 -> 3 -> 4 -> 6 -> 2 -> 5
+ // Order: 7 -> 1 -> 3 -> 6 -> 4 -> 2 -> 5
@Test
public void compareTo_mixedDevices_connectDeviceFirst() {
when(mDevice1.getBluetoothClass()).thenReturn(mCarkitClass);
@@ -394,8 +405,8 @@ public class MediaDeviceTest {
assertThat(mMediaDevices.get(0)).isEqualTo(mPhoneMediaDevice);
assertThat(mMediaDevices.get(1)).isEqualTo(mInfoMediaDevice1);
assertThat(mMediaDevices.get(2)).isEqualTo(mInfoMediaDevice2);
- assertThat(mMediaDevices.get(3)).isEqualTo(mInfoMediaDevice3);
- assertThat(mMediaDevices.get(4)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(3)).isEqualTo(mBluetoothMediaDevice3);
+ assertThat(mMediaDevices.get(4)).isEqualTo(mInfoMediaDevice3);
assertThat(mMediaDevices.get(5)).isEqualTo(mBluetoothMediaDevice1);
assertThat(mMediaDevices.get(6)).isEqualTo(mBluetoothMediaDevice2);
}
diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
new file mode 100644
index 000000000000..a793680a037d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_pressed="true">
+ <ripple android:color="#99999999" />
+ </item>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml
new file mode 100644
index 000000000000..129282dae77f
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/rounded_bg_full"
+ android:elevation="@dimen/bubble_manage_menu_elevation"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/bubble_manage_menu_dismiss_container"
+ android:background="@drawable/bubble_manage_menu_row"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_remove_no_shadow"
+ android:tint="@color/global_actions_text"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
+ android:text="@string/bubble_dismiss_text" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/bubble_manage_menu_dont_bubble_container"
+ android:background="@drawable/bubble_manage_menu_row"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/ic_stop_bubble"
+ android:tint="@color/global_actions_text"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
+ android:text="@string/bubbles_dont_bubble_conversation" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/bubble_manage_menu_settings_container"
+ android:background="@drawable/bubble_manage_menu_row"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/bubble_manage_menu_settings_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:src="@drawable/ic_remove_no_shadow"/>
+
+ <TextView
+ android:id="@+id/bubble_manage_menu_settings_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="16dp"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+ </LinearLayout>
+
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index ae57563cfb09..6da96d10c253 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -17,6 +17,7 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/controls_management_root"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 599ed1696ec9..179f8b8ea9f4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1014,6 +1014,9 @@
<!-- Margins at the left and right of the power menu and home controls widgets. -->
<dimen name="global_actions_side_margin">16dp</dimen>
+ <!-- Amount to shift the layout when exiting/entering for controls activities -->
+ <dimen name="global_actions_controls_y_translation">20dp</dimen>
+
<!-- The maximum offset in either direction that elements are moved horizontally to prevent
burn-in on AOD. -->
<dimen name="burn_in_prevention_offset_x">8dp</dimen>
@@ -1197,6 +1200,7 @@
snap to the dismiss target. -->
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+ <dimen name="bubble_manage_menu_elevation">4dp</dimen>
<dimen name="dismiss_circle_size">52dp</dimen>
<dimen name="dismiss_target_x_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cb20e7a15424..d639ed074240 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2629,6 +2629,8 @@
<string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
<!-- Button text for dismissing the bubble "manage" button tool tip [CHAR LIMIT=20]-->
<string name="bubbles_user_education_got_it">Got it</string>
+ <!-- Label for the button that takes the user to the notification settings for the given app. -->
+ <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
<!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
<string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 7e24f5dbbd50..4ed819e4925b 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -663,8 +663,13 @@
<!-- Controls styles -->
<style name="Theme.ControlsManagement" parent="@android:style/Theme.DeviceDefault.NoActionBar">
+ <item name="android:windowActivityTransitions">true</item>
+ <item name="android:windowContentTransitions">false</item>
<item name="android:windowIsTranslucent">false</item>
- <item name="wallpaperTextColor">@*android:color/primary_text_material_dark</item>
+ <item name="android:windowBackground">@android:color/black</item>
+ <item name="android:colorBackground">@android:color/black</item>
+ <item name="android:windowAnimationStyle">@null</item>
+ <item name="android:statusBarColor">@*android:color/transparent</item>
</style>
<style name="TextAppearance.Control">
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index aa2fe3c7f8fc..57b3761c294f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -31,6 +30,8 @@ import android.util.Log;
import android.view.KeyEvent;
import android.widget.FrameLayout;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -101,7 +102,8 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback {
public static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardViewBase";
- private KeyguardSecurityContainer mSecurityContainer;
+ @VisibleForTesting
+ protected KeyguardSecurityContainer mSecurityContainer;
public KeyguardHostView(Context context) {
this(context, null);
@@ -446,4 +448,11 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback {
public SecurityMode getCurrentSecurityMode() {
return mSecurityContainer.getCurrentSecurityMode();
}
+
+ /**
+ * When bouncer was visible and is starting to become hidden.
+ */
+ public void onStartingToHide() {
+ mSecurityContainer.onStartingToHide();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 718bcf16c832..65bf7e6e5025 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -152,6 +152,11 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
mImm.hideSoftInputFromWindow(getWindowToken(), 0);
}
+ @Override
+ public void onStartingToHide() {
+ mImm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+
private void updateSwitchImeButton() {
// If there's more than one IME, enable the IME switcher button
final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 502c0787fe38..9cfcc52134ce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -227,6 +227,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
@Override
+ public void onStartingToHide() {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ getSecurityView(mCurrentSecuritySelection).onStartingToHide();
+ }
+ }
+
+ @Override
public boolean shouldDelayChildPressedState() {
return true;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 20b1e0d2c822..43cef3acf147 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -159,4 +159,9 @@ public interface KeyguardSecurityView {
default boolean disallowInterceptTouch(MotionEvent event) {
return false;
}
+
+ /**
+ * When bouncer was visible but is being dragged down or dismissed.
+ */
+ default void onStartingToHide() {};
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 367058fa58dd..a96ef91850df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1301,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private FingerprintManager mFpm;
private FaceManager mFaceManager;
private boolean mFingerprintLockedOut;
+ private TelephonyManager mTelephonyManager;
/**
* When we receive a
@@ -1728,10 +1729,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
updateAirplaneModeState();
- TelephonyManager telephony =
+ mTelephonyManager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephony != null) {
- telephony.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ if (mTelephonyManager != null) {
+ mTelephonyManager.listen(mPhoneStateListener,
+ LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ // Set initial sim states values.
+ for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
+ int state = mTelephonyManager.getSimState(slot);
+ int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
+ if (subIds != null) {
+ for (int subId : subIds) {
+ mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
+ .sendToTarget();
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 409ae3f3c7d6..c92174a0d8af 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -164,7 +164,9 @@ public class PasswordTextView extends View {
currentDrawPosition = getPaddingLeft();
}
} else {
- currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+ float maxRight = getWidth() - getPaddingRight() - totalDrawingWidth;
+ float center = getWidth() / 2f - totalDrawingWidth / 2f;
+ currentDrawPosition = center > 0 ? center : maxRight;
}
int length = mTextChars.size();
Rect bounds = getCharBounds();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7262f8caac89..1f27ae238533 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -16,6 +16,8 @@
package com.android.systemui.accessibility;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+
import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -282,8 +284,8 @@ public class SystemActions extends SystemUI {
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true, true, new Handler(Looper.getMainLooper()), null);
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
private void handleAccessibilityMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 71f2bc09b983..38bfffbb75d9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -90,6 +90,7 @@ class Bubble implements BubbleViewProvider {
}
private FlyoutMessage mFlyoutMessage;
+ private Drawable mBadgedAppIcon;
private Bitmap mBadgedImage;
private int mDotColor;
private Path mDotPath;
@@ -133,6 +134,10 @@ class Bubble implements BubbleViewProvider {
return mBadgedImage;
}
+ public Drawable getBadgedAppIcon() {
+ return mBadgedAppIcon;
+ }
+
@Override
public int getDotColor() {
return mDotColor;
@@ -239,6 +244,7 @@ class Bubble implements BubbleViewProvider {
mAppName = info.appName;
mFlyoutMessage = info.flyoutMessage;
+ mBadgedAppIcon = info.badgedAppIcon;
mBadgedImage = info.badgedBubbleImage;
mDotColor = info.dotColor;
mDotPath = info.dotPath;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index c6883c89bd21..e488cf271fdf 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -605,6 +605,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
+
+ mStackView.setUnbubbleConversationCallback(notificationEntry ->
+ onUserChangedBubble(notificationEntry, false /* shouldBubble */));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 3524696dbc79..bb2365559f74 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -43,7 +43,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -55,14 +54,13 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
-import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.AlphaOptimizedButton;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
*/
-public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
+public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
private enum ActivityViewStatus {
@@ -100,9 +98,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
private int mPointerWidth;
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
- private Rect mTempRect = new Rect();
- private int[] mTempLoc = new int[2];
- private int mExpandedViewTouchSlop;
@Nullable private Bubble mBubble;
@@ -224,7 +219,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
- mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
}
@Override
@@ -239,7 +233,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
-
mPointerDrawable = new ShapeDrawable(TriangleShape.create(
mPointerWidth, mPointerHeight, true /* pointUp */));
mPointerView.setBackground(mPointerDrawable);
@@ -248,7 +241,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
- mSettingsIcon.setOnClickListener(this);
mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
true /* singleTaskInstance */);
@@ -289,6 +281,19 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
return mBubble != null ? mBubble.getEntry() : null;
}
+ void setManageClickListener(OnClickListener manageClickListener) {
+ findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+ }
+
+ /**
+ * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which
+ * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful
+ * if a view has been added or removed from on top of the ActivityView, such as the manage menu.
+ */
+ void updateObscuredTouchableRegion() {
+ mActivityView.onLocationChanged();
+ }
+
void applyThemeAttrs() {
final TypedArray ta = mContext.obtainStyledAttributes(
new int[] {
@@ -473,51 +478,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
/**
- * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
- * expanded view.
- *
- * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
- */
- boolean intersectingTouchableContent(int rawX, int rawY) {
- mTempRect.setEmpty();
- if (mActivityView != null) {
- mTempLoc = mActivityView.getLocationOnScreen();
- mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
- mTempLoc[1] - mExpandedViewTouchSlop,
- mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
- mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
- }
- if (mTempRect.contains(rawX, rawY)) {
- return true;
- }
- mTempLoc = mSettingsIcon.getLocationOnScreen();
- mTempRect.set(mTempLoc[0],
- mTempLoc[1],
- mTempLoc[0] + mSettingsIcon.getWidth(),
- mTempLoc[1] + mSettingsIcon.getHeight());
- if (mTempRect.contains(rawX, rawY)) {
- return true;
- }
- return false;
- }
-
- @Override
- public void onClick(View view) {
- if (mBubble == null) {
- return;
- }
- int id = view.getId();
- if (id == R.id.settings_button) {
- Intent intent = mBubble.getSettingsIntent();
- mStackView.collapseStack(() -> {
- mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser());
- logBubbleClickEvent(mBubble,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
- });
- }
- }
-
- /**
* Update appearance of the expanded view being displayed.
*/
public void updateView() {
@@ -547,10 +507,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* Position of the manage button displayed in the expanded view. Used for placing user
* education about the manage button.
*/
- public Rect getManageButtonLocationOnScreen() {
- mTempLoc = mSettingsIcon.getLocationOnScreen();
- return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
- mTempLoc[1] + mSettingsIcon.getHeight());
+ public void getManageButtonBoundsOnScreen(Rect rect) {
+ mSettingsIcon.getBoundsOnScreen(rect);
}
/**
@@ -611,26 +569,4 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
return INVALID_DISPLAY;
}
-
- /**
- * Logs bubble UI click event.
- *
- * @param bubble the bubble notification entry that user is interacting with.
- * @param action the user interaction enum.
- */
- private void logBubbleClickEvent(Bubble bubble, int action) {
- StatusBarNotification notification = bubble.getEntry().getSbn();
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- notification.getPackageName(),
- notification.getNotification().getChannelId(),
- notification.getId(),
- mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
- mStackView.getBubbleCount(),
- action,
- mStackView.getNormalizedXPosition(),
- mStackView.getNormalizedYPosition(),
- bubble.showInShade(),
- bubble.isOngoing(),
- false /* isAppForeground (unused) */);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0aabdff96e1e..c9069316028c 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,12 +33,14 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
@@ -47,6 +49,7 @@ import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
+import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
@@ -55,6 +58,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -62,6 +66,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.MainThread;
@@ -83,6 +88,7 @@ import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
@@ -97,6 +103,7 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
@@ -224,7 +231,7 @@ public class BubbleStackView extends FrameLayout {
private int mPointerHeight;
private int mStatusBarHeight;
private int mImeOffset;
- private BubbleViewProvider mExpandedBubble;
+ @Nullable private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded;
/** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -244,6 +251,10 @@ public class BubbleStackView extends FrameLayout {
}
private BubbleController.BubbleExpandListener mExpandListener;
+
+ /** Callback to run when we want to unbubble the given notification's conversation. */
+ private Consumer<NotificationEntry> mUnbubbleConversationCallback;
+
private SysUiState mSysUiState;
private boolean mViewUpdatedRequested = false;
@@ -255,9 +266,7 @@ public class BubbleStackView extends FrameLayout {
private LayoutInflater mInflater;
- // Used for determining view / touch intersection
- int[] mTempLoc = new int[2];
- RectF mTempRect = new RectF();
+ private Rect mTempRect = new Rect();
private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
@@ -471,6 +480,11 @@ public class BubbleStackView extends FrameLayout {
return true;
}
+ // If the manage menu is visible, just hide it.
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ }
+
if (mBubbleData.isExpanded()) {
maybeShowManageEducation(false /* show */);
@@ -627,6 +641,13 @@ public class BubbleStackView extends FrameLayout {
private BubbleManageEducationView mManageEducationView;
private boolean mAnimatingManageEducationAway;
+ private ViewGroup mManageMenu;
+ private ImageView mManageSettingsIcon;
+ private TextView mManageSettingsText;
+ private boolean mShowingManage = false;
+ private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+
@SuppressLint("ClickableViewAccessibility")
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
@@ -689,6 +710,8 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
+ setUpManageMenu();
+
setUpFlyout();
mFlyoutTransitionSpring.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_LOW)
@@ -838,7 +861,9 @@ public class BubbleStackView extends FrameLayout {
// ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
setOnTouchListener((view, ev) -> {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mBubbleData.isExpanded()) {
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ } else if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
}
}
@@ -847,6 +872,66 @@ public class BubbleStackView extends FrameLayout {
});
}
+ private void setUpManageMenu() {
+ if (mManageMenu != null) {
+ removeView(mManageMenu);
+ }
+
+ mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate(
+ R.layout.bubble_manage_menu, this, false);
+ mManageMenu.setVisibility(View.INVISIBLE);
+
+ PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.dialogCornerRadius});
+ final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
+ ta.recycle();
+
+ mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
+ }
+ });
+ mManageMenu.setClipToOutline(true);
+
+ mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ dismissBubbleIfExists(mBubbleData.getSelectedBubble());
+ });
+
+ mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ final Bubble bubble = mBubbleData.getSelectedBubble();
+ if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+ mUnbubbleConversationCallback.accept(bubble.getEntry());
+ }
+ });
+
+ mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ final Bubble bubble = mBubbleData.getSelectedBubble();
+ if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+ final Intent intent = bubble.getSettingsIntent();
+ collapseStack(() -> {
+ mContext.startActivityAsUser(
+ intent, bubble.getEntry().getSbn().getUser());
+ logBubbleClickEvent(
+ bubble,
+ SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+ });
+ }
+ });
+
+ mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
+ mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
+ addView(mManageMenu);
+ }
+
private void setUpUserEducation() {
if (mUserEducationView != null) {
removeView(mUserEducationView);
@@ -934,6 +1019,7 @@ public class BubbleStackView extends FrameLayout {
setUpFlyout();
setUpOverflow();
setUpUserEducation();
+ setUpManageMenu();
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -960,6 +1046,9 @@ public class BubbleStackView extends FrameLayout {
Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
+
+ mManageMenu.setVisibility(View.INVISIBLE);
+ mShowingManage = false;
}
@Override
@@ -1100,6 +1189,12 @@ public class BubbleStackView extends FrameLayout {
mExpandListener = listener;
}
+ /** Sets the function to call to un-bubble the given conversation. */
+ public void setUnbubbleConversationCallback(
+ Consumer<NotificationEntry> unbubbleConversationCallback) {
+ mUnbubbleConversationCallback = unbubbleConversationCallback;
+ }
+
/**
* Whether the stack of bubbles is expanded or not.
*/
@@ -1361,15 +1456,14 @@ public class BubbleStackView extends FrameLayout {
mManageEducationView.setAlpha(0);
mManageEducationView.setVisibility(VISIBLE);
mManageEducationView.post(() -> {
- final Rect position =
- mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
+ mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
final int viewHeight = mManageEducationView.getManageViewHeight();
final int inset = getResources().getDimensionPixelSize(
R.dimen.bubbles_manage_education_top_inset);
mManageEducationView.bringToFront();
- mManageEducationView.setManageViewPosition(position.left,
- position.top - viewHeight + inset);
- mManageEducationView.setPointerPosition(position.centerX() - position.left);
+ mManageEducationView.setManageViewPosition(mTempRect.left,
+ mTempRect.top - viewHeight + inset);
+ mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left);
mManageEducationView.animate()
.setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
.setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
@@ -1443,6 +1537,9 @@ public class BubbleStackView extends FrameLayout {
}
private void animateCollapse() {
+ // Hide the menu if it's visible.
+ showManageMenu(false);
+
mIsExpanded = false;
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
@@ -1570,9 +1667,9 @@ public class BubbleStackView extends FrameLayout {
*/
@Override
public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
- // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch
- // events from any location.
- if (mNotificationShadeWindowController.getPanelExpanded()) {
+ // If the notification shade is expanded, or the manage menu is open, we shouldn't let the
+ // ActivityView steal any touch events from any location.
+ if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) {
touchableRegion.setEmpty();
}
}
@@ -1658,17 +1755,20 @@ public class BubbleStackView extends FrameLayout {
private void dismissMagnetizedObject() {
if (mIsExpanded) {
final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
- final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
+ dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
- if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
- mBubbleData.notificationEntryRemoved(
- draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
- }
} else {
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
}
}
+ private void dismissBubbleIfExists(@Nullable Bubble bubble) {
+ if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+ mBubbleData.notificationEntryRemoved(
+ bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ }
+ }
+
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
@@ -1912,6 +2012,63 @@ public class BubbleStackView extends FrameLayout {
invalidate();
}
+ private void showManageMenu(boolean show) {
+ mShowingManage = show;
+
+ // This should not happen, since the manage menu is only visible when there's an expanded
+ // bubble. If we end up in this state, just hide the menu immediately.
+ if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+ mManageMenu.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ // If available, update the manage menu's settings option with the expanded bubble's app
+ // name and icon.
+ if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) {
+ final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey());
+ mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
+ mManageSettingsText.setText(getResources().getString(
+ R.string.bubbles_app_settings, bubble.getAppName()));
+ }
+
+ mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
+
+ // When the menu is open, it should be at these coordinates. This will make the menu's
+ // bottom left corner match up with the button's bottom left corner.
+ final float targetX = mTempRect.left;
+ final float targetY = mTempRect.bottom - mManageMenu.getHeight();
+
+ if (show) {
+ mManageMenu.setScaleX(0.5f);
+ mManageMenu.setScaleY(0.5f);
+ mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4);
+ mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4);
+ mManageMenu.setAlpha(0f);
+
+ PhysicsAnimator.getInstance(mManageMenu)
+ .spring(DynamicAnimation.ALPHA, 1f)
+ .spring(DynamicAnimation.SCALE_X, 1f)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ .spring(DynamicAnimation.TRANSLATION_X, targetX)
+ .spring(DynamicAnimation.TRANSLATION_Y, targetY)
+ .start();
+
+ mManageMenu.setVisibility(View.VISIBLE);
+ } else {
+ PhysicsAnimator.getInstance(mManageMenu)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .spring(DynamicAnimation.SCALE_X, 0.5f)
+ .spring(DynamicAnimation.SCALE_Y, 0.5f)
+ .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4)
+ .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4)
+ .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+ .start();
+ }
+
+ // Update the AV's obscured touchable region for the new menu visibility state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
+
private void updateExpandedBubble() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
@@ -1921,6 +2078,7 @@ public class BubbleStackView extends FrameLayout {
&& mExpandedBubble.getExpandedView() != null) {
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
mExpandedViewContainer.addView(bev);
+ bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
bev.populateExpandedView();
mExpandedViewContainer.setVisibility(VISIBLE);
mExpandedViewContainer.setAlpha(1.0f);
@@ -2089,4 +2247,26 @@ public class BubbleStackView extends FrameLayout {
}
return bubbles;
}
+
+ /**
+ * Logs bubble UI click event.
+ *
+ * @param bubble the bubble notification entry that user is interacting with.
+ * @param action the user interaction enum.
+ */
+ private void logBubbleClickEvent(Bubble bubble, int action) {
+ StatusBarNotification notification = bubble.getEntry().getSbn();
+ SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+ notification.getPackageName(),
+ notification.getNotification().getChannelId(),
+ notification.getId(),
+ getBubbleIndex(getExpandedBubble()),
+ getBubbleCount(),
+ action,
+ getNormalizedXPosition(),
+ getNormalizedYPosition(),
+ bubble.showInShade(),
+ bubble.isOngoing(),
+ false /* isAppForeground (unused) */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index c96f9a470ca4..8a57a735f6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -116,6 +116,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
ShortcutInfo shortcutInfo;
String appName;
Bitmap badgedBubbleImage;
+ Drawable badgedAppIcon;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
@@ -176,6 +177,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
+ info.badgedAppIcon = badgedIcon;
info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
badgeBitmapInfo).icon;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index ef84c73b3145..ca3e2e27fa9a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -20,15 +20,17 @@ import android.graphics.Bitmap;
import android.graphics.Path;
import android.view.View;
+import androidx.annotation.Nullable;
+
/**
* Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
*/
interface BubbleViewProvider {
- BubbleExpandedView getExpandedView();
+ @Nullable BubbleExpandedView getExpandedView();
void setContentVisibility(boolean visible);
- View getIconView();
+ @Nullable View getIconView();
void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 7e8fec716b1f..e84f439c1fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
@@ -284,13 +285,19 @@ open class ControlsBindingControllerImpl @Inject constructor(
val requestLimit: Long
) : IControlsSubscriber.Stub() {
val loadedControls = ArrayList<Control>()
- private var isTerminated = false
+ private var isTerminated = AtomicBoolean(false)
private var _loadCancelInternal: (() -> Unit)? = null
private lateinit var subscription: IControlsSubscription
+ /**
+ * Potentially cancel a subscriber. The subscriber may also have terminated, in which case
+ * the request is ignored.
+ */
fun loadCancel() = Runnable {
- Log.d(TAG, "Cancel load requested")
- _loadCancelInternal?.invoke()
+ _loadCancelInternal?.let {
+ Log.d(TAG, "Canceling loadSubscribtion")
+ it.invoke()
+ }
}
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
@@ -301,7 +308,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
override fun onNext(token: IBinder, c: Control) {
backgroundExecutor.execute {
- if (isTerminated) return@execute
+ if (isTerminated.get()) return@execute
loadedControls.add(c)
@@ -325,13 +332,15 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
- if (isTerminated) return
+ if (isTerminated.get()) return
- isTerminated = true
_loadCancelInternal = {}
currentProvider?.cancelLoadTimeout()
- backgroundExecutor.execute(postTerminateFn)
+ backgroundExecutor.execute {
+ isTerminated.compareAndSet(false, true)
+ postTerminateFn.run()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 79a5b0a1ce52..bc97c10756fd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -52,19 +52,17 @@ interface ControlsController : UserAwareController {
* Load all available [Control] for a given service.
*
* @param componentName the [ComponentName] of the [ControlsProviderService] to load from
- * @param dataCallback a callback in which to retrieve the result.
+ * @param dataCallback a callback in which to retrieve the result
+ * @param cancelWrapper a callback to receive a [Runnable] that can be run to cancel the
+ * request
*/
fun loadForComponent(
componentName: ComponentName,
- dataCallback: Consumer<LoadData>
+ dataCallback: Consumer<LoadData>,
+ cancelWrapper: Consumer<Runnable>
)
/**
- * Cancels a pending load call
- */
- fun cancelLoad()
-
- /**
* Request to subscribe for favorited controls per structure
*
* @param structureInfo structure to limit the subscription to
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 5626a5de2e3c..8e88756b16fd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -77,8 +77,6 @@ class ControlsControllerImpl @Inject constructor (
private var userChanging: Boolean = true
- private var loadCanceller: Runnable? = null
-
private var seedingInProgress = false
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
@@ -276,28 +274,29 @@ class ControlsControllerImpl @Inject constructor (
override fun loadForComponent(
componentName: ComponentName,
- dataCallback: Consumer<ControlsController.LoadData>
+ dataCallback: Consumer<ControlsController.LoadData>,
+ cancelWrapper: Consumer<Runnable>
) {
if (!confirmAvailability()) {
if (userChanging) {
// Try again later, userChanging should not last forever. If so, we have bigger
// problems. This will return a runnable that allows to cancel the delayed version,
// it will not be able to cancel the load if
- loadCanceller = executor.executeDelayed(
- { loadForComponent(componentName, dataCallback) },
- USER_CHANGE_RETRY_DELAY,
- TimeUnit.MILLISECONDS
+ executor.executeDelayed(
+ { loadForComponent(componentName, dataCallback, cancelWrapper) },
+ USER_CHANGE_RETRY_DELAY,
+ TimeUnit.MILLISECONDS
)
- } else {
- dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
}
- return
+
+ dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
}
- loadCanceller = bindingController.bindAndLoad(
+
+ cancelWrapper.accept(
+ bindingController.bindAndLoad(
componentName,
object : ControlsBindingController.LoadCallback {
override fun accept(controls: List<Control>) {
- loadCanceller = null
executor.execute {
val favoritesForComponentKeys = Favorites
.getControlsForComponent(componentName).map { it.controlId }
@@ -333,7 +332,6 @@ class ControlsControllerImpl @Inject constructor (
}
override fun error(message: String) {
- loadCanceller = null
executor.execute {
val controls = Favorites.getStructuresForComponent(componentName)
.flatMap { st ->
@@ -348,6 +346,7 @@ class ControlsControllerImpl @Inject constructor (
}
}
}
+ )
)
}
@@ -432,12 +431,6 @@ class ControlsControllerImpl @Inject constructor (
seedingCallbacks.clear()
}
- override fun cancelLoad() {
- loadCanceller?.let {
- executor.execute(it)
- }
- }
-
private fun createRemovedStatus(
componentName: ComponentName,
controlInfo: ControlInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
new file mode 100644
index 000000000000..4ca47d1d41ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.annotation.IdRes
+import android.content.Intent
+
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+
+import com.android.systemui.Interpolators
+import com.android.systemui.R
+
+import com.android.systemui.controls.ui.ControlsUiController
+
+object ControlsAnimations {
+
+ private const val ALPHA_EXIT_DURATION = 167L
+ private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION
+ private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY
+
+ private const val Y_TRANSLATION_EXIT_DURATION = 183L
+ private const val Y_TRANSLATION_ENTER_DELAY = Y_TRANSLATION_EXIT_DURATION - ALPHA_ENTER_DELAY
+ private const val Y_TRANSLATION_ENTER_DURATION = 400L - Y_TRANSLATION_EXIT_DURATION
+ private var translationY: Float = -1f
+
+ /**
+ * Setup an activity to handle enter/exit animations. [view] should be the root of the content.
+ * Fade and translate together.
+ */
+ fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+ return object : LifecycleObserver {
+ var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
+
+ init {
+ // Must flag the parent group to move it all together, and set the initial
+ // transitionAlpha to 0.0f. This property is reserved for fade animations.
+ view.setTransitionGroup(true)
+ view.transitionAlpha = 0.0f
+
+ if (translationY == -1f) {
+ translationY = view.context.resources.getDimensionPixelSize(
+ R.dimen.global_actions_controls_y_translation).toFloat()
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ fun setup() {
+ with(window) {
+ allowEnterTransitionOverlap = true
+ enterTransition = enterWindowTransition(view.getId())
+ exitTransition = exitWindowTransition(view.getId())
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ fun enterAnimation() {
+ if (showAnimation) {
+ ControlsAnimations.enterAnimation(view).start()
+ showAnimation = false
+ }
+ }
+ }
+ }
+
+ fun enterAnimation(view: View): Animator {
+ Log.d(ControlsUiController.TAG, "Enter animation for $view")
+
+ view.transitionAlpha = 0.0f
+ view.alpha = 1.0f
+
+ view.translationY = translationY
+
+ val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f, 1.0f).apply {
+ interpolator = Interpolators.DECELERATE_QUINT
+ startDelay = ALPHA_ENTER_DELAY
+ duration = ALPHA_ENTER_DURATION
+ }
+
+ val yAnimator = ObjectAnimator.ofFloat(view, "translationY", 0.0f).apply {
+ interpolator = Interpolators.DECELERATE_QUINT
+ startDelay = Y_TRANSLATION_ENTER_DURATION
+ duration = Y_TRANSLATION_ENTER_DURATION
+ }
+
+ return AnimatorSet().apply {
+ playTogether(alphaAnimator, yAnimator)
+ }
+ }
+
+ /**
+ * Properly handle animations originating from dialogs. Activity transitions require
+ * transitioning between two activities, so expose this method for dialogs to animate
+ * on exit.
+ */
+ @JvmStatic
+ fun exitAnimation(view: View, onEnd: Runnable? = null): Animator {
+ Log.d(ControlsUiController.TAG, "Exit animation for $view")
+
+ val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f).apply {
+ interpolator = Interpolators.ACCELERATE
+ duration = ALPHA_EXIT_DURATION
+ }
+
+ view.translationY = 0.0f
+ val yAnimator = ObjectAnimator.ofFloat(view, "translationY", -translationY).apply {
+ interpolator = Interpolators.ACCELERATE
+ duration = Y_TRANSLATION_EXIT_DURATION
+ }
+
+ return AnimatorSet().apply {
+ playTogether(alphaAnimator, yAnimator)
+ onEnd?.let {
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ it.run()
+ }
+ })
+ }
+ }
+ }
+
+ fun enterWindowTransition(@IdRes id: Int) =
+ WindowTransition({ view: View -> enterAnimation(view) }).apply {
+ addTarget(id)
+ }
+
+ fun exitWindowTransition(@IdRes id: Int) =
+ WindowTransition({ view: View -> exitAnimation(view) }).apply {
+ addTarget(id)
+ }
+}
+
+/**
+ * In order to animate, at least one property must be marked on each view that should move.
+ * Setting "item" is just a flag to indicate that it should move by the animator.
+ */
+class WindowTransition(
+ val animator: (view: View) -> Animator
+) : Transition() {
+ override fun captureStartValues(tv: TransitionValues) {
+ tv.values["item"] = 0.0f
+ }
+
+ override fun captureEndValues(tv: TransitionValues) {
+ tv.values["item"] = 1.0f
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? = animator(startValues!!.view)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index ee1ce7ab3d83..640c90d2ba59 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -16,11 +16,12 @@
package com.android.systemui.controls.management
-import android.app.Activity
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import android.view.View
+import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
@@ -31,7 +32,9 @@ import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
@@ -39,8 +42,9 @@ import javax.inject.Inject
*/
class ControlsEditingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
- broadcastDispatcher: BroadcastDispatcher
-) : Activity() {
+ broadcastDispatcher: BroadcastDispatcher,
+ private val globalActionsComponent: GlobalActionsComponent
+) : LifecycleActivity() {
companion object {
private const val TAG = "ControlsEditingActivity"
@@ -84,14 +88,31 @@ class ControlsEditingActivity @Inject constructor(
bindViews()
bindButtons()
+ }
+ override fun onStart() {
+ super.onStart()
setUpList()
currentUserTracker.startTracking()
}
+ override fun onStop() {
+ super.onStop()
+ currentUserTracker.stopTracking()
+ }
+
private fun bindViews() {
setContentView(R.layout.controls_management)
+
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.controls_management_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_editing
inflate()
@@ -113,17 +134,26 @@ class ControlsEditingActivity @Inject constructor(
putExtras(this@ControlsEditingActivity.intent)
putExtra(ControlsFavoritingActivity.EXTRA_SINGLE_STRUCTURE, true)
}
- startActivity(intent)
- finish()
+ startActivity(intent, ActivityOptions
+ .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle())
}
}
+ val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
saveButton = requireViewById<Button>(R.id.done).apply {
isEnabled = false
setText(R.string.save)
setOnClickListener {
saveFavorites()
- finishAffinity()
+ ControlsAnimations.exitAnimation(
+ rootView,
+ object : Runnable {
+ override fun run() {
+ finish()
+ }
+ }
+ ).start()
+ globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}
@@ -151,26 +181,38 @@ class ControlsEditingActivity @Inject constructor(
val controls = controller.getFavoritesForStructure(component, structure)
model = FavoritesModel(component, controls, favoritesModelCallback)
val elevation = resources.getFloat(R.dimen.control_card_elevation)
- val adapter = ControlAdapter(elevation)
- val recycler = requireViewById<RecyclerView>(R.id.list)
+ val recyclerView = requireViewById<RecyclerView>(R.id.list)
+ recyclerView.alpha = 0.0f
+ val adapter = ControlAdapter(elevation).apply {
+ registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+ override fun onChanged() {
+ if (!hasAnimated) {
+ hasAnimated = true
+ ControlsAnimations.enterAnimation(recyclerView).start()
+ }
+ }
+ })
+ }
+
val margin = resources
.getDimensionPixelSize(R.dimen.controls_card_margin)
val itemDecorator = MarginItemDecorator(margin, margin)
- recycler.apply {
+ recyclerView.apply {
this.adapter = adapter
- layoutManager = GridLayoutManager(recycler.context, 2).apply {
+ layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
spanSizeLookup = adapter.spanSizeLookup
}
addItemDecoration(itemDecorator)
}
adapter.changeModel(model)
model.attachAdapter(adapter)
- ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recycler)
+ ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView)
}
override fun onDestroy() {
currentUserTracker.stopTracking()
super.onDestroy()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 6f34deeb8547..183bd7bb2b7a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -16,7 +16,7 @@
package com.android.systemui.controls.management
-import android.app.Activity
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.content.res.Configuration
@@ -40,7 +40,9 @@ import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.util.LifecycleActivity
import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -50,8 +52,9 @@ class ControlsFavoritingActivity @Inject constructor(
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
- broadcastDispatcher: BroadcastDispatcher
-) : Activity() {
+ broadcastDispatcher: BroadcastDispatcher,
+ private val globalActionsComponent: GlobalActionsComponent
+) : LifecycleActivity() {
companion object {
private const val TAG = "ControlsFavoritingActivity"
@@ -81,6 +84,8 @@ class ControlsFavoritingActivity @Inject constructor(
private var listOfStructures = emptyList<StructureContainer>()
private lateinit var comparator: Comparator<StructureContainer>
+ private var cancelLoadRunnable: Runnable? = null
+ private var isPagerLoaded = false
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -115,6 +120,7 @@ class ControlsFavoritingActivity @Inject constructor(
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
val collator = Collator.getInstance(resources.configuration.locales[0])
comparator = compareBy(collator) { it.structureName }
appName = intent.getCharSequenceExtra(EXTRA_APP)
@@ -122,14 +128,6 @@ class ControlsFavoritingActivity @Inject constructor(
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
bindViews()
-
- setUpPager()
-
- loadControls()
-
- listingController.addCallback(listingCallback)
-
- currentUserTracker.startTracking()
}
private val controlsModelCallback = object : ControlsModel.ControlsModelCallback {
@@ -174,12 +172,17 @@ class ControlsFavoritingActivity @Inject constructor(
pageIndicator.setLocation(0f)
pageIndicator.visibility =
if (listOfStructures.size > 1) View.VISIBLE else View.GONE
+
+ ControlsAnimations.enterAnimation(pageIndicator).start()
+ ControlsAnimations.enterAnimation(structurePager).start()
}
- })
+ }, Consumer { runnable -> cancelLoadRunnable = runnable })
}
}
private fun setUpPager() {
+ structurePager.alpha = 0.0f
+ pageIndicator.alpha = 0.0f
structurePager.apply {
adapter = StructureAdapter(emptyList())
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
@@ -203,6 +206,15 @@ class ControlsFavoritingActivity @Inject constructor(
private fun bindViews() {
setContentView(R.layout.controls_management)
+
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.controls_management_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_favorites
inflate()
@@ -278,10 +290,12 @@ class ControlsFavoritingActivity @Inject constructor(
val i = Intent()
i.setComponent(
ComponentName(context, ControlsProviderSelectorActivity::class.java))
- context.startActivity(i)
+ startActivity(i, ActivityOptions
+ .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle())
}
}
+ val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
doneButton = requireViewById<Button>(R.id.done).apply {
isEnabled = false
setOnClickListener {
@@ -292,7 +306,16 @@ class ControlsFavoritingActivity @Inject constructor(
StructureInfo(component!!, it.structureName, favoritesForStorage)
)
}
- finishAffinity()
+
+ ControlsAnimations.exitAnimation(
+ rootView,
+ object : Runnable {
+ override fun run() {
+ finish()
+ }
+ }
+ ).start()
+ globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}
@@ -302,15 +325,39 @@ class ControlsFavoritingActivity @Inject constructor(
mTooltipManager?.hide(false)
}
+ override fun onStart() {
+ super.onStart()
+
+ listingController.addCallback(listingCallback)
+ currentUserTracker.startTracking()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ // only do once, to make sure that any user changes do not get replaces if resume is called
+ // more than once
+ if (!isPagerLoaded) {
+ setUpPager()
+ loadControls()
+ isPagerLoaded = true
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ listingController.removeCallback(listingCallback)
+ currentUserTracker.stopTracking()
+ }
+
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mTooltipManager?.hide(false)
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
- listingController.removeCallback(listingCallback)
- controller.cancelLoad()
+ cancelLoadRunnable?.run()
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 3be59009f531..80cb96803f24 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -16,15 +16,18 @@
package com.android.systemui.controls.management
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
@@ -66,12 +69,37 @@ class ControlsProviderSelectorActivity @Inject constructor(
super.onCreate(savedInstanceState)
setContentView(R.layout.controls_management)
+
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.controls_management_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_apps
inflate()
}
recyclerView = requireViewById(R.id.list)
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ requireViewById<TextView>(R.id.title).apply {
+ text = resources.getText(R.string.controls_providers_title)
+ }
+
+ requireViewById<Button>(R.id.done).setOnClickListener {
+ this@ControlsProviderSelectorActivity.finishAffinity()
+ }
+ }
+
+ override fun onStart() {
+ super.onStart()
+ currentUserTracker.startTracking()
+
+ recyclerView.alpha = 0.0f
recyclerView.adapter = AppAdapter(
backExecutor,
executor,
@@ -80,17 +108,22 @@ class ControlsProviderSelectorActivity @Inject constructor(
LayoutInflater.from(this),
::launchFavoritingActivity,
FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
- resources)
- recyclerView.layoutManager = LinearLayoutManager(applicationContext)
-
- requireViewById<TextView>(R.id.title).text =
- resources.getText(R.string.controls_providers_title)
-
- requireViewById<Button>(R.id.done).setOnClickListener {
- this@ControlsProviderSelectorActivity.finishAffinity()
+ resources).apply {
+ registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+ override fun onChanged() {
+ if (!hasAnimated) {
+ hasAnimated = true
+ ControlsAnimations.enterAnimation(recyclerView).start()
+ }
+ }
+ })
}
+ }
- currentUserTracker.startTracking()
+ override fun onStop() {
+ super.onStop()
+ currentUserTracker.stopTracking()
}
/**
@@ -98,7 +131,7 @@ class ControlsProviderSelectorActivity @Inject constructor(
* @param component a component name for a [ControlsProviderService]
*/
fun launchFavoritingActivity(component: ComponentName?) {
- backExecutor.execute {
+ executor.execute {
component?.let {
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
.apply {
@@ -107,7 +140,7 @@ class ControlsProviderSelectorActivity @Inject constructor(
putExtra(Intent.EXTRA_COMPONENT_NAME, it)
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
- startActivity(intent)
+ startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 61a1a986c091..aed7cd316bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -26,9 +26,10 @@ interface ControlsUiController {
companion object {
public const val TAG = "ControlsUiController"
+ public const val EXTRA_ANIMATE = "extra_animate"
}
- fun show(parent: ViewGroup)
+ fun show(parent: ViewGroup, dismissGlobalActions: Runnable)
fun hide()
fun onRefreshState(componentName: ComponentName, controls: List<Control>)
fun onActionResponse(
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 2adfb1bd6d4f..cfd8df059567 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -85,7 +85,7 @@ class ControlsUiControllerImpl @Inject constructor (
private const val PREF_STRUCTURE = "controls_structure"
private const val USE_PANELS = "systemui.controls_use_panel"
- private const val FADE_IN_MILLIS = 225L
+ private const val FADE_IN_MILLIS = 200L
private val EMPTY_COMPONENT = ComponentName("", "")
private val EMPTY_STRUCTURE = StructureInfo(
@@ -104,6 +104,7 @@ class ControlsUiControllerImpl @Inject constructor (
private var popup: ListPopupWindow? = null
private var activeDialog: Dialog? = null
private var hidden = true
+ private lateinit var dismissGlobalActions: Runnable
override val available: Boolean
get() = controlsController.get().available
@@ -134,9 +135,10 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
- override fun show(parent: ViewGroup) {
+ override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) {
Log.d(ControlsUiController.TAG, "show()")
this.parent = parent
+ this.dismissGlobalActions = dismissGlobalActions
hidden = false
allStructures = controlsController.get().getFavorites()
@@ -169,7 +171,7 @@ class ControlsUiControllerImpl @Inject constructor (
fadeAnim.setDuration(FADE_IN_MILLIS)
fadeAnim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- show(parent)
+ show(parent, dismissGlobalActions)
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
showAnim.setDuration(FADE_IN_MILLIS)
@@ -256,9 +258,10 @@ class ControlsUiControllerImpl @Inject constructor (
}
private fun startActivity(context: Context, intent: Intent) {
- val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
- context.sendBroadcast(closeDialog)
+ // Force animations when transitioning from a dialog to an activity
+ intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
context.startActivity(intent)
+ dismissGlobalActions.run()
}
private fun showControlsView(items: List<SelectionItem>) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index a7c40435b3ca..8f3dc224384b 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -19,8 +19,10 @@ package com.android.systemui.dagger;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -128,6 +130,12 @@ public class SystemServicesModule {
return ActivityManager.getService();
}
+ @Singleton
+ @Provides
+ static IActivityTaskManager provideIActivityTaskManager() {
+ return ActivityTaskManager.getService();
+ }
+
@Provides
@Singleton
static IBatteryStats provideIBatteryStats() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index ea358c74a0e0..a24fede36e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -16,6 +16,8 @@ package com.android.systemui.globalactions;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
@@ -110,6 +112,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.management.ControlsAnimations;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.dagger.qualifiers.Background;
@@ -126,7 +129,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils;
-import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
@@ -479,7 +481,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
*/
@VisibleForTesting
protected int getMaxShownPowerItems() {
- if (shouldUseControlsLayout()) {
+ // TODO: Overflow disabled on keyguard while we solve for touch blocking issues.
+ if (shouldUseControlsLayout() && !mKeyguardShowing) {
return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
} else {
return Integer.MAX_VALUE;
@@ -829,7 +832,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
}
@@ -1815,7 +1819,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
case MESSAGE_DISMISS:
if (mDialog != null) {
if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
- mDialog.dismissImmediately();
+ mDialog.completeDismiss();
} else {
mDialog.dismiss();
}
@@ -1894,6 +1898,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private ControlsUiController mControlsUiController;
private ViewGroup mControlsView;
+ private ViewGroup mContainer;
ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
GlobalActionsPanelPlugin.PanelViewController plugin,
@@ -2046,6 +2051,11 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
});
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
+ mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
+ // Some legacy dialog layouts don't have the outer container
+ if (mContainer == null) {
+ mContainer = mGlobalActionsLayout;
+ }
mOverflowPopup = createPowerOverflowPopup();
@@ -2172,10 +2182,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
mNotificationShadeWindowController.setForceHasTopUi(true);
mBackgroundDrawable.setAlpha(0);
- mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
- mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
- mGlobalActionsLayout.setAlpha(0);
- mGlobalActionsLayout.animate()
+ mContainer.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
+ mContainer.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
+ mContainer.setAlpha(0);
+ mContainer.animate()
.alpha(1)
.translationX(0)
.translationY(0)
@@ -2200,48 +2210,55 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return WindowInsets.CONSUMED;
});
if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
}
}
@Override
public void dismiss() {
+ dismissWithAnimation(() -> {
+ mContainer.setTranslationX(0);
+ mContainer.setTranslationY(0);
+ mContainer.setAlpha(1);
+ mContainer.animate()
+ .alpha(0)
+ .translationX(mGlobalActionsLayout.getAnimationOffsetX())
+ .translationY(mGlobalActionsLayout.getAnimationOffsetY())
+ .setDuration(450)
+ .withEndAction(this::completeDismiss)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener(animation -> {
+ float animatedValue = 1f - animation.getAnimatedFraction();
+ int alpha = (int) (animatedValue * mScrimAlpha * 255);
+ mBackgroundDrawable.setAlpha(alpha);
+ mDepthController.updateGlobalDialogVisibility(animatedValue,
+ mGlobalActionsLayout);
+ })
+ .start();
+ });
+ }
+
+ private void dismissForControlsActivity() {
+ dismissWithAnimation(() -> {
+ ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent();
+ ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
+ });
+ }
+
+ void dismissWithAnimation(Runnable animation) {
if (!mShowing) {
return;
}
mShowing = false;
- if (mControlsUiController != null) mControlsUiController.hide();
- mGlobalActionsLayout.setTranslationX(0);
- mGlobalActionsLayout.setTranslationY(0);
- mGlobalActionsLayout.setAlpha(1);
- mGlobalActionsLayout.animate()
- .alpha(0)
- .translationX(mGlobalActionsLayout.getAnimationOffsetX())
- .translationY(mGlobalActionsLayout.getAnimationOffsetY())
- .setDuration(550)
- .withEndAction(this::completeDismiss)
- .setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(animation -> {
- float animatedValue = 1f - animation.getAnimatedFraction();
- int alpha = (int) (animatedValue * mScrimAlpha * 255);
- mBackgroundDrawable.setAlpha(alpha);
- mDepthController.updateGlobalDialogVisibility(animatedValue,
- mGlobalActionsLayout);
- })
- .start();
- dismissPanel();
- resetOrientation();
+ animation.run();
}
- void dismissImmediately() {
+ private void completeDismiss() {
mShowing = false;
- if (mControlsUiController != null) mControlsUiController.hide();
- dismissPanel();
resetOrientation();
- completeDismiss();
- }
-
- private void completeDismiss() {
+ dismissPanel();
+ dismissOverflow();
+ if (mControlsUiController != null) mControlsUiController.hide();
mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
mDepthController.updateGlobalDialogVisibility(0, null /* view */);
super.dismiss();
@@ -2253,6 +2270,12 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
+ private void dismissOverflow() {
+ if (mOverflowPopup != null) {
+ mOverflowPopup.dismiss();
+ }
+ }
+
private void setRotationSuggestionsEnabled(boolean enabled) {
try {
final int userId = Binder.getCallingUserHandle().getIdentifier();
@@ -2296,7 +2319,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
initializeLayout();
mGlobalActionsLayout.updateList();
if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
}
}
@@ -2335,10 +2358,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
&& mControlsUiController.getAvailable()
&& !mControlsServiceInfos.isEmpty();
}
-
// TODO: Remove legacy layout XML and classes.
protected boolean shouldUseControlsLayout() {
// always use new controls layout
return true;
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 14e3e9390825..123cf78d74f8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -61,6 +61,18 @@ public class LogModule {
return buffer;
}
+ /** Provides a logging buffer for all logs related to the data layer of notifications. */
+ @Provides
+ @Singleton
+ @NotifInteractionLog
+ public static LogBuffer provideNotifInteractionLogBuffer(
+ LogcatEchoTracker echoTracker,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Provides a logging buffer for all logs related to Quick Settings. */
@Provides
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
new file mode 100644
index 000000000000..20fc6ff445a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for messages related to the user interacting with notifications (e.g.
+ * clicking on them).
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotifInteractionLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 9217eb161a87..233d24b17d44 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -21,20 +21,25 @@ import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
+import android.media.MediaDescription;
import android.media.MediaMetadata;
+import android.media.ThumbnailUtils;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.service.media.MediaBrowserService;
import android.util.Log;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -55,8 +60,10 @@ import com.android.settingslib.widget.AdaptiveIcon;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.QSMediaBrowser;
import com.android.systemui.util.Assert;
+import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
@@ -67,7 +74,7 @@ public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
@Nullable private final LocalMediaManager mLocalMediaManager;
private final Executor mForegroundExecutor;
- private final Executor mBackgroundExecutor;
+ protected final Executor mBackgroundExecutor;
private Context mContext;
protected LinearLayout mMediaNotifView;
@@ -76,13 +83,18 @@ public class MediaControlPanel {
private MediaController mController;
private int mForegroundColor;
private int mBackgroundColor;
- protected ComponentName mRecvComponent;
private MediaDevice mDevice;
+ protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
private String mKey;
private final int[] mActionIds;
+ public static final String MEDIA_PREFERENCES = "media_control_prefs";
+ public static final String MEDIA_PREFERENCE_KEY = "browser_components";
+ private SharedPreferences mSharedPrefs;
+ private boolean mCheckedForResumption = false;
+
// Button IDs used in notifications
protected static final int[] NOTIF_ACTION_IDS = {
com.android.internal.R.id.action0,
@@ -92,6 +104,13 @@ public class MediaControlPanel {
com.android.internal.R.id.action4
};
+ // URI fields to try loading album art from
+ private static final String[] ART_URIS = {
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ };
+
private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
@Override
public void onSessionDestroyed() {
@@ -154,7 +173,6 @@ public class MediaControlPanel {
* Initialize a new control panel
* @param context
* @param parent
- * @param manager
* @param routeManager Manager used to listen for device change events.
* @param layoutId layout resource to use for this control panel
* @param actionIds resource IDs for action buttons in the layout
@@ -198,47 +216,52 @@ public class MediaControlPanel {
/**
* Update the media panel view for the given media session
* @param token
- * @param icon
+ * @param iconDrawable
+ * @param largeIcon
* @param iconColor
* @param bgColor
* @param contentIntent
* @param appNameString
* @param key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
- int bgColor, PendingIntent contentIntent, String appNameString, String key) {
- mToken = token;
+ public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
+ int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
+ String key) {
+ // Ensure that component names are updated if token has changed
+ if (mToken == null || !mToken.equals(token)) {
+ mToken = token;
+ mServiceComponent = null;
+ mCheckedForResumption = false;
+ }
+
mForegroundColor = iconColor;
mBackgroundColor = bgColor;
mController = new MediaController(mContext, mToken);
mKey = key;
- MediaMetadata mediaMetadata = mController.getMetadata();
-
- // Try to find a receiver for the media button that matches this app
- PackageManager pm = mContext.getPackageManager();
- Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
- List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
- if (info != null) {
- for (ResolveInfo inf : info) {
- if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
- mRecvComponent = inf.getComponentInfo().getComponentName();
+ // Try to find a browser service component for this app
+ // TODO also check for a media button receiver intended for restarting (b/154127084)
+ // Only check if we haven't tried yet or the session token changed
+ String pkgName = mController.getPackageName();
+ if (mServiceComponent == null && !mCheckedForResumption) {
+ Log.d(TAG, "Checking for service component");
+ PackageManager pm = mContext.getPackageManager();
+ Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
+ List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
+ if (resumeInfo != null) {
+ for (ResolveInfo inf : resumeInfo) {
+ if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
+ mBackgroundExecutor.execute(() ->
+ tryUpdateResumptionList(inf.getComponentInfo().getComponentName()));
+ break;
+ }
}
}
+ mCheckedForResumption = true;
}
mController.registerCallback(mSessionCallback);
- if (mediaMetadata == null) {
- Log.e(TAG, "Media metadata was null");
- return;
- }
-
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumView != null) {
- // Resize art in a background thread
- mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
- }
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
// Click action
@@ -256,32 +279,9 @@ public class MediaControlPanel {
// App icon
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
- Drawable iconDrawable = icon.loadDrawable(mContext);
iconDrawable.setTint(mForegroundColor);
appIcon.setImageDrawable(iconDrawable);
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- titleText.setText(songName);
- titleText.setTextColor(mForegroundColor);
-
- // Not in mini player:
- // App title
- TextView appName = mMediaNotifView.findViewById(R.id.app_name);
- if (appName != null) {
- appName.setText(appNameString);
- appName.setTextColor(mForegroundColor);
- }
-
- // Artist name
- TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
- if (artistText != null) {
- String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
- artistText.setText(artistName);
- artistText.setTextColor(mForegroundColor);
- }
-
// Transfer chip
mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
if (mSeamless != null && mLocalMediaManager != null) {
@@ -300,6 +300,39 @@ public class MediaControlPanel {
}
makeActive();
+
+ // App title (not in mini player)
+ TextView appName = mMediaNotifView.findViewById(R.id.app_name);
+ if (appName != null) {
+ appName.setText(appNameString);
+ appName.setTextColor(mForegroundColor);
+ }
+
+ MediaMetadata mediaMetadata = mController.getMetadata();
+ if (mediaMetadata == null) {
+ Log.e(TAG, "Media metadata was null");
+ return;
+ }
+
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
+ }
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ titleText.setText(songName);
+ titleText.setTextColor(mForegroundColor);
+
+ // Artist name (not in mini player)
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
+ if (artistText != null) {
+ String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
+ artistText.setText(artistName);
+ artistText.setTextColor(mForegroundColor);
+ }
}
/**
@@ -320,9 +353,12 @@ public class MediaControlPanel {
/**
* Get the name of the package associated with the current media controller
- * @return the package name
+ * @return the package name, or null if no controller
*/
public String getMediaPlayerPackage() {
+ if (mController == null) {
+ return null;
+ }
return mController.getPackageName();
}
@@ -370,18 +406,86 @@ public class MediaControlPanel {
/**
* Process album art for layout
+ * @param description media description
+ * @param albumView view to hold the album art
+ */
+ protected void processAlbumArt(MediaDescription description, ImageView albumView) {
+ Bitmap albumArt = null;
+
+ // First try loading from URI
+ albumArt = loadBitmapFromUri(description.getIconUri());
+
+ // Then check bitmap
+ if (albumArt == null) {
+ albumArt = description.getIconBitmap();
+ }
+
+ processAlbumArtInternal(albumArt, albumView);
+ }
+
+ /**
+ * Process album art for layout
* @param metadata media metadata
+ * @param largeIcon from notification, checked as a fallback if metadata does not have art
* @param albumView view to hold the album art
*/
- private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
- Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+ private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
+ Bitmap albumArt = null;
+
+ // First look in URI fields
+ for (String field : ART_URIS) {
+ String uriString = metadata.getString(field);
+ if (uriString != null) {
+ albumArt = loadBitmapFromUri(Uri.parse(uriString));
+ if (albumArt != null) {
+ Log.d(TAG, "loaded art from " + field);
+ break;
+ }
+ }
+ }
+
+ // Then check bitmap field
+ if (albumArt == null) {
+ albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ }
+
+ // Finally try the notification's largeIcon
+ if (albumArt == null && largeIcon != null) {
+ albumArt = largeIcon.getBitmap();
+ }
+
+ processAlbumArtInternal(albumArt, albumView);
+ }
+
+ /**
+ * Load a bitmap from a URI
+ * @param uri
+ * @return bitmap, or null if couldn't be loaded
+ */
+ private Bitmap loadBitmapFromUri(Uri uri) {
+ ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
+ try {
+ return ImageDecoder.decodeBitmap(source);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Resize and crop the image if provided and update the control view
+ * @param albumArt Bitmap of art to display, or null to hide view
+ * @param albumView View that will hold the art
+ */
+ private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
+ // Resize
RoundedBitmapDrawable roundedDrawable = null;
if (albumArt != null) {
+ float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
int albumSize = (int) mContext.getResources().getDimension(
R.dimen.qs_media_album_size);
- Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
+ Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
roundedDrawable.setCornerRadius(radius);
} else {
@@ -449,10 +553,24 @@ public class MediaControlPanel {
}
/**
- * Put controls into a resumption state
+ * Puts controls into a resumption state if possible, or calls removePlayer if no component was
+ * found that could resume playback
*/
public void clearControls() {
Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage());
+ if (mServiceComponent == null) {
+ // If we don't have a way to resume, just remove the player altogether
+ Log.d(TAG, "Removing unresumable controls");
+ removePlayer();
+ return;
+ }
+ resetButtons();
+ }
+
+ /**
+ * Hide the media buttons and show only a restart button
+ */
+ protected void resetButtons() {
// Hide all the old buttons
for (int i = 0; i < mActionIds.length; i++) {
ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
@@ -465,27 +583,8 @@ public class MediaControlPanel {
ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
btn.setOnClickListener(v -> {
Log.d(TAG, "Attempting to restart session");
- // Send a media button event to previously found receiver
- if (mRecvComponent != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.setComponent(mRecvComponent);
- int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
- intent.putExtra(
- Intent.EXTRA_KEY_EVENT,
- new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
- mContext.sendBroadcast(intent);
- } else {
- // If we don't have a receiver, try relaunching the activity instead
- if (mController.getSessionActivity() != null) {
- try {
- mController.getSessionActivity().send();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled", e);
- }
- } else {
- Log.e(TAG, "No receiver or activity to restart");
- }
- }
+ QSMediaBrowser browser = new QSMediaBrowser(mContext, null, mServiceComponent);
+ browser.restart();
});
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
@@ -514,4 +613,65 @@ public class MediaControlPanel {
}
}
+ /**
+ * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
+ * component to the list of resumption components
+ */
+ private void tryUpdateResumptionList(ComponentName componentName) {
+ Log.d(TAG, "Testing if we can connect to " + componentName);
+ QSMediaBrowser.testConnection(mContext,
+ new QSMediaBrowser.Callback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "yes we can resume with " + componentName);
+ mServiceComponent = componentName;
+ updateResumptionList(componentName);
+ }
+
+ @Override
+ public void onError() {
+ Log.d(TAG, "Cannot resume with " + componentName);
+ mServiceComponent = null;
+ clearControls();
+ // remove
+ }
+ },
+ componentName);
+ }
+
+ /**
+ * Add the component to the saved list of media browser services, checking for duplicates and
+ * removing older components that exceed the maximum limit
+ * @param componentName
+ */
+ private synchronized void updateResumptionList(ComponentName componentName) {
+ // Add to front of saved list
+ if (mSharedPrefs == null) {
+ mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0);
+ }
+ String componentString = componentName.flattenToString();
+ String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null);
+ if (listString == null) {
+ listString = componentString;
+ } else {
+ String[] components = listString.split(QSMediaBrowser.DELIMITER);
+ StringBuilder updated = new StringBuilder(componentString);
+ int nBrowsers = 1;
+ for (int i = 0; i < components.length
+ && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
+ if (componentString.equals(components[i])) {
+ continue;
+ }
+ updated.append(QSMediaBrowser.DELIMITER).append(components[i]);
+ nBrowsers++;
+ }
+ listString = updated.toString();
+ }
+ mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply();
+ }
+
+ /**
+ * Called when a player can't be resumed to give it an opportunity to hide or remove itself
+ */
+ protected void removePlayer() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
new file mode 100644
index 000000000000..302b84203641
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Media browser for managing resumption in QS media controls
+ */
+public class QSMediaBrowser {
+
+ /** Maximum number of controls to show on boot */
+ public static final int MAX_RESUMPTION_CONTROLS = 5;
+
+ /** Delimiter for saved component names */
+ public static final String DELIMITER = ":";
+
+ private static final String TAG = "QSMediaBrowser";
+ private final Context mContext;
+ private final Callback mCallback;
+ private MediaBrowser mMediaBrowser;
+ private ComponentName mComponentName;
+
+ /**
+ * Initialize a new media browser
+ * @param context the context
+ * @param callback used to report media items found
+ * @param componentName Component name of the MediaBrowserService this browser will connect to
+ */
+ public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+ mContext = context;
+ mCallback = callback;
+ mComponentName = componentName;
+
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ mMediaBrowser = new MediaBrowser(mContext,
+ mComponentName,
+ mConnectionCallback,
+ rootHints);
+ }
+
+ /**
+ * Connects to the MediaBrowserService and looks for valid media. If a media item is returned
+ * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription
+ */
+ public void findRecentMedia() {
+ Log.d(TAG, "Connecting to " + mComponentName);
+ mMediaBrowser.connect();
+ }
+
+ private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+ new MediaBrowser.SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(String parentId,
+ List<MediaBrowser.MediaItem> children) {
+ if (children.size() == 0) {
+ Log.e(TAG, "No children found");
+ return;
+ }
+ // We ask apps to return a playable item as the first child when sending
+ // a request with EXTRA_RECENT; if they don't, no resume controls
+ MediaBrowser.MediaItem child = children.get(0);
+ MediaDescription desc = child.getDescription();
+ if (child.isPlayable()) {
+ mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this);
+ } else {
+ Log.e(TAG, "Child found but not playable for " + mComponentName);
+ }
+ mMediaBrowser.disconnect();
+ }
+
+ @Override
+ public void onError(String parentId) {
+ Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId);
+ mMediaBrowser.disconnect();
+ }
+
+ @Override
+ public void onError(String parentId, Bundle options) {
+ Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId
+ + ", options: " + options);
+ mMediaBrowser.disconnect();
+ }
+ };
+
+ private final MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ /**
+ * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed.
+ * For resumption controls, apps are expected to return a playable media item as the first
+ * child. If there are no children or it isn't playable it will be ignored.
+ */
+ @Override
+ public void onConnected() {
+ if (mMediaBrowser.isConnected()) {
+ mCallback.onConnected();
+ Log.d(TAG, "Service connected for " + mComponentName);
+ String root = mMediaBrowser.getRoot();
+ mMediaBrowser.subscribe(root, mSubscriptionCallback);
+ }
+ }
+
+ /**
+ * Invoked when the client is disconnected from the media browser.
+ */
+ @Override
+ public void onConnectionSuspended() {
+ Log.d(TAG, "Connection suspended for " + mComponentName);
+ }
+
+ /**
+ * Invoked when the connection to the media browser failed.
+ */
+ @Override
+ public void onConnectionFailed() {
+ Log.e(TAG, "Connection failed for " + mComponentName);
+ mCallback.onError();
+ }
+ };
+
+ /**
+ * Connects to the MediaBrowserService and starts playback
+ */
+ public void restart() {
+ if (mMediaBrowser.isConnected()) {
+ mMediaBrowser.disconnect();
+ }
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ mMediaBrowser = new MediaBrowser(mContext, mComponentName,
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected());
+ MediaSession.Token token = mMediaBrowser.getSessionToken();
+ MediaController controller = new MediaController(mContext, token);
+ controller.getTransportControls();
+ controller.getTransportControls().prepare();
+ controller.getTransportControls().play();
+ }
+ }, rootHints);
+ mMediaBrowser.connect();
+ }
+
+ /**
+ * Get the media session token
+ * @return the token, or null if the MediaBrowser is null or disconnected
+ */
+ public MediaSession.Token getToken() {
+ if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
+ return null;
+ }
+ return mMediaBrowser.getSessionToken();
+ }
+
+ /**
+ * Get an intent to launch the app associated with this browser service
+ * @return
+ */
+ public PendingIntent getAppIntent() {
+ PackageManager pm = mContext.getPackageManager();
+ Intent launchIntent = pm.getLaunchIntentForPackage(mComponentName.getPackageName());
+ return PendingIntent.getActivity(mContext, 0, launchIntent, 0);
+ }
+
+ /**
+ * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser
+ * @param mContext the context
+ * @param callback methods onConnected or onError will be called to indicate whether the
+ * connection was successful or not
+ * @param mComponentName Component name of the MediaBrowserService this browser will connect to
+ */
+ public static MediaBrowser testConnection(Context mContext, Callback callback,
+ ComponentName mComponentName) {
+ final MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "connected");
+ callback.onConnected();
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ Log.d(TAG, "suspended");
+ callback.onError();
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ Log.d(TAG, "failed");
+ callback.onError();
+ }
+ };
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ MediaBrowser browser = new MediaBrowser(mContext,
+ mComponentName,
+ mConnectionCallback,
+ rootHints);
+ browser.connect();
+ return browser;
+ }
+
+ /**
+ * Interface to handle results from QSMediaBrowser
+ */
+ public static class Callback {
+ /**
+ * Called when the browser has successfully connected to the service
+ */
+ public void onConnected() {
+ }
+
+ /**
+ * Called when the browser encountered an error connecting to the service
+ */
+ public void onError() {
+ }
+
+ /**
+ * Called when the browser finds a suitable track to add to the media carousel
+ * @param track media info for the item
+ * @param component component of the MediaBrowserService which returned this
+ * @param browser reference to the browser
+ */
+ public void addTrack(MediaDescription track, ComponentName component,
+ QSMediaBrowser browser) {
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 89b22bc518bb..9e574a1fa621 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -18,11 +18,13 @@ package com.android.systemui.qs;
import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.media.MediaDescription;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.util.Log;
@@ -60,9 +62,11 @@ public class QSMediaPlayer extends MediaControlPanel {
};
private final QSPanel mParent;
+ private final Executor mForegroundExecutor;
private final DelayableExecutor mBackgroundExecutor;
private final SeekBarViewModel mSeekBarViewModel;
private final SeekBarObserver mSeekBarObserver;
+ private String mPackageName;
/**
* Initialize quick shade version of player
@@ -77,6 +81,7 @@ public class QSMediaPlayer extends MediaControlPanel {
super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
foregroundExecutor, backgroundExecutor);
mParent = (QSPanel) parent;
+ mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
mSeekBarObserver = new SeekBarObserver(getView());
@@ -90,47 +95,103 @@ public class QSMediaPlayer extends MediaControlPanel {
}
/**
+ * Add a media panel view based on a media description. Used for resumption
+ * @param description
+ * @param iconColor
+ * @param bgColor
+ * @param contentIntent
+ * @param pkgName
+ */
+ public void setMediaSession(MediaSession.Token token, MediaDescription description,
+ int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
+ mPackageName = pkgName;
+ PackageManager pm = getContext().getPackageManager();
+ Drawable icon = null;
+ CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
+ try {
+ icon = pm.getApplicationIcon(pkgName);
+ appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Error getting package information", e);
+ }
+
+ // Set what we can normally
+ super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
+ appName.toString(), null);
+
+ // Then add info from MediaDescription
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
+ }
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ CharSequence songName = description.getTitle();
+ titleText.setText(songName);
+ titleText.setTextColor(iconColor);
+
+ // Artist name (not in mini player)
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
+ if (artistText != null) {
+ CharSequence artistName = description.getSubtitle();
+ artistText.setText(artistName);
+ artistText.setTextColor(iconColor);
+ }
+
+ initLongPressMenu(iconColor);
+
+ // Set buttons to resume state
+ resetButtons();
+ }
+
+ /**
* Update media panel view for the given media session
* @param token token for this media session
* @param icon app notification icon
+ * @param largeIcon notification's largeIcon, used as a fallback for album art
* @param iconColor foreground color (for text, icons)
* @param bgColor background color
* @param actionsContainer a LinearLayout containing the media action buttons
- * @param notif reference to original notification
+ * @param contentIntent Intent to send when user taps on player
+ * @param appName Application title
* @param key original notification's key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
- int bgColor, View actionsContainer, Notification notif, String key) {
+ public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
+ String appName, String key) {
- String appName = Notification.Builder.recoverBuilder(getContext(), notif)
- .loadHeaderAppName();
- super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent, appName, key);
+ super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
+ key);
// Media controls
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- thisBtn.setVisibility(View.GONE);
- continue;
- }
+ if (actionsContainer != null) {
+ LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+ int i = 0;
+ for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
+ if (thatBtn == null || thatBtn.getDrawable() == null
+ || thatBtn.getVisibility() != View.VISIBLE) {
+ thisBtn.setVisibility(View.GONE);
+ continue;
+ }
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- thisBtn.setVisibility(View.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- Log.d(TAG, "clicking on other button");
- thatBtn.performClick();
- });
- }
+ Drawable thatIcon = thatBtn.getDrawable();
+ thisBtn.setImageDrawable(thatIcon.mutate());
+ thisBtn.setVisibility(View.VISIBLE);
+ thisBtn.setOnClickListener(v -> {
+ Log.d(TAG, "clicking on other button");
+ thatBtn.performClick();
+ });
+ }
- // Hide any unused buttons
- for (; i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- thisBtn.setVisibility(View.GONE);
+ // Hide any unused buttons
+ for (; i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ thisBtn.setVisibility(View.GONE);
+ }
}
// Seek Bar
@@ -138,6 +199,10 @@ public class QSMediaPlayer extends MediaControlPanel {
mBackgroundExecutor.execute(
() -> mSeekBarViewModel.updateController(controller, iconColor));
+ initLongPressMenu(iconColor);
+ }
+
+ private void initLongPressMenu(int iconColor) {
// Set up long press menu
View guts = mMediaNotifView.findViewById(R.id.media_guts);
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -145,7 +210,7 @@ public class QSMediaPlayer extends MediaControlPanel {
View clearView = options.findViewById(R.id.remove);
clearView.setOnClickListener(b -> {
- mParent.removeMediaPlayer(QSMediaPlayer.this);
+ removePlayer();
});
ImageView removeIcon = options.findViewById(R.id.remove_icon);
removeIcon.setImageTintList(ColorStateList.valueOf(iconColor));
@@ -165,11 +230,9 @@ public class QSMediaPlayer extends MediaControlPanel {
}
@Override
- public void clearControls() {
- super.clearControls();
-
+ protected void resetButtons() {
+ super.resetButtons();
mSeekBarViewModel.clearController();
-
View guts = mMediaNotifView.findViewById(R.id.media_guts);
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -192,4 +255,19 @@ public class QSMediaPlayer extends MediaControlPanel {
public void setListening(boolean listening) {
mSeekBarViewModel.setListening(listening);
}
+
+ @Override
+ public void removePlayer() {
+ Log.d(TAG, "removing player from parent: " + mParent);
+ // Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
+ mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
+ }
+
+ @Override
+ public String getMediaPlayerPackage() {
+ if (getController() == null) {
+ return mPackageName;
+ }
+ return super.getMediaPlayerPackage();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index fee08389388d..1252008755a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -21,16 +21,26 @@ import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEX
import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.media.MediaDescription;
import android.media.session.MediaSession;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
@@ -54,6 +64,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaControlPanel;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -90,6 +101,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected final Context mContext;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+ private final BroadcastDispatcher mBroadcastDispatcher;
private String mCachedSpecs = "";
protected final View mBrightnessView;
private final H mHandler = new H();
@@ -123,6 +135,19 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private BrightnessMirrorController mBrightnessMirrorController;
private View mDivider;
+ private boolean mHasLoadedMediaControls;
+
+ private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ if (!mHasLoadedMediaControls) {
+ loadMediaResumptionControls();
+ }
+ }
+ }
+ };
@Inject
public QSPanel(
@@ -142,6 +167,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mLocalBluetoothManager = localBluetoothManager;
+ mBroadcastDispatcher = broadcastDispatcher;
setOrientation(VERTICAL);
@@ -176,7 +202,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
updateResources();
mBrightnessController = new BrightnessController(getContext(),
- findViewById(R.id.brightness_slider), broadcastDispatcher);
+ findViewById(R.id.brightness_slider), mBroadcastDispatcher);
}
@Override
@@ -200,14 +226,16 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
* Add or update a player for the associated media session
* @param token
* @param icon
+ * @param largeIcon
* @param iconColor
* @param bgColor
* @param actionsContainer
* @param notif
* @param key
*/
- public void addMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
- View actionsContainer, StatusBarNotification notif, String key) {
+ public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
+ String key) {
if (!useQsMediaPlayer(mContext)) {
// Shouldn't happen, but just in case
Log.e(TAG, "Tried to add media session without player!");
@@ -221,7 +249,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
QSMediaPlayer player = null;
String packageName = notif.getPackageName();
for (QSMediaPlayer p : mMediaPlayers) {
- if (p.getMediaSessionToken().equals(token)) {
+ if (p.getKey() == null) {
+ // No notification key = loaded via mediabrowser, so just match on package
+ if (packageName.equals(p.getMediaPlayerPackage())) {
+ Log.d(TAG, "Found matching resume player by package: " + packageName);
+ player = p;
+ break;
+ }
+ } else if (p.getMediaSessionToken().equals(token)) {
Log.d(TAG, "Found matching player by token " + packageName);
player = p;
break;
@@ -262,8 +297,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
Log.d(TAG, "setting player session");
- player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
- notif.getNotification(), key);
+ String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
+ .loadHeaderAppName();
+ player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
+ notif.getNotification().contentIntent, appName, key);
if (mMediaPlayers.size() > 0) {
((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
@@ -293,6 +330,74 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return true;
}
+ private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
+ @Override
+ public void addTrack(MediaDescription desc, ComponentName component,
+ QSMediaBrowser browser) {
+ if (component == null) {
+ Log.e(TAG, "Component cannot be null");
+ return;
+ }
+
+ Log.d(TAG, "adding track from browser: " + desc + ", " + component);
+ QSMediaPlayer player = new QSMediaPlayer(mContext, QSPanel.this,
+ null, mForegroundExecutor, mBackgroundExecutor);
+
+ String pkgName = component.getPackageName();
+
+ // Add controls to carousel
+ int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
+ int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(playerWidth,
+ LayoutParams.MATCH_PARENT);
+ lp.setMarginStart(padding);
+ lp.setMarginEnd(padding);
+ mMediaCarousel.addView(player.getView(), lp);
+ ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
+ mMediaPlayers.add(player);
+
+ int iconColor = Color.DKGRAY;
+ int bgColor = Color.LTGRAY;
+
+ MediaSession.Token token = browser.getToken();
+ player.setMediaSession(token, desc, iconColor, bgColor, browser.getAppIntent(),
+ pkgName);
+ }
+ };
+
+ /**
+ * Load controls for resuming media, if available
+ */
+ private void loadMediaResumptionControls() {
+ if (!useQsMediaPlayer(mContext)) {
+ return;
+ }
+ Log.d(TAG, "Loading resumption controls");
+
+ // Look up saved components to resume
+ Context userContext = mContext.createContextAsUser(mContext.getUser(), 0);
+ SharedPreferences prefs = userContext.getSharedPreferences(
+ MediaControlPanel.MEDIA_PREFERENCES, Context.MODE_PRIVATE);
+ String listString = prefs.getString(MediaControlPanel.MEDIA_PREFERENCE_KEY, null);
+ if (listString == null) {
+ Log.d(TAG, "No saved media components");
+ return;
+ }
+
+ String[] components = listString.split(QSMediaBrowser.DELIMITER);
+ Log.d(TAG, "components are: " + listString + " count " + components.length);
+ for (int i = 0; i < components.length && i < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
+ String[] info = components[i].split("/");
+ String packageName = info[0];
+ String className = info[1];
+ ComponentName component = new ComponentName(packageName, className);
+ QSMediaBrowser browser = new QSMediaBrowser(mContext, mMediaBrowserCallback,
+ component);
+ browser.findRecentMedia();
+ }
+ mHasLoadedMediaControls = true;
+ }
+
protected void addDivider() {
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -343,6 +448,22 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mBrightnessMirrorController.addCallback(this);
}
mDumpManager.registerDumpable(getDumpableTag(), this);
+
+ if (getClass() == QSPanel.class) {
+ //TODO(ethibodeau) remove class check after media refactor in ag/11059751
+ // Only run this in QSPanel proper, not QQS
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, filter, null,
+ UserHandle.ALL);
+ mHasLoadedMediaControls = false;
+
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager.isUserUnlocked(mContext.getUserId())) {
+ // If it's already unlocked (like if dark theme was toggled), we can load now
+ loadMediaResumptionControls();
+ }
+ }
}
@Override
@@ -358,6 +479,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mBrightnessMirrorController.removeCallback(this);
}
mDumpManager.unregisterDumpable(getDumpableTag());
+ mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
super.onDetachedFromWindow();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 62296720213b..89b36da0c834 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -59,6 +59,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel {
* Update media panel view for the given media session
* @param token token for this media session
* @param icon app notification icon
+ * @param largeIcon notification's largeIcon, used as a fallback for album art
* @param iconColor foreground color (for text, icons)
* @param bgColor background color
* @param actionsContainer a LinearLayout containing the media action buttons
@@ -67,8 +68,9 @@ public class QuickQSMediaPlayer extends MediaControlPanel {
* @param contentIntent Intent to send when user taps on the view
* @param key original notification's key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
- View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) {
+ public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
+ PendingIntent contentIntent, String key) {
// Only update if this is a different session and currently playing
String oldPackage = "";
if (getController() != null) {
@@ -83,7 +85,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel {
return;
}
- super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, key);
+ super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
int i = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 9cee7e7ccba4..1a6a104387ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -18,7 +18,9 @@ package com.android.systemui.qs.tiles;
import android.content.Intent;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.util.Log;
+import android.widget.Switch;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
@@ -88,6 +90,10 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
}
+ state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
+ ? state.label
+ : TextUtils.concat(state.label, ", ", state.secondaryLabel);
+ state.expandedAccessibilityClassName = Switch.class.getName();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66bc177da81d..fecb7b602012 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
@@ -380,7 +381,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
Insets visibleInsets, int taskId) {
mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
- taskId, mHandler, null);
+ taskId, SCREENSHOT_OVERVIEW, mHandler, null);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index e1cc0b0c90c2..1efe663ca6ce 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -76,6 +76,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -104,7 +105,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
*/
static class SaveImageInBackgroundData {
public Bitmap image;
- public Uri imageUri;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
public int errorMsgResId;
@@ -112,13 +112,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
void clearImage() {
image = null;
- imageUri = null;
+ }
+ }
+
+ /**
+ * Structure returned by the SaveImageInBackgroundTask
+ */
+ static class SavedImageData {
+ public Uri uri;
+ public Notification.Action shareAction;
+ public Notification.Action editAction;
+ public Notification.Action deleteAction;
+ public List<Notification.Action> smartActions;
+
+ /**
+ * Used to reset the return data on error
+ */
+ public void reset() {
+ uri = null;
+ shareAction = null;
+ editAction = null;
+ deleteAction = null;
+ smartActions = null;
}
}
abstract static class ActionsReadyListener {
- abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions,
- List<Notification.Action> actions);
+ abstract void onActionsReady(SavedImageData imageData);
}
// These strings are used for communicating the action invoked to
@@ -147,6 +167,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private static final int MESSAGE_CORNER_TIMEOUT = 2;
private final ScreenshotNotificationsController mNotificationsController;
+ private final UiEventLogger mUiEventLogger;
private final Context mContext;
private final WindowManager mWindowManager;
@@ -185,6 +206,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_CORNER_TIMEOUT:
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
GlobalScreenshot.this.clearScreenshot("timeout");
break;
default:
@@ -199,9 +221,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
@Inject
public GlobalScreenshot(
Context context, @Main Resources resources, LayoutInflater layoutInflater,
- ScreenshotNotificationsController screenshotNotificationsController) {
+ ScreenshotNotificationsController screenshotNotificationsController,
+ UiEventLogger uiEventLogger) {
mContext = context;
mNotificationsController = screenshotNotificationsController;
+ mUiEventLogger = uiEventLogger;
// Inflate the screenshot layout
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
@@ -222,7 +246,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mBackgroundProtection = mScreenshotLayout.findViewById(
R.id.global_screenshot_actions_background);
mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
- mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button"));
+ mDismissButton.setOnClickListener(view -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+ clearScreenshot("dismiss_button");
+ });
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
@@ -445,12 +472,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
- void onActionsReady(Uri uri, List<Notification.Action> smartActions,
- List<Notification.Action> actions) {
- if (uri == null) {
+ void onActionsReady(SavedImageData imageData) {
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
mScreenshotHandler.post(() -> {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.addListener(
@@ -458,13 +487,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- createScreenshotActionsShadeAnimation(
- smartActions, actions).start();
+ createScreenshotActionsShadeAnimation(imageData)
+ .start();
}
});
} else {
- createScreenshotActionsShadeAnimation(smartActions,
- actions).start();
+ createScreenshotActionsShadeAnimation(imageData).start();
}
});
}
@@ -567,8 +595,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
return dropInAnimation;
}
- private ValueAnimator createScreenshotActionsShadeAnimation(
- List<Notification.Action> smartActions, List<Notification.Action> actions) {
+ private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
LayoutInflater inflater = LayoutInflater.from(mContext);
mActionsView.removeAllViews();
mActionsContainer.setScrollX(0);
@@ -583,45 +610,63 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
} catch (RemoteException e) {
}
- for (Notification.Action smartAction : smartActions) {
+ for (Notification.Action smartAction : imageData.smartActions) {
ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
R.layout.global_screenshot_action_chip, mActionsView, false);
actionChip.setText(smartAction.title);
actionChip.setIcon(smartAction.getIcon(), false);
actionChip.setPendingIntent(smartAction.actionIntent,
- () -> clearScreenshot("chip tapped"));
+ () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
+ clearScreenshot("chip tapped");
+ });
mActionsView.addView(actionChip);
}
- for (Notification.Action action : actions) {
- ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- actionChip.setText(action.title);
- actionChip.setIcon(action.getIcon(), true);
- actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped"));
- if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) {
- mScreenshotView.setOnClickListener(v -> {
- try {
- action.actionIntent.send();
- clearScreenshot("screenshot preview tapped");
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Intent cancelled", e);
- }
- });
- mScreenshotView.setContentDescription(action.title);
+ ScreenshotActionChip shareChip = (ScreenshotActionChip) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ shareChip.setText(imageData.shareAction.title);
+ shareChip.setIcon(imageData.shareAction.getIcon(), true);
+ shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
+ clearScreenshot("chip tapped");
+ });
+ mActionsView.addView(shareChip);
+
+ ScreenshotActionChip editChip = (ScreenshotActionChip) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ editChip.setText(imageData.editAction.title);
+ editChip.setIcon(imageData.editAction.getIcon(), true);
+ editChip.setPendingIntent(imageData.editAction.actionIntent, () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
+ clearScreenshot("chip tapped");
+ });
+ mActionsView.addView(editChip);
+
+ mScreenshotView.setOnClickListener(v -> {
+ try {
+ imageData.editAction.actionIntent.send();
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+ clearScreenshot("screenshot preview tapped");
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Intent cancelled", e);
}
- mActionsView.addView(actionChip);
- }
+ });
+ mScreenshotView.setContentDescription(imageData.editAction.title);
+
if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
R.layout.global_screenshot_action_chip, mActionsView, false);
Toast scrollNotImplemented = Toast.makeText(
mContext, "Not implemented", Toast.LENGTH_SHORT);
- scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate
+ scrollChip.setText("Extend"); // TODO : add resource and translate
scrollChip.setIcon(
Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true);
- scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+ scrollChip.setOnClickListener(v -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
+ scrollNotImplemented.show();
+ });
mActionsView.addView(scrollChip);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
index f3614ffbdb1b..095c32f4a2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -24,7 +24,6 @@ import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -53,7 +52,6 @@ import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
-import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -347,14 +345,13 @@ public class GlobalScreenshotLegacy {
// Save the screenshot once we have a bit of time now
saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
@Override
- void onActionsReady(Uri uri, List<Notification.Action> smartActions,
- List<Notification.Action> actions) {
- if (uri == null) {
+ void onActionsReady(GlobalScreenshot.SavedImageData actionData) {
+ if (actionData.uri == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
mNotificationsController
- .showScreenshotActionsNotification(uri, smartActions, actions);
+ .showScreenshotActionsNotification(actionData);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index c828c4cccce5..170174deaeb3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final GlobalScreenshot.SaveImageInBackgroundData mParams;
+ private final GlobalScreenshot.SavedImageData mImageData;
private final String mImageFileName;
private final long mImageTime;
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
@@ -93,6 +94,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
mContext = context;
+ mImageData = new GlobalScreenshot.SavedImageData();
// Prepare all the output metadata
mParams = data;
@@ -145,6 +147,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
values.put(MediaColumns.IS_PENDING, 1);
final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+
try {
// First, write the actual data for our screenshot
try (OutputStream out = resolver.openOutputStream(uri)) {
@@ -192,8 +195,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
throw e;
}
- List<Notification.Action> actions =
- populateNotificationActions(mContext, r, uri);
List<Notification.Action> smartActions = new ArrayList<>();
if (mSmartActionsEnabled) {
int timeoutMs = DeviceConfig.getInt(
@@ -206,8 +207,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
mSmartActionsProvider),
mContext));
}
- mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions);
- mParams.imageUri = uri;
+
+ mImageData.uri = uri;
+ mImageData.smartActions = smartActions;
+ mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri);
+ mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri);
+ mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+
+ mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.image = null;
mParams.errorMsgResId = 0;
} catch (Exception e) {
@@ -216,29 +223,26 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Slog.e(TAG, "unable to save screenshot", e);
mParams.clearImage();
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
- mParams.mActionsReadyListener.onActionsReady(null, null, null);
+ mImageData.reset();
+ mParams.mActionsReadyListener.onActionsReady(mImageData);
}
return null;
}
@Override
- protected void onPostExecute(Void params) {
- mParams.finisher.accept(mParams.imageUri);
- }
-
- @Override
protected void onCancelled(Void params) {
// If we are cancelled while the task is running in the background, we may get null
// params. The finisher is expected to always be called back, so just use the baked-in
// params from the ctor in any case.
- mParams.mActionsReadyListener.onActionsReady(null, null, null);
+ mImageData.reset();
+ mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.finisher.accept(null);
mParams.clearImage();
}
@VisibleForTesting
- List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) {
+ Notification.Action createShareAction(Context context, Resources r, Uri uri) {
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
// closeSystemWindows
@@ -263,8 +267,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// by setting the (otherwise unused) request code to the current user id.
int requestCode = context.getUserId();
- ArrayList<Notification.Action> actions = new ArrayList<>();
-
PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
@@ -288,7 +290,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
r.getString(com.android.internal.R.string.share), shareAction);
- actions.add(shareActionBuilder.build());
+
+ return shareActionBuilder.build();
+ }
+
+ @VisibleForTesting
+ Notification.Action createEditAction(Context context, Resources r, Uri uri) {
+ // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
+ // order to do some common work like dismissing the keyguard and sending
+ // closeSystemWindows
// Create an edit intent, if a specific package is provided as the editor, then
// launch that directly
@@ -302,6 +312,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ // Make sure pending intents for the system user are still unique across users
+ // by setting the (otherwise unused) request code to the current user id.
+ int requestCode = mContext.getUserId();
+
// Create a edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, GlobalScreenshot.ActionProxyReceiver.class)
@@ -317,24 +331,30 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
- actions.add(editActionBuilder.build());
-
- if (mCreateDeleteAction) {
- // Create a delete action for the notification
- PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
- new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
- .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
- r.getString(com.android.internal.R.string.delete), deleteAction);
- actions.add(deleteActionBuilder.build());
- }
- return actions;
+
+ return editActionBuilder.build();
+ }
+
+ @VisibleForTesting
+ Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+ // Make sure pending intents for the system user are still unique across users
+ // by setting the (otherwise unused) request code to the current user id.
+ int requestCode = mContext.getUserId();
+
+ // Create a delete action for the notification
+ PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
+ new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+ .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+ Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
+ r.getString(com.android.internal.R.string.delete), deleteAction);
+
+ return deleteActionBuilder.build();
}
private int getUserHandleOfForegroundApplication(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
new file mode 100644
index 000000000000..20fa991dcc1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum ScreenshotEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "screenshot requested from global actions")
+ SCREENSHOT_REQUESTED_GLOBAL_ACTIONS(302),
+ @UiEvent(doc = "screenshot requested from key chord")
+ SCREENSHOT_REQUESTED_KEY_CHORD(303),
+ @UiEvent(doc = "screenshot requested from other key press (e.g. ctrl-s)")
+ SCREENSHOT_REQUESTED_KEY_OTHER(384),
+ @UiEvent(doc = "screenshot requested from overview")
+ SCREENSHOT_REQUESTED_OVERVIEW(304),
+ @UiEvent(doc = "screenshot requested from accessibility actions")
+ SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382),
+ @UiEvent(doc = "screenshot requested (other)")
+ SCREENSHOT_REQUESTED_OTHER(305),
+ @UiEvent(doc = "screenshot was saved")
+ SCREENSHOT_SAVED(306),
+ @UiEvent(doc = "screenshot failed to save")
+ SCREENSHOT_NOT_SAVED(336),
+ @UiEvent(doc = "screenshot preview tapped")
+ SCREENSHOT_PREVIEW_TAPPED(307),
+ @UiEvent(doc = "screenshot edit button tapped")
+ SCREENSHOT_EDIT_TAPPED(308),
+ @UiEvent(doc = "screenshot share button tapped")
+ SCREENSHOT_SHARE_TAPPED(309),
+ @UiEvent(doc = "screenshot smart action chip tapped")
+ SCREENSHOT_SMART_ACTION_TAPPED(374),
+ @UiEvent(doc = "screenshot scroll tapped")
+ SCREENSHOT_SCROLL_TAPPED(373),
+ @UiEvent(doc = "screenshot interaction timed out")
+ SCREENSHOT_INTERACTION_TIMEOUT(310),
+ @UiEvent(doc = "screenshot explicitly dismissed")
+ SCREENSHOT_EXPLICIT_DISMISSAL(311);
+
+ private final int mId;
+
+ ScreenshotEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ static ScreenshotEvent getScreenshotSource(int source) {
+ switch (source) {
+ case SCREENSHOT_GLOBAL_ACTIONS:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_GLOBAL_ACTIONS;
+ case SCREENSHOT_KEY_CHORD:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD;
+ case SCREENSHOT_KEY_OTHER:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER;
+ case SCREENSHOT_OVERVIEW:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW;
+ case SCREENSHOT_ACCESSIBILITY_ACTIONS:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS;
+ case SCREENSHOT_OTHER:
+ default:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 811a8d936b77..fbcd6ba0ff47 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -32,7 +32,6 @@ import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
-import android.net.Uri;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.WindowManager;
@@ -42,8 +41,6 @@ import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.util.NotificationChannels;
-import java.util.List;
-
import javax.inject.Inject;
/**
@@ -185,23 +182,20 @@ public class ScreenshotNotificationsController {
/**
* Shows a notification with the saved screenshot and actions that can be taken with it.
*
- * @param imageUri URI for the saved image
- * @param actions a list of notification actions which can be taken
+ * @param actionData SavedImageData struct with image URI and actions
*/
public void showScreenshotActionsNotification(
- Uri imageUri,
- List<Notification.Action> smartActions,
- List<Notification.Action> actions) {
- for (Notification.Action action : actions) {
- mNotificationBuilder.addAction(action);
- }
- for (Notification.Action smartAction : smartActions) {
+ GlobalScreenshot.SavedImageData actionData) {
+ mNotificationBuilder.addAction(actionData.shareAction);
+ mNotificationBuilder.addAction(actionData.editAction);
+ mNotificationBuilder.addAction(actionData.deleteAction);
+ for (Notification.Action smartAction : actionData.smartActions) {
mNotificationBuilder.addAction(smartAction);
}
// Create the intent to show the screenshot in gallery
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
- launchIntent.setDataAndType(imageUri, "image/png");
+ launchIntent.setDataAndType(actionData.uri, "image/png");
launchIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
new file mode 100644
index 000000000000..5ced40cb1b3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.os.IBinder;
+import android.view.IWindowManager;
+
+import javax.inject.Inject;
+
+/**
+ * Stub
+ */
+public class ScrollCaptureController {
+
+ public static final int STATUS_A = 0;
+ public static final int STATUS_B = 1;
+
+ private final IWindowManager mWindowManagerService;
+ private StatusListener mListener;
+
+ /**
+ *
+ * @param windowManagerService
+ */
+ @Inject
+ public ScrollCaptureController(IWindowManager windowManagerService) {
+ mWindowManagerService = windowManagerService;
+ }
+
+ interface StatusListener {
+ void onScrollCaptureStatus(boolean available);
+ }
+
+ /**
+ *
+ * @param window
+ * @param listener
+ */
+ public void getStatus(IBinder window, StatusListener listener) {
+ mListener = listener;
+// try {
+// mWindowManagerService.requestScrollCapture(window, new ClientCallbacks());
+// } catch (RemoteException e) {
+// }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 8b8b6f8071e1..a6e083b04ea3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -32,6 +32,9 @@ import android.os.UserManager;
import android.util.Log;
import android.view.WindowManager;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.ScreenshotHelper;
+
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -42,6 +45,7 @@ public class TakeScreenshotService extends Service {
private final GlobalScreenshot mScreenshot;
private final GlobalScreenshotLegacy mScreenshotLegacy;
private final UserManager mUserManager;
+ private final UiEventLogger mUiEventLogger;
private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
@@ -64,14 +68,22 @@ public class TakeScreenshotService extends Service {
return;
}
- // TODO (mkephart): clean up once notifications flow is fully deprecated
+ // TODO: clean up once notifications flow is fully deprecated
boolean useCornerFlow = true;
+
+ ScreenshotHelper.ScreenshotRequest screenshotRequest =
+ (ScreenshotHelper.ScreenshotRequest) msg.obj;
+
+ mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
+
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (useCornerFlow) {
mScreenshot.takeScreenshot(finisher);
} else {
- mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ mScreenshotLegacy.takeScreenshot(
+ finisher, screenshotRequest.getHasStatusBar(),
+ screenshotRequest.getHasNavBar());
}
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
@@ -79,17 +91,15 @@ public class TakeScreenshotService extends Service {
mScreenshot.takeScreenshotPartial(finisher);
} else {
mScreenshotLegacy.takeScreenshotPartial(
- finisher, msg.arg1 > 0, msg.arg2 > 0);
+ finisher, screenshotRequest.getHasStatusBar(),
+ screenshotRequest.getHasNavBar());
}
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
- Bitmap screenshot = msg.getData().getParcelable(
- WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP);
- Rect screenBounds = msg.getData().getParcelable(
- WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS);
- Insets insets = msg.getData().getParcelable(
- WindowManager.PARCEL_KEY_SCREENSHOT_INSETS);
- int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID);
+ Bitmap screenshot = screenshotRequest.getBitmap();
+ Rect screenBounds = screenshotRequest.getBoundsInScreen();
+ Insets insets = screenshotRequest.getInsets();
+ int taskId = screenshotRequest.getTaskId();
if (useCornerFlow) {
mScreenshot.handleImageAsScreenshot(
screenshot, screenBounds, insets, taskId, finisher);
@@ -106,10 +116,12 @@ public class TakeScreenshotService extends Service {
@Inject
public TakeScreenshotService(GlobalScreenshot globalScreenshot,
- GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
+ GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager,
+ UiEventLogger uiEventLogger) {
mScreenshot = globalScreenshot;
mScreenshotLegacy = globalScreenshotLegacy;
mUserManager = userManager;
+ mUiEventLogger = uiEventLogger;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
index fa1b0267fafa..4de978c77128 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
@@ -18,8 +18,10 @@ package com.android.systemui.settings
import android.content.Context
import android.os.UserHandle
+import androidx.annotation.VisibleForTesting
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.util.Assert
+import java.lang.IllegalStateException
import javax.inject.Inject
import javax.inject.Singleton
@@ -32,7 +34,16 @@ class CurrentUserContextTracker @Inject constructor(
broadcastDispatcher: BroadcastDispatcher
) {
private val userTracker: CurrentUserTracker
- var currentUserContext: Context
+ private var initialized = false
+
+ private var _curUserContext: Context? = null
+ val currentUserContext: Context
+ get() {
+ if (!initialized) {
+ throw IllegalStateException("Must initialize before getting context")
+ }
+ return _curUserContext!!
+ }
init {
userTracker = object : CurrentUserTracker(broadcastDispatcher) {
@@ -40,21 +51,21 @@ class CurrentUserContextTracker @Inject constructor(
handleUserSwitched(newUserId)
}
}
-
- currentUserContext = makeUserContext(userTracker.currentUserId)
}
fun initialize() {
+ initialized = true
+ _curUserContext = makeUserContext(userTracker.currentUserId)
userTracker.startTracking()
}
- private fun handleUserSwitched(newUserId: Int) {
- currentUserContext = makeUserContext(newUserId)
+ @VisibleForTesting
+ fun handleUserSwitched(newUserId: Int) {
+ _curUserContext = makeUserContext(newUserId)
}
private fun makeUserContext(uid: Int): Context {
Assert.isMainThread()
- return sysuiContext.createContextAsUser(
- UserHandle.getUserHandleForUid(userTracker.currentUserId), 0)
+ return sysuiContext.createContextAsUser(UserHandle.of(uid), 0)
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 85dcbb6316d0..5aa7946bcb7f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -180,12 +180,17 @@ public class WindowManagerProxy {
if (isHomeOrRecentTask(rootTask)) {
tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash());
}
+ // Only move resizeable task to split secondary. WM will just ignore this anyways...
+ if (!rootTask.isResizable()) continue;
+ // Only move fullscreen tasks to split secondary.
if (rootTask.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_FULLSCREEN) {
continue;
}
wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
}
+ // Move the secondary split-forward.
+ wct.reorder(tiles.mSecondary.token, true /* onTop */);
boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
WindowOrganizer.applyTransaction(wct);
return isHomeResizable;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 1b7524521d76..8c24c540e743 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.NavigationBarFragment;
import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NavigationModeController;
import com.android.systemui.statusbar.policy.BatteryController;
import javax.inject.Inject;
@@ -139,7 +140,8 @@ public class NavigationBarController implements Callbacks {
? Dependency.get(LightBarController.class)
: new LightBarController(context,
Dependency.get(DarkIconDispatcher.class),
- Dependency.get(BatteryController.class));
+ Dependency.get(BatteryController.class),
+ Dependency.get(NavigationModeController.class));
navBar.setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 37fc13e2df5b..afb50027f03d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -123,7 +123,6 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
mBubbleController = bubbleController;
mDynamicPrivacyController = privacyController;
- privacyController.addListener(this);
mDynamicChildBindController = dynamicChildBindController;
}
@@ -131,6 +130,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
NotificationListContainer listContainer) {
mPresenter = presenter;
mListContainer = listContainer;
+ mDynamicPrivacyController.addListener(this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 4e6df0ad1ba4..d364689a65d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -23,11 +23,14 @@ import android.view.View;
import com.android.systemui.DejankUtils;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.Optional;
+import javax.inject.Inject;
+
/**
* Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret,
* app ops icon, etc) are handled elsewhere.
@@ -35,15 +38,19 @@ import java.util.Optional;
public final class NotificationClicker implements View.OnClickListener {
private static final String TAG = "NotificationClicker";
- private final Optional<StatusBar> mStatusBar;
private final BubbleController mBubbleController;
+ private final NotificationClickerLogger mLogger;
+ private final Optional<StatusBar> mStatusBar;
private final NotificationActivityStarter mNotificationActivityStarter;
- public NotificationClicker(Optional<StatusBar> statusBar,
+ private NotificationClicker(
BubbleController bubbleController,
+ NotificationClickerLogger logger,
+ Optional<StatusBar> statusBar,
NotificationActivityStarter notificationActivityStarter) {
- mStatusBar = statusBar;
mBubbleController = bubbleController;
+ mLogger = logger;
+ mStatusBar = statusBar;
mNotificationActivityStarter = notificationActivityStarter;
}
@@ -58,25 +65,26 @@ public final class NotificationClicker implements View.OnClickListener {
SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final StatusBarNotification sbn = row.getEntry().getSbn();
- if (sbn == null) {
- Log.e(TAG, "NotificationClicker called on an unclickable notification,");
- return;
- }
+ final NotificationEntry entry = row.getEntry();
+ mLogger.logOnClick(entry);
// Check if the notification is displaying the menu, if so slide notification back
if (isMenuVisible(row)) {
+ mLogger.logMenuVisible(entry);
row.animateTranslateNotification(0);
return;
} else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
+ mLogger.logParentMenuVisible(entry);
row.getNotificationParent().animateTranslateNotification(0);
return;
} else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
// We never want to open the app directly if the user clicks in between
// the notifications.
+ mLogger.logChildrenExpanded(entry);
return;
} else if (row.areGutsExposed()) {
// ignore click if guts are exposed
+ mLogger.logGutsExposed(entry);
return;
}
@@ -88,7 +96,7 @@ public final class NotificationClicker implements View.OnClickListener {
mBubbleController.collapseStack();
}
- mNotificationActivityStarter.onNotificationClicked(sbn, row);
+ mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
}
private boolean isMenuVisible(ExpandableNotificationRow row) {
@@ -107,4 +115,30 @@ public final class NotificationClicker implements View.OnClickListener {
row.setOnClickListener(null);
}
}
+
+ /** Daggerized builder for NotificationClicker. */
+ public static class Builder {
+ private final BubbleController mBubbleController;
+ private final NotificationClickerLogger mLogger;
+
+ @Inject
+ public Builder(
+ BubbleController bubbleController,
+ NotificationClickerLogger logger) {
+ mBubbleController = bubbleController;
+ mLogger = logger;
+ }
+
+ /** Builds an instance. */
+ public NotificationClicker build(
+ Optional<StatusBar> statusBar,
+ NotificationActivityStarter notificationActivityStarter
+ ) {
+ return new NotificationClicker(
+ mBubbleController,
+ mLogger,
+ statusBar,
+ notificationActivityStarter);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
new file mode 100644
index 000000000000..fbf033bd2291
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+class NotificationClickerLogger @Inject constructor(
+ @NotifInteractionLog private val buffer: LogBuffer
+) {
+ fun logOnClick(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ str2 = entry.ranking.channel.id
+ }, {
+ "CLICK $str1 (channel=$str2)"
+ })
+ }
+
+ fun logMenuVisible(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; menu is visible"
+ })
+ }
+
+ fun logParentMenuVisible(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; parent menu is visible"
+ })
+ }
+
+ fun logChildrenExpanded(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; children are expanded"
+ })
+ }
+
+ fun logGutsExposed(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; guts are exposed"
+ })
+ }
+}
+
+private const val TAG = "NotificationClicker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 5236385b3716..cb0c2838c24d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -378,6 +378,7 @@ public final class NotificationEntry extends ListEntry {
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
+ @Nullable
public Notification.BubbleMetadata getBubbleMetadata() {
return mBubbleMetadata;
}
@@ -385,7 +386,7 @@ public final class NotificationEntry extends ListEntry {
/**
* Sets bubble metadata for this notification.
*/
- public void setBubbleMetadata(Notification.BubbleMetadata metadata) {
+ public void setBubbleMetadata(@Nullable Notification.BubbleMetadata metadata) {
mBubbleMetadata = metadata;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 7f2f898565d8..c9754048e1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.BubbleController
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationListener
@@ -62,12 +61,12 @@ class NotificationsControllerImpl @Inject constructor(
private val deviceProvisionedController: DeviceProvisionedController,
private val notificationRowBinder: NotificationRowBinderImpl,
private val remoteInputUriController: RemoteInputUriController,
- private val bubbleController: BubbleController,
private val groupManager: NotificationGroupManager,
private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
private val headsUpManager: HeadsUpManager,
private val headsUpController: HeadsUpController,
- private val headsUpViewBinder: HeadsUpViewBinder
+ private val headsUpViewBinder: HeadsUpViewBinder,
+ private val clickerBuilder: NotificationClicker.Builder
) : NotificationsController {
override fun initialize(
@@ -87,10 +86,7 @@ class NotificationsControllerImpl @Inject constructor(
listController.bind()
notificationRowBinder.setNotificationClicker(
- NotificationClicker(
- Optional.of(statusBar),
- bubbleController,
- notificationActivityStarter))
+ clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter))
notificationRowBinder.setUpWithPresenter(
presenter,
listContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index 6d14ccf85716..9b6ae9a7f99d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -175,7 +175,7 @@ public class HeadsUpController {
private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
@Override
public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
- if (!isHeadsUp) {
+ if (!isHeadsUp && !entry.getRow().isRemoved()) {
mHeadsUpViewBinder.unbindHeadsUpView(entry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 55a593541819..bcc81a8b967f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -31,6 +31,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -98,7 +99,7 @@ public class NotificationConversationInfo extends LinearLayout implements
private ShortcutInfo mShortcutInfo;
private String mConversationId;
private StatusBarNotification mSbn;
- private Notification.BubbleMetadata mBubbleMetadata;
+ @Nullable private Notification.BubbleMetadata mBubbleMetadata;
private Context mUserContext;
private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
private boolean mIsDeviceProvisioned;
@@ -203,6 +204,7 @@ public class NotificationConversationInfo extends LinearLayout implements
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
+ Notification.BubbleMetadata bubbleMetadata,
OnSettingsClickListener onSettingsClick,
OnSnoozeClickListener onSnoozeClickListener,
ConversationIconFactory conversationIconFactory,
@@ -224,7 +226,7 @@ public class NotificationConversationInfo extends LinearLayout implements
mOnSnoozeClickListener = onSnoozeClickListener;
mIconFactory = conversationIconFactory;
mUserContext = userContext;
- mBubbleMetadata = entry.getBubbleMetadata();
+ mBubbleMetadata = bubbleMetadata;
mBuilderProvider = builderProvider;
mShortcutManager = shortcutManager;
@@ -538,7 +540,8 @@ public class NotificationConversationInfo extends LinearLayout implements
Log.e(TAG, "Could not check conversation senders", e);
}
- boolean showAsBubble = mBubbleMetadata.getAutoExpandBubble()
+ boolean showAsBubble = mBubbleMetadata != null
+ && mBubbleMetadata.getAutoExpandBubble()
&& Settings.Global.getInt(mContext.getContentResolver(),
NOTIFICATION_BUBBLES, 0) == 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 624fabc0a496..1c808cf90321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -366,7 +366,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
final ExpandableNotificationRow row,
NotificationConversationInfo notificationInfoView) throws Exception {
NotificationGuts guts = row.getGuts();
- StatusBarNotification sbn = row.getEntry().getSbn();
+ NotificationEntry entry = row.getEntry();
+ StatusBarNotification sbn = entry.getSbn();
String packageName = sbn.getPackageName();
// Settings link is only valid for notifications that specify a non-system user
NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
@@ -407,8 +408,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mNotificationManager,
mVisualStabilityManager,
packageName,
- row.getEntry().getChannel(),
- row.getEntry(),
+ entry.getChannel(),
+ entry,
+ entry.getBubbleMetadata(),
onSettingsClick,
onSnoozeClickListener,
iconFactoryLoader,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 2da2724aacb2..b96cff830f31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -187,8 +188,10 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
com.android.systemui.R.id.quick_qs_panel);
StatusBarNotification sbn = mRow.getEntry().getSbn();
Notification notif = sbn.getNotification();
+ Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
panel.getMediaPlayer().setMediaSession(token,
- notif.getSmallIcon(),
+ iconDrawable,
+ notif.getLargeIcon(),
tintColor,
mBackgroundColor,
mActions,
@@ -198,7 +201,8 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
com.android.systemui.R.id.quick_settings_panel);
bigPanel.addMediaSession(token,
- notif.getSmallIcon(),
+ iconDrawable,
+ notif.getLargeIcon(),
tintColor,
mBackgroundColor,
mActions,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 7d422e3c15a2..f9119c7a010f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -53,11 +53,13 @@ import android.view.WindowManagerGlobal;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -74,7 +76,7 @@ import java.util.concurrent.Executor;
/**
* Utility class to handle edge swipes for back gesture
*/
-public class EdgeBackGestureHandler implements DisplayListener,
+public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener,
PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = "EdgeBackGestureHandler";
@@ -165,6 +167,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
private boolean mIsGesturalModeEnabled;
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
+ private boolean mIsBackGestureAllowed;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -200,7 +203,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
SysUiState sysUiFlagContainer, PluginManager pluginManager) {
- final Resources res = context.getResources();
+ super(Dependency.get(BroadcastDispatcher.class));
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = context.getMainExecutor();
@@ -216,20 +219,30 @@ public class EdgeBackGestureHandler implements DisplayListener,
ViewConfiguration.getLongPressTimeout());
mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
- mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res));
+ mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources);
- updateCurrentUserResources(res);
+ updateCurrentUserResources();
sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
}
- public void updateCurrentUserResources(Resources res) {
+ public void updateCurrentUserResources() {
+ Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext()
+ .getResources();
mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+ mIsBackGestureAllowed =
+ !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
mBottomGestureHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_height);
}
+ @Override
+ public void onUserSwitched(int newUserId) {
+ updateIsEnabled();
+ updateCurrentUserResources();
+ }
+
/**
* @see NavigationBarView#onAttachedToWindow()
*/
@@ -243,6 +256,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME),
false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL);
updateIsEnabled();
+ startTracking();
}
/**
@@ -255,6 +269,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
}
mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver);
updateIsEnabled();
+ stopTracking();
}
private void setRotationCallbacks(boolean enable) {
@@ -269,10 +284,13 @@ public class EdgeBackGestureHandler implements DisplayListener,
}
}
- public void onNavigationModeChanged(int mode, Context currentUserContext) {
+ /**
+ * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
+ */
+ public void onNavigationModeChanged(int mode) {
mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
updateIsEnabled();
- updateCurrentUserResources(currentUserContext.getResources());
+ updateCurrentUserResources();
}
public void onNavBarTransientStateChanged(boolean isTransient) {
@@ -363,6 +381,10 @@ public class EdgeBackGestureHandler implements DisplayListener,
updateDisplaySize();
}
+ public boolean isHandlingGestures() {
+ return mIsEnabled && mIsBackGestureAllowed;
+ }
+
private WindowManager.LayoutParams createLayoutParams() {
Resources resources = mContext.getResources();
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
@@ -469,9 +491,9 @@ public class EdgeBackGestureHandler implements DisplayListener,
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
mLogGesture = false;
mInRejectedExclusion = false;
- mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
- && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
- && !mDisabledForQuickstep;
+ mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
+ && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
+ && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
mEdgeBackPlugin.onMotionEvent(ev);
@@ -599,6 +621,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
public void dump(PrintWriter pw) {
pw.println("EdgeBackGestureHandler:");
pw.println(" mIsEnabled=" + mIsEnabled);
+ pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed);
pw.println(" mAllowGesture=" + mAllowGesture);
pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep);
pw.println(" mInRejectedExclusion" + mInRejectedExclusion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 90bc075b399d..ae7867d68af4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -85,6 +85,8 @@ import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
import com.android.systemui.tuner.TunerService;
+import java.util.concurrent.Executor;
+
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
@@ -553,7 +555,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
if (!mKeyguardStateController.canDismissLockScreen()) {
- AsyncTask.execute(runnable);
+ Dependency.get(Executor.class).execute(runnable);
} else {
boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
&& Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 82e02b47974c..39949c82661f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -400,6 +400,9 @@ public class KeyguardBouncer {
mExpansionCallback.onFullyHidden();
} else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
mExpansionCallback.onStartingToHide();
+ if (mKeyguardView != null) {
+ mKeyguardView.onStartingToHide();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d35e1e1e176a..3e5eb5fba8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -33,6 +33,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.policy.BatteryController;
import java.io.FileDescriptor;
@@ -58,6 +59,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
private int mStatusBarMode;
private int mNavigationBarMode;
+ private int mNavigationMode;
private final Color mDarkModeColor;
/**
@@ -84,11 +86,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
@Inject
public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher,
- BatteryController batteryController) {
+ BatteryController batteryController, NavigationModeController navModeController) {
mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
mBatteryController.addCallback(this);
+ mNavigationMode = navModeController.addListener((mode) -> {
+ mNavigationMode = mode;
+ });
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -234,7 +239,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
}
private void updateNavigation() {
- if (mNavigationBarController != null) {
+ if (mNavigationBarController != null
+ && !QuickStepContract.isGesturalMode(mNavigationMode)) {
mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 84aecd4e0759..2978772cac5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -123,7 +123,7 @@ public class NavigationBarView extends FrameLayout implements
private KeyButtonDrawable mRecentIcon;
private KeyButtonDrawable mDockedIcon;
- private final EdgeBackGestureHandler mEdgeBackGestureHandler;
+ private EdgeBackGestureHandler mEdgeBackGestureHandler;
private final DeadZone mDeadZone;
private boolean mDeadZoneConsuming = false;
private final NavigationBarTransitions mBarTransitions;
@@ -244,7 +244,7 @@ public class NavigationBarView extends FrameLayout implements
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
// When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
// gestural mode, the entire nav bar should be touchable.
- if (!isGesturalMode(mNavBarMode) || mImeVisible) {
+ if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) {
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
return;
}
@@ -296,8 +296,6 @@ public class NavigationBarView extends FrameLayout implements
R.style.RotateButtonCCWStart90,
isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
- final ContextualButton backButton = new ContextualButton(R.id.back, 0);
-
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
@@ -305,7 +303,7 @@ public class NavigationBarView extends FrameLayout implements
mScreenPinningNotify = new ScreenPinningNotify(mContext);
mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
- mButtonDispatchers.put(R.id.back, backButton);
+ mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
@@ -659,7 +657,7 @@ public class NavigationBarView extends FrameLayout implements
boolean disableHomeHandle = disableRecent
&& ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
- boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
+ boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
// When screen pinning, don't hide back and home when connected service or back and
@@ -686,9 +684,9 @@ public class NavigationBarView extends FrameLayout implements
}
}
- getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
- getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
- getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
+ getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
+ getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
+ getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
}
@@ -838,10 +836,9 @@ public class NavigationBarView extends FrameLayout implements
@Override
public void onNavigationModeChanged(int mode) {
- Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext();
mNavBarMode = mode;
mBarTransitions.onNavigationModeChanged(mNavBarMode);
- mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
+ mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
@@ -864,6 +861,7 @@ public class NavigationBarView extends FrameLayout implements
@Override
public void onFinishInflate() {
+ super.onFinishInflate();
mNavigationInflaterView = findViewById(R.id.navigation_inflater);
mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index d24ccf343a3a..6061b1e73d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -17,9 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED;
-import static android.os.UserHandle.USER_CURRENT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
@@ -38,17 +35,14 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
-import android.util.SparseBooleanArray;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -70,104 +64,34 @@ public class NavigationModeController implements Dumpable {
private final Context mContext;
private Context mCurrentUserContext;
private final IOverlayManager mOverlayManager;
- private final DeviceProvisionedController mDeviceProvisionedController;
private final Executor mUiBgExecutor;
- private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
-
private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case ACTION_OVERLAY_CHANGED:
if (DEBUG) {
Log.d(TAG, "ACTION_OVERLAY_CHANGED");
}
updateCurrentInteractionMode(true /* notify */);
- break;
- }
}
};
- private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- if (DEBUG) {
- Log.d(TAG, "onDeviceProvisionedChanged: "
- + mDeviceProvisionedController.isDeviceProvisioned());
- }
- // Once the device has been provisioned, check if we can restore gestural nav
- restoreGesturalNavOverlayIfNecessary();
- }
-
- @Override
- public void onUserSetupChanged() {
- if (DEBUG) {
- Log.d(TAG, "onUserSetupChanged: "
- + mDeviceProvisionedController.isCurrentUserSetup());
- }
- // Once the user has been setup, check if we can restore gestural nav
- restoreGesturalNavOverlayIfNecessary();
- }
-
- @Override
- public void onUserSwitched() {
- if (DEBUG) {
- Log.d(TAG, "onUserSwitched: "
- + ActivityManagerWrapper.getInstance().getCurrentUserId());
- }
-
- // Update the nav mode for the current user
- updateCurrentInteractionMode(true /* notify */);
-
- // When switching users, defer enabling the gestural nav overlay until the user
- // is all set up
- deferGesturalNavOverlayIfNecessary();
- }
- };
-
@Inject
- public NavigationModeController(Context context,
- DeviceProvisionedController deviceProvisionedController,
- @UiBackground Executor uiBgExecutor) {
+ public NavigationModeController(Context context, @UiBackground Executor uiBgExecutor) {
mContext = context;
mCurrentUserContext = context;
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
mUiBgExecutor = uiBgExecutor;
- mDeviceProvisionedController = deviceProvisionedController;
- mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
overlayFilter.addDataScheme("package");
overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
- IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null,
- null);
-
updateCurrentInteractionMode(false /* notify */);
-
- // Check if we need to defer enabling gestural nav
- deferGesturalNavOverlayIfNecessary();
- }
-
- private boolean setGestureModeOverlayForMainLauncher() {
- if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) {
- // Already in gesture mode
- return true;
- }
-
- Log.d(TAG, "Switching system navigation to full-gesture mode:"
- + " contextUser="
- + mCurrentUserContext.getUserId());
-
- setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
- return true;
}
public void updateCurrentInteractionMode(boolean notify) {
@@ -176,10 +100,9 @@ public class NavigationModeController implements Dumpable {
if (mode == NAV_BAR_MODE_GESTURAL) {
switchToDefaultGestureNavOverlayIfNecessary();
}
- mUiBgExecutor.execute(() -> {
+ mUiBgExecutor.execute(() ->
Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
- Secure.NAVIGATION_MODE, String.valueOf(mode));
- });
+ Secure.NAVIGATION_MODE, String.valueOf(mode)));
if (DEBUG) {
Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode);
dumpAssetPaths(mCurrentUserContext);
@@ -230,61 +153,11 @@ public class NavigationModeController implements Dumpable {
}
}
- private void deferGesturalNavOverlayIfNecessary() {
- final int userId = mDeviceProvisionedController.getCurrentUser();
- mRestoreGesturalNavBarMode.put(userId, false);
- if (mDeviceProvisionedController.isDeviceProvisioned()
- && mDeviceProvisionedController.isCurrentUserSetup()) {
- // User is already setup and device is provisioned, nothing to do
- if (DEBUG) {
- Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
- + "setup");
- }
- return;
- }
-
- ArrayList<String> defaultOverlays = new ArrayList<>();
- try {
- defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
- } catch (RemoteException e) {
- Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
- }
- if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
- // No default gesture nav overlay
- if (DEBUG) {
- Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
- + "default=" + defaultOverlays);
- }
- return;
- }
-
- // If the default is gestural, force-enable three button mode until the device is
- // provisioned
- setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
- mRestoreGesturalNavBarMode.put(userId, true);
-
- if (DEBUG) {
- Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
- }
- }
-
- private void restoreGesturalNavOverlayIfNecessary() {
- if (DEBUG) {
- Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
- + mRestoreGesturalNavBarMode);
- }
- final int userId = mDeviceProvisionedController.getCurrentUser();
- if (mRestoreGesturalNavBarMode.get(userId)) {
- // Restore the gestural state if necessary
- setGestureModeOverlayForMainLauncher();
- mRestoreGesturalNavBarMode.put(userId, false);
- }
- }
-
private void switchToDefaultGestureNavOverlayIfNecessary() {
final int userId = mCurrentUserContext.getUserId();
try {
- final IOverlayManager om = mOverlayManager;
+ final IOverlayManager om = IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE));
final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
if (info != null && !info.isEnabled()) {
// Enable the default gesture nav overlay, and move the back gesture inset scale to
@@ -309,20 +182,6 @@ public class NavigationModeController implements Dumpable {
}
}
- public void setModeOverlay(String overlayPkg, int userId) {
- mUiBgExecutor.execute(() -> {
- try {
- mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
- if (DEBUG) {
- Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg
- + " userId=" + userId);
- }
- } catch (SecurityException | IllegalStateException | RemoteException e) {
- Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
- }
- });
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NavigationModeController:");
@@ -334,11 +193,6 @@ public class NavigationModeController implements Dumpable {
defaultOverlays = "failed_to_fetch";
}
pw.println(" defaultOverlays=" + defaultOverlays);
- pw.println(" restoreGesturalNavMode:");
- for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) {
- pw.println(" userId=" + mRestoreGesturalNavBarMode.keyAt(i)
- + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i));
- }
dumpAssetPaths(mCurrentUserContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index 1a6b415f87db..bf52a7ae2bf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -148,11 +148,6 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
updateSamplingRect();
}
- private void postUpdateSamplingListener() {
- mHandler.removeCallbacks(mUpdateSamplingListener);
- mHandler.post(mUpdateSamplingListener);
- }
-
private void updateSamplingListener() {
boolean isSamplingEnabled = mSamplingEnabled
&& !mSamplingRequestBounds.isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 53fa2630a9c3..fbe3e9b19248 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -40,7 +40,6 @@ import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
-import android.util.Log;
import android.view.RemoteAnimationAdapter;
import android.view.View;
@@ -91,92 +90,119 @@ import dagger.Lazy;
*/
public class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
- private static final String TAG = "NotifActivityStarter";
- protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Context mContext;
+
+ private final CommandQueue mCommandQueue;
+ private final Handler mMainThreadHandler;
+ private final Handler mBackgroundHandler;
+ private final Executor mUiBgExecutor;
+ private final NotificationEntryManager mEntryManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final HeadsUpManagerPhone mHeadsUpManager;
+ private final ActivityStarter mActivityStarter;
+ private final IStatusBarService mBarService;
+ private final StatusBarStateController mStatusBarStateController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardManager mKeyguardManager;
+ private final IDreamManager mDreamManager;
+ private final BubbleController mBubbleController;
private final Lazy<AssistManager> mAssistManagerLazy;
- private final NotificationGroupManager mGroupManager;
- private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final NotificationGroupManager mGroupManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final ShadeController mShadeController;
- private final StatusBar mStatusBar;
private final KeyguardStateController mKeyguardStateController;
- private final ActivityStarter mActivityStarter;
- private final NotificationEntryManager mEntryManager;
- private final NotifPipeline mNotifPipeline;
- private final NotifCollection mNotifCollection;
- private final FeatureFlags mFeatureFlags;
- private final StatusBarStateController mStatusBarStateController;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+ private final LockPatternUtils mLockPatternUtils;
+ private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
+ private final ActivityIntentHelper mActivityIntentHelper;
+
+ private final FeatureFlags mFeatureFlags;
private final MetricsLogger mMetricsLogger;
- private final Context mContext;
- private final NotificationPanelViewController mNotificationPanel;
+ private final StatusBarNotificationActivityStarterLogger mLogger;
+
+ private final StatusBar mStatusBar;
private final NotificationPresenter mPresenter;
- private final LockPatternUtils mLockPatternUtils;
- private final HeadsUpManagerPhone mHeadsUpManager;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final KeyguardManager mKeyguardManager;
+ private final NotificationPanelViewController mNotificationPanel;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final IStatusBarService mBarService;
- private final CommandQueue mCommandQueue;
- private final IDreamManager mDreamManager;
- private final Handler mMainThreadHandler;
- private final Handler mBackgroundHandler;
- private final ActivityIntentHelper mActivityIntentHelper;
- private final BubbleController mBubbleController;
- private final Executor mUiBgExecutor;
private boolean mIsCollapsingToShowActivityOverLockscreen;
- private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue,
- Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel,
- NotificationPresenter presenter, NotificationEntryManager entryManager,
- HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter,
- ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService,
+ private StatusBarNotificationActivityStarter(
+ Context context,
+ CommandQueue commandQueue,
+ Handler mainThreadHandler,
+ Handler backgroundHandler,
+ Executor uiBgExecutor,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
+ HeadsUpManagerPhone headsUpManager,
+ ActivityStarter activityStarter,
+ IStatusBarService statusBarService,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
- IDreamManager dreamManager, NotificationRemoteInputManager remoteInputManager,
- StatusBarRemoteInputCallback remoteInputCallback, NotificationGroupManager groupManager,
+ IDreamManager dreamManager,
+ BubbleController bubbleController,
+ Lazy<AssistManager> assistManagerLazy,
+ NotificationRemoteInputManager remoteInputManager,
+ NotificationGroupManager groupManager,
NotificationLockscreenUserManager lockscreenUserManager,
- ShadeController shadeController, StatusBar statusBar,
+ ShadeController shadeController,
KeyguardStateController keyguardStateController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
- Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
- ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
- FeatureFlags featureFlags, NotifPipeline notifPipeline,
- NotifCollection notifCollection) {
+ LockPatternUtils lockPatternUtils,
+ StatusBarRemoteInputCallback remoteInputCallback,
+ ActivityIntentHelper activityIntentHelper,
+
+ FeatureFlags featureFlags,
+ MetricsLogger metricsLogger,
+ StatusBarNotificationActivityStarterLogger logger,
+
+ StatusBar statusBar,
+ NotificationPresenter presenter,
+ NotificationPanelViewController panel,
+ ActivityLaunchAnimator activityLaunchAnimator) {
mContext = context;
- mNotificationPanel = panel;
- mPresenter = presenter;
+ mCommandQueue = commandQueue;
+ mMainThreadHandler = mainThreadHandler;
+ mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
+ mEntryManager = entryManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
mHeadsUpManager = headsUpManager;
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mActivityStarter = activityStarter;
mBarService = statusBarService;
- mCommandQueue = commandQueue;
+ mStatusBarStateController = statusBarStateController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
+ mBubbleController = bubbleController;
+ mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
+ mGroupManager = groupManager;
mLockscreenUserManager = lockscreenUserManager;
mShadeController = shadeController;
- // TODO: use KeyguardStateController#isOccluded to remove this dependency
- mStatusBar = statusBar;
mKeyguardStateController = keyguardStateController;
- mActivityStarter = activityStarter;
- mEntryManager = entryManager;
- mStatusBarStateController = statusBarStateController;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mMetricsLogger = metricsLogger;
- mAssistManagerLazy = assistManagerLazy;
- mGroupManager = groupManager;
mLockPatternUtils = lockPatternUtils;
- mBackgroundHandler = backgroundHandler;
- mUiBgExecutor = uiBgExecutor;
+ mStatusBarRemoteInputCallback = remoteInputCallback;
+ mActivityIntentHelper = activityIntentHelper;
+
mFeatureFlags = featureFlags;
- mNotifPipeline = notifPipeline;
- mNotifCollection = notifCollection;
+ mMetricsLogger = metricsLogger;
+ mLogger = logger;
+
+ // TODO: use KeyguardStateController#isOccluded to remove this dependency
+ mStatusBar = statusBar;
+ mPresenter = presenter;
+ mNotificationPanel = panel;
+ mActivityLaunchAnimator = activityLaunchAnimator;
+
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -192,11 +218,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
});
}
-
- mStatusBarRemoteInputCallback = remoteInputCallback;
- mMainThreadHandler = mainThreadHandler;
- mActivityIntentHelper = activityIntentHelper;
- mBubbleController = bubbleController;
}
/**
@@ -207,6 +228,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
*/
@Override
public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+ mLogger.logStartingActivityFromClick(sbn.getKey());
+
RemoteInputController controller = mRemoteInputManager.getController();
if (controller.isRemoteInputActive(row.getEntry())
&& !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
@@ -225,7 +248,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
// The only valid case is Bubble notifications. Guard against other cases
// entering here.
if (intent == null && !isBubble) {
- Log.e(TAG, "onNotificationClicked called for non-clickable notification!");
+ mLogger.logNonClickableNotification(sbn.getKey());
return;
}
@@ -258,6 +281,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent,
boolean wasOccluded,
boolean showOverLockscreen) {
+ mLogger.logHandleClickAfterKeyguardDismissed(sbn.getKey());
+
// TODO: Some of this code may be able to move to NotificationEntryManager.
if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(sbn.getKey())) {
// Release the HUN notification to the shade.
@@ -304,6 +329,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent,
boolean wasOccluded,
NotificationEntry parentToCancelFinal) {
+ mLogger.logHandleClickAfterPanelCollapsed(sbn.getKey());
+
String notificationKey = sbn.getKey();
try {
// The intent we are sending is for the application, which
@@ -343,9 +370,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
remoteInputText.toString());
}
if (isBubble) {
+ mLogger.logExpandingBubble(notificationKey);
expandBubbleStackOnMainThread(notificationKey);
} else {
- startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
+ startNotificationIntent(
+ intent, fillInIntent, entry, row, wasOccluded, isActivityIntent);
}
if (isActivityIntent || isBubble) {
mAssistManagerLazy.get().hideAssist();
@@ -392,10 +421,16 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
}
- private void startNotificationIntent(PendingIntent intent, Intent fillInIntent,
- View row, boolean wasOccluded, boolean isActivityIntent) {
+ private void startNotificationIntent(
+ PendingIntent intent,
+ Intent fillInIntent,
+ NotificationEntry entry,
+ View row,
+ boolean wasOccluded,
+ boolean isActivityIntent) {
RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
wasOccluded);
+ mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
if (adapter != null) {
ActivityTaskManager.getService()
@@ -408,7 +443,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
} catch (RemoteException | PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
+ mLogger.logSendingIntentFailed(e);
// TODO: Dismiss Keyguard.
}
}
@@ -438,13 +473,9 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private void handleFullScreenIntent(NotificationEntry entry) {
if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
if (shouldSuppressFullScreenIntent(entry)) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.getKey());
- }
+ mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey());
} else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.getKey());
- }
+ mLogger.logFullScreenIntentNotImportantEnough(entry.getKey());
} else {
// Stop screensaver if the notification has a fullscreen intent.
// (like an incoming phone call)
@@ -457,13 +488,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
});
// not immersive & a fullscreen alert should be shown
- if (DEBUG) {
- Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
- }
+ final PendingIntent fullscreenIntent =
+ entry.getSbn().getNotification().fullScreenIntent;
+ mLogger.logSendingFullScreenIntent(entry.getKey(), fullscreenIntent);
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
- entry.getSbn().getNotification().fullScreenIntent.send();
+ fullscreenIntent.send();
entry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
} catch (PendingIntent.CanceledException e) {
@@ -578,9 +609,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
public static class Builder {
private final Context mContext;
private final CommandQueue mCommandQueue;
- private final Lazy<AssistManager> mAssistManagerLazy;
+ private final Handler mMainThreadHandler;
+ private final Handler mBackgroundHandler;
+ private final Executor mUiBgExecutor;
private final NotificationEntryManager mEntryManager;
- private final FeatureFlags mFeatureFlags;
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
@@ -590,30 +622,37 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
+ private final BubbleController mBubbleController;
+ private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
- private final StatusBarRemoteInputCallback mRemoteInputCallback;
private final NotificationGroupManager mGroupManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
- private final MetricsLogger mMetricsLogger;
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final LockPatternUtils mLockPatternUtils;
- private final Handler mMainThreadHandler;
- private final Handler mBackgroundHandler;
- private final Executor mUiBgExecutor;
+ private final StatusBarRemoteInputCallback mRemoteInputCallback;
private final ActivityIntentHelper mActivityIntentHelper;
- private final BubbleController mBubbleController;
- private NotificationPanelViewController mNotificationPanelViewController;
- private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
- private final ShadeController mShadeController;
+
+ private final FeatureFlags mFeatureFlags;
+ private final MetricsLogger mMetricsLogger;
+ private final StatusBarNotificationActivityStarterLogger mLogger;
+
+ private StatusBar mStatusBar;
private NotificationPresenter mNotificationPresenter;
+ private NotificationPanelViewController mNotificationPanelViewController;
private ActivityLaunchAnimator mActivityLaunchAnimator;
- private StatusBar mStatusBar;
@Inject
- public Builder(Context context,
+ public Builder(
+ Context context,
CommandQueue commandQueue,
- Lazy<AssistManager> assistManagerLazy,
+ @Main Handler mainThreadHandler,
+ @Background Handler backgroundHandler,
+ @UiBackground Executor uiBgExecutor,
NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
IStatusBarService statusBarService,
@@ -621,27 +660,30 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
+ BubbleController bubbleController,
+ Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
- StatusBarRemoteInputCallback remoteInputCallback,
NotificationGroupManager groupManager,
NotificationLockscreenUserManager lockscreenUserManager,
+ ShadeController shadeController,
KeyguardStateController keyguardStateController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
- @Main Handler mainThreadHandler,
- @Background Handler backgroundHandler,
- @UiBackground Executor uiBgExecutor,
+ StatusBarRemoteInputCallback remoteInputCallback,
ActivityIntentHelper activityIntentHelper,
- BubbleController bubbleController,
- ShadeController shadeController,
+
FeatureFlags featureFlags,
- NotifPipeline notifPipeline,
- NotifCollection notifCollection) {
+ MetricsLogger metricsLogger,
+ StatusBarNotificationActivityStarterLogger logger) {
+
mContext = context;
mCommandQueue = commandQueue;
- mAssistManagerLazy = assistManagerLazy;
+ mMainThreadHandler = mainThreadHandler;
+ mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
mEntryManager = entryManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
mHeadsUpManager = headsUpManager;
mActivityStarter = activityStarter;
mStatusBarService = statusBarService;
@@ -649,23 +691,21 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
+ mBubbleController = bubbleController;
+ mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
- mRemoteInputCallback = remoteInputCallback;
mGroupManager = groupManager;
mLockscreenUserManager = lockscreenUserManager;
+ mShadeController = shadeController;
mKeyguardStateController = keyguardStateController;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mMetricsLogger = metricsLogger;
mLockPatternUtils = lockPatternUtils;
- mMainThreadHandler = mainThreadHandler;
- mBackgroundHandler = backgroundHandler;
- mUiBgExecutor = uiBgExecutor;
+ mRemoteInputCallback = remoteInputCallback;
mActivityIntentHelper = activityIntentHelper;
- mBubbleController = bubbleController;
- mShadeController = shadeController;
+
mFeatureFlags = featureFlags;
- mNotifPipeline = notifPipeline;
- mNotifCollection = notifCollection;
+ mMetricsLogger = metricsLogger;
+ mLogger = logger;
}
/** Sets the status bar to use as {@link StatusBar}. */
@@ -692,37 +732,42 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
public StatusBarNotificationActivityStarter build() {
- return new StatusBarNotificationActivityStarter(mContext,
- mCommandQueue, mAssistManagerLazy,
- mNotificationPanelViewController,
- mNotificationPresenter,
+ return new StatusBarNotificationActivityStarter(
+ mContext,
+ mCommandQueue,
+ mMainThreadHandler,
+ mBackgroundHandler,
+ mUiBgExecutor,
mEntryManager,
+ mNotifPipeline,
+ mNotifCollection,
mHeadsUpManager,
mActivityStarter,
- mActivityLaunchAnimator,
mStatusBarService,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
mKeyguardManager,
mDreamManager,
+ mBubbleController,
+ mAssistManagerLazy,
mRemoteInputManager,
- mRemoteInputCallback,
mGroupManager,
mLockscreenUserManager,
mShadeController,
- mStatusBar,
mKeyguardStateController,
mNotificationInterruptStateProvider,
- mMetricsLogger,
mLockPatternUtils,
- mMainThreadHandler,
- mBackgroundHandler,
- mUiBgExecutor,
+ mRemoteInputCallback,
mActivityIntentHelper,
- mBubbleController,
+
mFeatureFlags,
- mNotifPipeline,
- mNotifCollection);
+ mMetricsLogger,
+ mLogger,
+
+ mStatusBar,
+ mNotificationPresenter,
+ mNotificationPanelViewController,
+ mActivityLaunchAnimator);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
new file mode 100644
index 000000000000..d118747a0365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.PendingIntent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.NotifInteractionLog
+import javax.inject.Inject
+
+class StatusBarNotificationActivityStarterLogger @Inject constructor(
+ @NotifInteractionLog private val buffer: LogBuffer
+) {
+ fun logStartingActivityFromClick(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "(1/4) onNotificationClicked: $str1"
+ })
+ }
+
+ fun logHandleClickAfterKeyguardDismissed(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1"
+ })
+ }
+
+ fun logHandleClickAfterPanelCollapsed(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "(3/4) handleNotificationClickAfterPanelCollapsed: $str1"
+ })
+ }
+
+ fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = pendingIntent.intent.toString()
+ }, {
+ "(4/4) Starting $str2 for notification $str1"
+ })
+ }
+
+ fun logExpandingBubble(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "Expanding bubble for $str1 (rather than firing intent)"
+ })
+ }
+
+ fun logSendingIntentFailed(e: Exception) {
+ buffer.log(TAG, WARNING, {
+ str1 = e.toString()
+ }, {
+ "Sending contentIntentFailed: $str1"
+ })
+ }
+
+ fun logNonClickableNotification(key: String) {
+ buffer.log(TAG, ERROR, {
+ str1 = key
+ }, {
+ "onNotificationClicked called for non-clickable notification! $str1"
+ })
+ }
+
+ fun logFullScreenIntentSuppressedByDnD(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "No Fullscreen intent: suppressed by DND: $str1"
+ })
+ }
+
+ fun logFullScreenIntentNotImportantEnough(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "No Fullscreen intent: not important enough: $str1"
+ })
+ }
+
+ fun logSendingFullScreenIntent(key: String, pendingIntent: PendingIntent) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = pendingIntent.intent.toString()
+ }, {
+ "Notification $str1 has fullScreenIntent; sending fullScreenIntent $str2"
+ })
+ }
+}
+
+private const val TAG = "NotifActivityStarter"
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 051bd29bc323..a284335c972e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -357,7 +357,18 @@ public class NetworkControllerImpl extends BroadcastReceiver
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
+ // Initial setup of connectivity. Handled as if we had received a sticky broadcast of
+ // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION.
+ mReceiverHandler.post(this::updateConnectivity);
+
+ // Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
+ // of WifiManager.WIFI_STATE_CHANGED_ACTION or WifiManager.NETWORK_STATE_CHANGED_ACTION
+ mReceiverHandler.post(mWifiSignalController::fetchInitialState);
updateMobileControllers();
+
+ // Initial setup of emergency information. Handled as if we had received a sticky broadcast
+ // of TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
+ mReceiverHandler.post(this::recalculateEmergency);
}
private void unregisterListeners() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index b258fd47871a..5257ce4c6bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -102,6 +102,20 @@ public class WifiSignalController extends
}
/**
+ * Fetches wifi initial state replacing the initial sticky broadcast.
+ */
+ public void fetchInitialState() {
+ mWifiTracker.fetchInitialState();
+ mCurrentState.enabled = mWifiTracker.enabled;
+ mCurrentState.connected = mWifiTracker.connected;
+ mCurrentState.ssid = mWifiTracker.ssid;
+ mCurrentState.rssi = mWifiTracker.rssi;
+ mCurrentState.level = mWifiTracker.level;
+ mCurrentState.statusLabel = mWifiTracker.statusLabel;
+ notifyListenersIfNecessary();
+ }
+
+ /**
* Extract wifi state directly from broadcasts about changes in wifi state.
*/
public void handleBroadcast(Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
index d65b285adb0c..8880df9959c1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -115,7 +115,9 @@ abstract class RelativeTouchListener : View.OnTouchListener {
performedLongClick = false
handler.postDelayed({
- performedLongClick = v.performLongClick()
+ if (v.isLongClickable) {
+ performedLongClick = v.performLongClick()
+ }
}, ViewConfiguration.getLongPressTimeout().toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index 8625d63a3c7e..db08d64acc10 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -61,14 +61,17 @@ internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
/**
* Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided.
+ * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
*/
-private val defaultSpring = PhysicsAnimator.SpringConfig(
+private val globalDefaultSpring = PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_MEDIUM,
SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
-/** Default fling configuration to use for animations where friction was not provided. */
-private val defaultFling = PhysicsAnimator.FlingConfig(
+/**
+ * Default fling configuration to use for animations where friction was not provided, and a default
+ * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig].
+ */
+private val globalDefaultFling = PhysicsAnimator.FlingConfig(
friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
/** Whether to log helpful debug information about animations. */
@@ -111,6 +114,12 @@ class PhysicsAnimator<T> private constructor (val target: T) {
/** End actions to run when all animations have completed. */
private val endActions = ArrayList<EndAction>()
+ /** SpringConfig to use by default for properties whose springs were not provided. */
+ private var defaultSpring: SpringConfig = globalDefaultSpring
+
+ /** FlingConfig to use by default for properties whose fling configs were not provided. */
+ private var defaultFling: FlingConfig = globalDefaultFling
+
/**
* Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
* the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
@@ -204,6 +213,19 @@ class PhysicsAnimator<T> private constructor (val target: T) {
}
/**
+ * Springs a property to a given value using the provided configuration options, and a start
+ * velocity of 0f.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float
+ ): PhysicsAnimator<T> {
+ return spring(property, toPosition, 0f)
+ }
+
+ /**
* Flings a property using the given start velocity, using a [FlingAnimation] configured using
* the provided configuration settings.
*
@@ -392,6 +414,14 @@ class PhysicsAnimator<T> private constructor (val target: T) {
return this
}
+ fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
+ this.defaultSpring = defaultSpring
+ }
+
+ fun setDefaultFlingConfig(defaultFling: FlingConfig) {
+ this.defaultFling = defaultFling
+ }
+
/** Starts the animations! */
fun start() {
startAction()
@@ -752,7 +782,7 @@ class PhysicsAnimator<T> private constructor (val target: T) {
) {
constructor() :
- this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+ this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
@@ -782,10 +812,10 @@ class PhysicsAnimator<T> private constructor (val target: T) {
internal var startVelocity: Float
) {
- constructor() : this(defaultFling.friction)
+ constructor() : this(globalDefaultFling.friction)
constructor(friction: Float) :
- this(friction, defaultFling.min, defaultFling.max)
+ this(friction, globalDefaultFling.min, globalDefaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
this(friction, min, max, startVelocity = 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index cc6d607a60cf..8acfbf2b6996 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -137,6 +137,36 @@ public abstract class ConcurrencyModule {
}
/**
+ * Provide a Background-Thread Executor by default.
+ */
+ @Provides
+ @Singleton
+ public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
+ return new RepeatableExecutorImpl(exec);
+ }
+
+ /**
+ * Provide a Background-Thread Executor.
+ */
+ @Provides
+ @Singleton
+ @Background
+ public static RepeatableExecutor provideBackgroundRepeatableExecutor(
+ @Background DelayableExecutor exec) {
+ return new RepeatableExecutorImpl(exec);
+ }
+
+ /**
+ * Provide a Main-Thread Executor.
+ */
+ @Provides
+ @Singleton
+ @Main
+ public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
+ return new RepeatableExecutorImpl(exec);
+ }
+
+ /**
* Provide an Executor specifically for running UI operations on a separate thread.
*
* Keep submitted runnables short and to the point, just as with any other UI code.
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java
new file mode 100644
index 000000000000..aefdc992e831
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A sub-class of {@link Executor} that allows scheduling commands to execute periodically.
+ */
+public interface RepeatableExecutor extends Executor {
+
+ /**
+ * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with
+ * the given delay between the termination of one execution and the commencement of the next.
+ *
+ * Each invocation of the supplied Runnable will be scheduled after the previous invocation
+ * completes. For example, if you schedule the Runnable with a 60 second delay, and the Runnable
+ * itself takes 1 second, the effective delay will be 61 seconds between each invocation.
+ *
+ * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable,
+ * long, long)}
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ default Runnable executeRepeatedly(Runnable r, long initialDelayMillis, long delayMillis) {
+ return executeRepeatedly(r, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with
+ * the given delay between the termination of one execution and the commencement of the next..
+ *
+ * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable,
+ * long, long)}
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ Runnable executeRepeatedly(Runnable r, long initialDelay, long delay, TimeUnit unit);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java
new file mode 100644
index 000000000000..c03e10e5c981
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link RepeatableExecutor} for SystemUI.
+ */
+class RepeatableExecutorImpl implements RepeatableExecutor {
+
+ private final DelayableExecutor mExecutor;
+
+ RepeatableExecutorImpl(DelayableExecutor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ mExecutor.execute(command);
+ }
+
+ @Override
+ public Runnable executeRepeatedly(Runnable r, long initDelay, long delay, TimeUnit unit) {
+ ExecutionToken token = new ExecutionToken(r, delay, unit);
+ token.start(initDelay, unit);
+ return token::cancel;
+ }
+
+ private class ExecutionToken implements Runnable {
+ private final Runnable mCommand;
+ private final long mDelay;
+ private final TimeUnit mUnit;
+ private final Object mLock = new Object();
+ private Runnable mCancel;
+
+ ExecutionToken(Runnable r, long delay, TimeUnit unit) {
+ mCommand = r;
+ mDelay = delay;
+ mUnit = unit;
+ }
+
+ @Override
+ public void run() {
+ mCommand.run();
+ synchronized (mLock) {
+ if (mCancel != null) {
+ mCancel = mExecutor.executeDelayed(this, mDelay, mUnit);
+ }
+ }
+ }
+
+ /** Starts execution that will repeat the command until {@link cancel}. */
+ public void start(long startDelay, TimeUnit unit) {
+ synchronized (mLock) {
+ mCancel = mExecutor.executeDelayed(this, startDelay, unit);
+ }
+ }
+
+ /** Cancel repeated execution of command. */
+ public void cancel() {
+ synchronized (mLock) {
+ if (mCancel != null) {
+ mCancel.run();
+ mCancel = null;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
index e5da603321cd..899aabb2e9a7 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -32,6 +32,7 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DragEvent;
+import android.view.IScrollCaptureController;
import android.view.IWindow;
import android.view.IWindowManager;
import android.view.IWindowSession;
@@ -352,5 +353,14 @@ public class SystemWindows {
@Override
public void dispatchPointerCaptureChanged(boolean hasCapture) {}
+
+ @Override
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ try {
+ controller.onClientUnavailable();
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
index 25f279b45d04..dd5c8335eefa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewTest.java
@@ -17,30 +17,50 @@
package com.android.keyguard;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class KeyguardHostViewTest extends SysuiTestCase {
+ @Mock
+ private KeyguardSecurityContainer mSecurityContainer;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
private KeyguardHostView mKeyguardHostView;
@Before
public void setup() {
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
- mKeyguardHostView = new KeyguardHostView(getContext());
+ mKeyguardHostView = new KeyguardHostView(getContext()) {
+ @Override
+ protected void onFinishInflate() {
+ mSecurityContainer = KeyguardHostViewTest.this.mSecurityContainer;
+ mLockPatternUtils = KeyguardHostViewTest.this.mLockPatternUtils;
+ }
+ };
+ mKeyguardHostView.onFinishInflate();
}
@Test
@@ -50,4 +70,10 @@ public class KeyguardHostViewTest extends SysuiTestCase {
null /* cancelAction */);
Assert.assertTrue("Action should exist", mKeyguardHostView.hasDismissActions());
}
+
+ @Test
+ public void testOnStartingToHide() {
+ mKeyguardHostView.onStartingToHide();
+ verify(mSecurityContainer).onStartingToHide();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 7403a11fecbf..6c00ecacf97d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -61,6 +61,7 @@ import android.os.UserManager;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
@@ -133,20 +134,23 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private BroadcastDispatcher mBroadcastDispatcher;
@Mock
- private Executor mBackgroundExecutor;
- @Mock
private RingerModeTracker mRingerModeTracker;
@Mock
private LiveData<Integer> mRingerModeLiveData;
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ // Direct executor
+ private Executor mBackgroundExecutor = Runnable::run;
private TestableLooper mTestableLooper;
private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private TestableContext mSpiedContext;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
- TestableContext context = spy(mContext);
+ mSpiedContext = spy(mContext);
when(mPackageManager.hasSystemFeature(anyString())).thenReturn(true);
- when(context.getPackageManager()).thenReturn(mPackageManager);
+ when(mSpiedContext.getPackageManager()).thenReturn(mPackageManager);
doAnswer(invocation -> {
IBiometricEnabledOnKeyguardCallback callback = invocation.getArgument(0);
callback.onChanged(BiometricSourceType.FACE, true /* enabled */,
@@ -161,19 +165,20 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mStrongAuthTracker
.isUnlockingWithBiometricAllowed(anyBoolean() /* isStrongBiometric */))
.thenReturn(true);
- context.addMockSystemService(TrustManager.class, mTrustManager);
- context.addMockSystemService(FingerprintManager.class, mFingerprintManager);
- context.addMockSystemService(BiometricManager.class, mBiometricManager);
- context.addMockSystemService(FaceManager.class, mFaceManager);
- context.addMockSystemService(UserManager.class, mUserManager);
- context.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
- context.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
+ mSpiedContext.addMockSystemService(TrustManager.class, mTrustManager);
+ mSpiedContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
+ mSpiedContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+ mSpiedContext.addMockSystemService(FaceManager.class, mFaceManager);
+ mSpiedContext.addMockSystemService(UserManager.class, mUserManager);
+ mSpiedContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+ mSpiedContext.addMockSystemService(SubscriptionManager.class, mSubscriptionManager);
+ mSpiedContext.addMockSystemService(TelephonyManager.class, mTelephonyManager);
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
mTestableLooper = TestableLooper.get(this);
allowTestableLooperAsMainThread();
- mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(context);
+ mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mSpiedContext);
}
@After
@@ -192,6 +197,22 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
}
@Test
+ public void testSimStateInitialized() {
+ final int subId = 3;
+ final int state = TelephonyManager.SIM_STATE_ABSENT;
+
+ when(mTelephonyManager.getActiveModemCount()).thenReturn(1);
+ when(mTelephonyManager.getSimState(anyInt())).thenReturn(state);
+ when(mSubscriptionManager.getSubscriptionIds(anyInt())).thenReturn(new int[] { subId });
+
+ KeyguardUpdateMonitor testKUM = new TestableKeyguardUpdateMonitor(mSpiedContext);
+
+ mTestableLooper.processAllMessages();
+
+ assertThat(testKUM.getSimState(subId)).isEqualTo(state);
+ }
+
+ @Test
public void testIgnoresSimStateCallback_rebroadcast() {
Intent intent = new Intent(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 9e18e61229c3..3ef693a1ec98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -85,6 +85,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -183,6 +184,8 @@ public class BubbleControllerTest extends SysuiTestCase {
private DumpManager mDumpManager;
@Mock
private LockscreenLockIconController mLockIconController;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private BubbleData mBubbleData;
@@ -219,8 +222,7 @@ public class BubbleControllerTest extends SysuiTestCase {
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mKeyguardBypassController, mColorExtractor,
mDumpManager);
- mNotificationShadeWindowController.setNotificationShadeView(
- mSuperStatusBarViewFactory.getNotificationShadeWindowView());
+ mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
// Need notifications for bubbles
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index 49236e0275e0..8e6fc8a24b31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -79,6 +79,7 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LockscreenLockIconController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -134,6 +135,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
private KeyguardBypassController mKeyguardBypassController;
@Mock
private FloatingContentCoordinator mFloatingContentCoordinator;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
private SysUiState mSysUiState = new SysUiState();
@@ -206,8 +209,7 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
mConfigurationController, mKeyguardBypassController, mColorExtractor,
mDumpManager);
- mNotificationShadeWindowController.setNotificationShadeView(
- mSuperStatusBarViewFactory.getNotificationShadeWindowView());
+ mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
mNotificationShadeWindowController.attach();
// Need notifications for bubbles
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index f6ee46b0303a..e3f25c6b0c72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -89,6 +89,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
@Captor
private lateinit var controlLoadCallbackCaptor:
ArgumentCaptor<ControlsBindingController.LoadCallback>
+
@Captor
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
@Captor
@@ -97,6 +98,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var delayableExecutor: FakeExecutor
private lateinit var controller: ControlsControllerImpl
+ private lateinit var canceller: DidRunRunnable
companion object {
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
@@ -146,6 +148,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
}
}
+ canceller = DidRunRunnable()
+ `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+
controller = ControlsControllerImpl(
wrapper,
delayableExecutor,
@@ -266,7 +271,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertTrue(favorites.isEmpty())
assertFalse(data.errorOnLoad)
- })
+ }, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -301,7 +306,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(1, favorites.size)
assertEquals(TEST_CONTROL_ID, favorites[0])
assertFalse(data.errorOnLoad)
- })
+ }, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -332,7 +337,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(1, favorites.size)
assertEquals(TEST_CONTROL_ID, favorites[0])
assertFalse(data.errorOnLoad)
- })
+ }, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -363,7 +368,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(1, favorites.size)
assertEquals(TEST_CONTROL_ID, favorites[0])
assertTrue(data.errorOnLoad)
- })
+ }, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -377,22 +382,15 @@ class ControlsControllerImplTest : SysuiTestCase() {
@Test
fun testCancelLoad() {
- val canceller = object : Runnable {
- var ran = false
- override fun run() {
- ran = true
- }
- }
- `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
-
var loaded = false
+ var cancelRunnable: Runnable? = null
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
delayableExecutor.runAllReady()
controller.loadForComponent(TEST_COMPONENT, Consumer {
loaded = true
- })
+ }, Consumer { runnable -> cancelRunnable = runnable })
- controller.cancelLoad()
+ cancelRunnable?.run()
delayableExecutor.runAllReady()
assertFalse(loaded)
@@ -400,61 +398,47 @@ class ControlsControllerImplTest : SysuiTestCase() {
}
@Test
- fun testCancelLoad_noCancelAfterSuccessfulLoad() {
- val canceller = object : Runnable {
- var ran = false
- override fun run() {
- ran = true
- }
- }
- `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
-
+ fun testCancelLoad_afterSuccessfulLoad() {
var loaded = false
+ var cancelRunnable: Runnable? = null
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
delayableExecutor.runAllReady()
controller.loadForComponent(TEST_COMPONENT, Consumer {
loaded = true
- })
+ }, Consumer { runnable -> cancelRunnable = runnable })
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
controlLoadCallbackCaptor.value.accept(emptyList())
- controller.cancelLoad()
+ cancelRunnable?.run()
delayableExecutor.runAllReady()
assertTrue(loaded)
- assertFalse(canceller.ran)
+ assertTrue(canceller.ran)
}
@Test
- fun testCancelLoad_noCancelAfterErrorLoad() {
- val canceller = object : Runnable {
- var ran = false
- override fun run() {
- ran = true
- }
- }
- `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
-
+ fun testCancelLoad_afterErrorLoad() {
var loaded = false
+ var cancelRunnable: Runnable? = null
controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
delayableExecutor.runAllReady()
controller.loadForComponent(TEST_COMPONENT, Consumer {
loaded = true
- })
+ }, Consumer { runnable -> cancelRunnable = runnable })
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
controlLoadCallbackCaptor.value.error("")
- controller.cancelLoad()
+ cancelRunnable?.run()
delayableExecutor.runAllReady()
assertTrue(loaded)
- assertFalse(canceller.ran)
+ assertTrue(canceller.ran)
}
@Test
@@ -465,7 +449,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
val control = statelessBuilderFromInfo(newControlInfo).build()
- controller.loadForComponent(TEST_COMPONENT, Consumer {})
+ controller.loadForComponent(TEST_COMPONENT, Consumer {}, Consumer {})
verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor))
@@ -963,3 +947,10 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
}
}
+
+private class DidRunRunnable() : Runnable {
+ var ran = false
+ override fun run() {
+ ran = true
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
index e8e98b49c47f..4ac5912d0690 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java
@@ -133,4 +133,12 @@ public class ScreenRecordTileTest extends SysuiTestCase {
verify(mController, times(1)).stopRecording();
}
+
+ @Test
+ public void testContentDescriptionHasTileName() {
+ mTile.refreshState();
+ mTestableLooper.processAllMessages();
+
+ assertTrue(mTile.getState().contentDescription.toString().contains(mTile.getState().label));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 11649ca34709..ffe3cd5bd504 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -16,6 +16,10 @@
package com.android.systemui.screenshot;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -40,7 +44,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
-import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,9 +85,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
CompletableFuture<List<Notification.Action>> smartActionsFuture =
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
smartActionsProvider, true, false);
- Assert.assertNotNull(smartActionsFuture);
+ assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
- Assert.assertEquals(Collections.emptyList(), smartActions);
+ assertEquals(Collections.emptyList(), smartActions);
}
// Tests any exception thrown in waiting for smart actions future to complete does
@@ -99,7 +102,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
RuntimeException.class);
List<Notification.Action> actions = ScreenshotSmartActions.getSmartActions(
"", "", smartActionsFuture, timeoutMs, mSmartActionsProvider);
- Assert.assertEquals(Collections.emptyList(), actions);
+ assertEquals(Collections.emptyList(), actions);
}
// Tests any exception thrown in notifying feedback does not affect regular screenshot flow.
@@ -123,9 +126,9 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
mSmartActionsProvider, true, true);
verify(mSmartActionsProvider, never()).getActions(any(), any(), any(), any(),
eq(false));
- Assert.assertNotNull(smartActionsFuture);
+ assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
- Assert.assertEquals(Collections.emptyList(), smartActions);
+ assertEquals(Collections.emptyList(), smartActions);
}
// Tests for a hardware bitmap, ScreenshotNotificationSmartActionsProvider is invoked once.
@@ -152,14 +155,14 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
ScreenshotSmartActions.getSmartActionsFuture("", "", bitmap,
actionsProvider,
true, true);
- Assert.assertNotNull(smartActionsFuture);
+ assertNotNull(smartActionsFuture);
List<Notification.Action> smartActions = smartActionsFuture.get(5, TimeUnit.MILLISECONDS);
- Assert.assertEquals(smartActions.size(), 0);
+ assertEquals(smartActions.size(), 0);
}
- // Tests for notification action extras.
+ // Tests for share action extras
@Test
- public void testNotificationActionExtras() {
+ public void testShareActionExtras() {
if (Looper.myLooper() == null) {
Looper.prepare();
}
@@ -169,35 +172,70 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
data.finisher = null;
data.mActionsReadyListener = null;
- data.createDeleteAction = true;
SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
- List<Notification.Action> actions = task.populateNotificationActions(
- mContext, mContext.getResources(),
+
+ Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png"));
- Assert.assertEquals(actions.size(), 3);
- boolean isShareFound = false;
- boolean isEditFound = false;
- boolean isDeleteFound = false;
- for (Notification.Action action : actions) {
- Intent intent = action.actionIntent.getIntent();
- Assert.assertNotNull(intent);
- Bundle bundle = intent.getExtras();
- Assert.assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
- Assert.assertTrue(
- bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
-
- if (action.title.equals(GlobalScreenshot.ACTION_TYPE_DELETE)) {
- isDeleteFound = intent.getAction() == null;
- } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_EDIT)) {
- isEditFound = Intent.ACTION_EDIT.equals(intent.getAction());
- } else if (action.title.equals(GlobalScreenshot.ACTION_TYPE_SHARE)) {
- isShareFound = Intent.ACTION_SEND.equals(intent.getAction());
- }
+ Intent intent = shareAction.actionIntent.getIntent();
+ assertNotNull(intent);
+ Bundle bundle = intent.getExtras();
+ assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+ assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+ assertEquals(GlobalScreenshot.ACTION_TYPE_SHARE, shareAction.title);
+ assertEquals(Intent.ACTION_SEND, intent.getAction());
+ }
+
+ // Tests for edit action extras
+ @Test
+ public void testEditActionExtras() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
}
- Assert.assertTrue(isEditFound);
- Assert.assertTrue(isDeleteFound);
- Assert.assertTrue(isShareFound);
+ GlobalScreenshot.SaveImageInBackgroundData
+ data = new GlobalScreenshot.SaveImageInBackgroundData();
+ data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+ data.finisher = null;
+ data.mActionsReadyListener = null;
+ SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+
+ Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
+ Uri.parse("Screenshot_123.png"));
+
+ Intent intent = editAction.actionIntent.getIntent();
+ assertNotNull(intent);
+ Bundle bundle = intent.getExtras();
+ assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+ assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+ assertEquals(GlobalScreenshot.ACTION_TYPE_EDIT, editAction.title);
+ assertEquals(Intent.ACTION_EDIT, intent.getAction());
+ }
+
+ // Tests for share action extras
+ @Test
+ public void testDeleteActionExtras() {
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
+ GlobalScreenshot.SaveImageInBackgroundData
+ data = new GlobalScreenshot.SaveImageInBackgroundData();
+ data.image = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+ data.finisher = null;
+ data.mActionsReadyListener = null;
+ SaveImageInBackgroundTask task = new SaveImageInBackgroundTask(mContext, data);
+
+ Notification.Action deleteAction = task.createDeleteAction(mContext,
+ mContext.getResources(),
+ Uri.parse("Screenshot_123.png"));
+
+ Intent intent = deleteAction.actionIntent.getIntent();
+ assertNotNull(intent);
+ Bundle bundle = intent.getExtras();
+ assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_ID));
+ assertTrue(bundle.containsKey(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED));
+ assertEquals(deleteAction.title, GlobalScreenshot.ACTION_TYPE_DELETE);
+ assertNull(intent.getAction());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt
new file mode 100644
index 000000000000..628c06a56abd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/CurrentUserContextTrackerTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.settings
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class CurrentUserContextTrackerTest : SysuiTestCase() {
+
+ private lateinit var tracker: CurrentUserContextTracker
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ allowTestableLooperAsMainThread()
+
+ // wrap Context so that tests don't throw for missing package errors
+ val wrapped = object : ContextWrapper(context) {
+ override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+ val mockContext = mock(Context::class.java)
+ `when`(mockContext.user).thenReturn(user)
+ `when`(mockContext.userId).thenReturn(user.identifier)
+ return mockContext
+ }
+ }
+
+ tracker = CurrentUserContextTracker(wrapped, broadcastDispatcher)
+ tracker.initialize()
+ }
+
+ @Test
+ fun testContextExistsAfterInit_noCrash() {
+ tracker.currentUserContext
+ }
+
+ @Test
+ fun testUserContextIsCorrectAfterUserSwitch() {
+ // We always start out with system ui test
+ assertTrue("Starting userId should be 0", tracker.currentUserContext.userId == 0)
+
+ // WHEN user changes
+ tracker.handleUserSwitched(1)
+
+ // THEN user context should have the correct userId
+ assertTrue("User has changed to userId 1, the context should reflect that",
+ tracker.currentUserContext.userId == 1)
+ }
+
+ @Suppress("UNUSED_PARAMETER")
+ @Test(expected = IllegalStateException::class)
+ fun testContextTrackerThrowsExceptionWhenNotInitialized() {
+ // GIVEN an uninitialized CurrentUserContextTracker
+ val userTracker = CurrentUserContextTracker(context, broadcastDispatcher)
+
+ // WHEN client asks for a context
+ val userContext = userTracker.currentUserContext
+
+ // THEN an exception is thrown
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 61388b6d0389..6db868563d3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -48,9 +48,9 @@ import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Person;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -91,6 +91,7 @@ import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.stubbing.Answer;
@@ -147,14 +148,15 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
private ShadeController mShadeController;
@Mock
private ConversationIconFactory mIconFactory;
- @Mock
- private Context mUserContext;
@Mock(answer = Answers.RETURNS_SELF)
private PriorityOnboardingDialogController.Builder mBuilder;
private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider = () -> mBuilder;
+ @Mock
+ private Notification.BubbleMetadata mBubbleMetadata;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
@@ -228,6 +230,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
.thenReturn(mConversationChannel);
+
+ when(mMockINotificationManager.getConsolidatedNotificationPolicy())
+ .thenReturn(mock(NotificationManager.Policy.class));
+
+ when(mBuilder.build()).thenReturn(mock(PriorityOnboardingDialogController.class));
}
@Test
@@ -240,10 +247,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
@@ -261,10 +269,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
@@ -283,7 +292,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
- null,
+ mBubbleMetadata,
+ null,
null,
null,
true);
@@ -308,10 +318,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
@@ -331,10 +342,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
@@ -353,10 +365,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
@@ -382,10 +395,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
entry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
@@ -404,13 +418,14 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mConversationChannel, c);
latch.countDown();
},
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -430,10 +445,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -451,13 +467,14 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
(View v, NotificationChannel c, int appUid) -> {
assertEquals(mNotificationChannel, c);
latch.countDown();
},
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
false);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
@@ -476,10 +493,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
View view = mNotificationInfo.findViewById(R.id.silence);
@@ -501,10 +519,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
@@ -529,10 +548,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
@@ -556,10 +576,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -596,10 +617,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -635,10 +657,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -675,10 +698,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -709,10 +733,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -741,10 +766,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -774,10 +800,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -807,10 +834,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -839,10 +867,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -870,10 +899,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -892,10 +922,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
+ mContext,
mBuilderProvider,
true);
@@ -910,13 +941,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
// GIVEN the priority onboarding screen is present
PriorityOnboardingDialogController.Builder b =
- new PriorityOnboardingDialogController.Builder();
+ mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
PriorityOnboardingDialogController controller =
mock(PriorityOnboardingDialogController.class);
when(b.build()).thenReturn(controller);
// GIVEN the user is changing conversation settings
- when(mBuilderProvider.get()).thenReturn(b);
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
@@ -925,11 +955,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
- mBuilderProvider,
+ mContext,
+ () -> b,
true);
// WHEN user clicks "priority"
@@ -945,12 +976,11 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true);
PriorityOnboardingDialogController.Builder b =
- new PriorityOnboardingDialogController.Builder();
+ mock(PriorityOnboardingDialogController.Builder.class, Answers.RETURNS_SELF);
PriorityOnboardingDialogController controller =
mock(PriorityOnboardingDialogController.class);
when(b.build()).thenReturn(controller);
- when(mBuilderProvider.get()).thenReturn(b);
mNotificationInfo.bindNotification(
mShortcutManager,
mMockPackageManager,
@@ -959,11 +989,12 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
TEST_PACKAGE_NAME,
mNotificationChannel,
mEntry,
+ mBubbleMetadata,
null,
null,
mIconFactory,
- mUserContext,
- mBuilderProvider,
+ mContext,
+ () -> b,
true);
// WHEN user clicks "priority"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 2b091f297184..210744eb30cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -6,11 +6,18 @@ import android.view.LayoutInflater
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.AccessibilityController
+import com.android.systemui.statusbar.policy.FlashlightController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.tuner.TunerService
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -24,6 +31,15 @@ class KeyguardBottomAreaTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ // Mocked dependencies
+ mDependency.injectMockDependency(AccessibilityController::class.java)
+ mDependency.injectMockDependency(ActivityStarter::class.java)
+ mDependency.injectMockDependency(AssistManager::class.java)
+ mDependency.injectTestDependency(Executor::class.java, Executor { it.run() })
+ mDependency.injectMockDependency(FlashlightController::class.java)
+ mDependency.injectMockDependency(KeyguardStateController::class.java)
+ mDependency.injectMockDependency(TunerService::class.java)
+
mKeyguardBottomArea = LayoutInflater.from(mContext).inflate(
R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView
mKeyguardBottomArea.setStatusBar(mStatusBar)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index e052ae2653f0..0a041e4a4dc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -26,7 +26,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -37,6 +36,7 @@ import android.graphics.Color;
import android.os.Handler;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
@@ -64,6 +64,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -94,9 +95,11 @@ public class KeyguardBouncerTest extends SysuiTestCase {
private Handler mHandler;
@Mock
private KeyguardSecurityModel mKeyguardSecurityModel;
+ @Mock
+ private ViewGroup mRootView;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
- private ViewGroup mRootView;
+ private Integer mRootVisibility = View.INVISIBLE;
private KeyguardBouncer mBouncer;
@Before
@@ -105,6 +108,11 @@ public class KeyguardBouncerTest extends SysuiTestCase {
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
mDependency.injectMockDependency(KeyguardStateController.class);
+ when(mRootView.getVisibility()).thenAnswer((Answer<Integer>) invocation -> mRootVisibility);
+ doAnswer(invocation -> {
+ mRootVisibility = invocation.getArgument(0);
+ return null;
+ }).when(mRootView).setVisibility(anyInt());
when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.None);
DejankUtils.setImmediate(true);
@@ -117,10 +125,8 @@ public class KeyguardBouncerTest extends SysuiTestCase {
mKeyguardBypassController, mHandler) {
@Override
protected void inflateView() {
- super.inflateView();
mKeyguardView = mKeyguardHostView;
- mRoot = spy(mRoot);
- mRootView = mRoot;
+ mRoot = mRootView;
}
};
}
@@ -212,8 +218,10 @@ public class KeyguardBouncerTest extends SysuiTestCase {
verify(mExpansionCallback).onFullyShown();
verify(mExpansionCallback, never()).onStartingToHide();
+ verify(mKeyguardHostView, never()).onStartingToHide();
mBouncer.setExpansion(0.9f);
verify(mExpansionCallback).onStartingToHide();
+ verify(mKeyguardHostView).onStartingToHide();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 8ab660c3fca3..3e469073694f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -56,7 +56,7 @@ public class LightBarControllerTest extends SysuiTestCase {
when(mStatusBarIconController.getTransitionsController()).thenReturn(
mLightBarTransitionsController);
mLightBarController = new LightBarController(mContext, mStatusBarIconController,
- mock(BatteryController.class));
+ mock(BatteryController.class), mock(NavigationModeController.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index 27a50027cfe2..14c6e9f9624d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.phone;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
@@ -50,9 +51,11 @@ public class NavigationBarTransitionsTest extends SysuiTestCase {
mDependency.injectMockDependency(IWindowManager.class);
mDependency.injectMockDependency(AssistManager.class);
mDependency.injectMockDependency(OverviewProxyService.class);
- mDependency.injectMockDependency(NavigationModeController.class);
mDependency.injectMockDependency(StatusBarStateController.class);
mDependency.injectMockDependency(KeyguardStateController.class);
+ doReturn(mContext)
+ .when(mDependency.injectMockDependency(NavigationModeController.class))
+ .getCurrentUserContext();
NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
when(navBar.getCurrentView()).thenReturn(navBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index dd28687e749c..1afe132f00a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -176,25 +176,43 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
- mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
- getContext(), mock(CommandQueue.class), () -> mAssistManager,
- mEntryManager, mock(HeadsUpManagerPhone.class),
- mActivityStarter, mStatusBarService,
- mock(StatusBarStateController.class), mStatusBarKeyguardViewManager,
- mock(KeyguardManager.class),
- mock(IDreamManager.class), mRemoteInputManager,
- mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
- mock(NotificationLockscreenUserManager.class),
- mKeyguardStateController,
- mock(NotificationInterruptStateProvider.class), mock(MetricsLogger.class),
- mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
- mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
- mNotifPipeline, mNotifCollection)
+ mNotificationActivityStarter =
+ new StatusBarNotificationActivityStarter.Builder(
+ getContext(),
+ mock(CommandQueue.class),
+ mHandler,
+ mHandler,
+ mUiBgExecutor,
+ mEntryManager,
+ mNotifPipeline,
+ mNotifCollection,
+ mock(HeadsUpManagerPhone.class),
+ mActivityStarter,
+ mStatusBarService,
+ mock(StatusBarStateController.class),
+ mStatusBarKeyguardViewManager,
+ mock(KeyguardManager.class),
+ mock(IDreamManager.class),
+ mBubbleController,
+ () -> mAssistManager,
+ mRemoteInputManager,
+ mock(NotificationGroupManager.class),
+ mock(NotificationLockscreenUserManager.class),
+ mShadeController,
+ mKeyguardStateController,
+ mock(NotificationInterruptStateProvider.class),
+ mock(LockPatternUtils.class),
+ mock(StatusBarRemoteInputCallback.class),
+ mActivityIntentHelper,
+
+ mFeatureFlags,
+ mock(MetricsLogger.class),
+ mock(StatusBarNotificationActivityStarterLogger.class))
.setStatusBar(mStatusBar)
- .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
.setNotificationPresenter(mock(NotificationPresenter.class))
- .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
- .build();
+ .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
+ .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+ .build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 962d77366875..aef454fc1374 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -32,6 +32,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Instrumentation;
@@ -222,7 +223,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
ArgumentCaptor<ConnectivityManager.NetworkCallback> callbackArg =
ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
- Mockito.verify(mMockCm, atLeastOnce())
+ verify(mMockCm, atLeastOnce())
.registerDefaultNetworkCallback(callbackArg.capture(), isA(Handler.class));
mNetworkCallback = callbackArg.getValue();
assertNotNull(mNetworkCallback);
@@ -384,7 +385,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
}
protected void verifyHasNoSims(boolean hasNoSimsVisible) {
- Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setNoSims(
eq(hasNoSimsVisible), eq(false));
}
@@ -395,7 +396,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
ArgumentCaptor<Boolean> dataInArg = ArgumentCaptor.forClass(Boolean.class);
ArgumentCaptor<Boolean> dataOutArg = ArgumentCaptor.forClass(Boolean.class);
- Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
any(),
iconArg.capture(),
anyInt(),
@@ -429,7 +430,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
ArgumentCaptor<Integer> typeIconArg = ArgumentCaptor.forClass(Integer.class);
// TODO: Verify all fields.
- Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
iconArg.capture(),
any(),
typeIconArg.capture(),
@@ -475,7 +476,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
ArgumentCaptor<CharSequence> typeContentDescriptionHtmlArg =
ArgumentCaptor.forClass(CharSequence.class);
- Mockito.verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
+ verify(mCallbackHandler, Mockito.atLeastOnce()).setMobileDataIndicators(
iconArg.capture(),
qsIconArg.capture(),
typeIconArg.capture(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 9c250c5e8e16..988e02203843 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -2,12 +2,14 @@ package com.android.systemui.statusbar.policy;
import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.Intent;
+import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
@@ -171,6 +173,32 @@ public class NetworkControllerWifiTest extends NetworkControllerBaseTest {
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
}
+ @Test
+ public void testFetchInitialData() {
+ mNetworkController.mWifiSignalController.fetchInitialState();
+ Mockito.verify(mMockWm).getWifiState();
+ Mockito.verify(mMockCm).getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+ }
+
+ @Test
+ public void testFetchInitialData_correctValues() {
+ String testSsid = "TEST";
+
+ when(mMockWm.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
+ NetworkInfo networkInfo = mock(NetworkInfo.class);
+ when(networkInfo.isConnected()).thenReturn(true);
+ when(mMockCm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)).thenReturn(networkInfo);
+ WifiInfo wifiInfo = mock(WifiInfo.class);
+ when(wifiInfo.getSSID()).thenReturn(testSsid);
+ when(mMockWm.getConnectionInfo()).thenReturn(wifiInfo);
+
+ mNetworkController.mWifiSignalController.fetchInitialState();
+
+ assertTrue(mNetworkController.mWifiSignalController.mCurrentState.enabled);
+ assertTrue(mNetworkController.mWifiSignalController.mCurrentState.connected);
+ assertEquals(testSsid, mNetworkController.mWifiSignalController.mCurrentState.ssid);
+ }
+
protected void setWifiActivity(int activity) {
// TODO: Not this, because this variable probably isn't sticking around.
mNetworkController.mWifiSignalController.setActivity(activity);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
new file mode 100644
index 000000000000..00f37ae6f6cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class RepeatableExecutorTest extends SysuiTestCase {
+
+ private static final int DELAY = 100;
+
+ private FakeSystemClock mFakeClock;
+ private FakeExecutor mFakeExecutor;
+ private RepeatableExecutor mExecutor;
+ private CountingTask mCountingTask;
+
+ @Before
+ public void setUp() throws Exception {
+ mFakeClock = new FakeSystemClock();
+ mFakeExecutor = new FakeExecutor(mFakeClock);
+ mCountingTask = new CountingTask();
+ mExecutor = new RepeatableExecutorImpl(mFakeExecutor);
+ }
+
+ /**
+ * Test FakeExecutor that receives non-delayed items to execute.
+ */
+ @Test
+ public void testExecute() {
+ mExecutor.execute(mCountingTask);
+ mFakeExecutor.runAllReady();
+ assertThat(mCountingTask.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testRepeats() {
+ // GIVEN that a command is queued to repeat
+ mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+ // WHEN The clock advances and the task is run
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+ // THEN another task is queued
+ assertThat(mCountingTask.getCount()).isEqualTo(1);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+ }
+
+ @Test
+ public void testNoExecutionBeforeStartDelay() {
+ // WHEN a command is queued with a start delay
+ mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY);
+ mFakeExecutor.runAllReady();
+ // THEN then it doesn't run immediately
+ assertThat(mCountingTask.getCount()).isEqualTo(0);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+ }
+
+ @Test
+ public void testExecuteAfterStartDelay() {
+ // GIVEN that a command is queued to repeat with a longer start delay
+ mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY);
+ // WHEN the clock advances the start delay
+ mFakeClock.advanceTime(2 * DELAY);
+ mFakeExecutor.runAllReady();
+ // THEN the command has run and another task is queued
+ assertThat(mCountingTask.getCount()).isEqualTo(1);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+ }
+
+ @Test
+ public void testExecuteWithZeroStartDelay() {
+ // WHEN a command is queued with no start delay
+ mExecutor.executeRepeatedly(mCountingTask, 0L, DELAY);
+ mFakeExecutor.runAllReady();
+ // THEN the command has run and another task is queued
+ assertThat(mCountingTask.getCount()).isEqualTo(1);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+ }
+
+ @Test
+ public void testAdvanceTimeTwice() {
+ // GIVEN that a command is queued to repeat
+ mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+ // WHEN the clock advances the time DELAY twice
+ mFakeClock.advanceTime(DELAY);
+ mFakeExecutor.runAllReady();
+ mFakeClock.advanceTime(DELAY);
+ mFakeExecutor.runAllReady();
+ // THEN the command has run twice and another task is queued
+ assertThat(mCountingTask.getCount()).isEqualTo(2);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+ }
+
+ @Test
+ public void testCancel() {
+ // GIVEN that a scheduled command has been cancelled
+ Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+ cancel.run();
+ // WHEN the clock advances the time DELAY
+ mFakeClock.advanceTime(DELAY);
+ mFakeExecutor.runAllReady();
+ // THEN the comamnd has not run and no further tasks are queued
+ assertThat(mCountingTask.getCount()).isEqualTo(0);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+ }
+
+ @Test
+ public void testCancelAfterStart() {
+ // GIVEN that a command has reapeated a few times
+ Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+ mFakeClock.advanceTime(DELAY);
+ mFakeExecutor.runAllReady();
+ // WHEN cancelled and time advances
+ cancel.run();
+ // THEN the command has only run the first time
+ assertThat(mCountingTask.getCount()).isEqualTo(1);
+ assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+ }
+
+ /**
+ * Runnable used for testing that counts the number of times run() is invoked.
+ */
+ private static class CountingTask implements Runnable {
+
+ private int mRunCount;
+
+ @Override
+ public void run() {
+ mRunCount++;
+ }
+
+ /** Gets the run count. */
+ public int getCount() {
+ return mRunCount;
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 132b6927badd..da9bdf3262d5 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1023,8 +1023,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
try {
mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
final ScreenshotGraphicBuffer screenshotBuffer = LocalServices
- .getService(DisplayManagerInternal.class)
- .screenshotWithoutSecureLayer(displayId);
+ .getService(DisplayManagerInternal.class).userScreenshot(displayId);
if (screenshotBuffer != null) {
sendScreenshotSuccess(screenshotBuffer, callback);
} else {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index f21f0e73e787..1a72cf023453 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2732,7 +2732,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- new AccessibilityShellCommand(this).exec(this, in, out, err, args,
+ new AccessibilityShellCommand(this, mSystemActionPerformer).exec(this, in, out, err, args,
callback, resultReceiver);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
index 20a11bd9acd3..b36626f9d736 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityShellCommand.java
@@ -18,6 +18,8 @@ package com.android.server.accessibility;
import android.annotation.NonNull;
import android.app.ActivityManager;
+import android.os.Binder;
+import android.os.Process;
import android.os.ShellCommand;
import android.os.UserHandle;
@@ -28,9 +30,12 @@ import java.io.PrintWriter;
*/
final class AccessibilityShellCommand extends ShellCommand {
final @NonNull AccessibilityManagerService mService;
+ final @NonNull SystemActionPerformer mSystemActionPerformer;
- AccessibilityShellCommand(@NonNull AccessibilityManagerService service) {
+ AccessibilityShellCommand(@NonNull AccessibilityManagerService service,
+ @NonNull SystemActionPerformer systemActionPerformer) {
mService = service;
+ mSystemActionPerformer = systemActionPerformer;
}
@Override
@@ -45,6 +50,9 @@ final class AccessibilityShellCommand extends ShellCommand {
case "set-bind-instant-service-allowed": {
return runSetBindInstantServiceAllowed();
}
+ case "call-system-action": {
+ return runCallSystemAction();
+ }
}
return -1;
}
@@ -74,6 +82,22 @@ final class AccessibilityShellCommand extends ShellCommand {
return 0;
}
+ private int runCallSystemAction() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID
+ && callingUid != Process.SYSTEM_UID
+ && callingUid != Process.SHELL_UID) {
+ return -1;
+ }
+ final String option = getNextArg();
+ if (option != null) {
+ int actionId = Integer.parseInt(option);
+ mSystemActionPerformer.performSystemAction(actionId);
+ return 0;
+ }
+ return -1;
+ }
+
private Integer parseUserId() {
final String option = getNextOption();
if (option != null) {
@@ -97,5 +121,7 @@ final class AccessibilityShellCommand extends ShellCommand {
pw.println(" Set whether binding to services provided by instant apps is allowed.");
pw.println(" get-bind-instant-service-allowed [--user <USER_ID>]");
pw.println(" Get whether binding to services provided by instant apps is allowed.");
+ pw.println(" call-system-action <ACTION_ID>");
+ pw.println(" Calls the system action with the given action id.");
}
} \ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index ef8d524bee25..a1fc3fa1857d 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -301,7 +303,11 @@ public class SystemActionPerformer {
return lockScreen();
case AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT:
return takeScreenshot();
+ case AccessibilityService.GLOBAL_ACTION_KEYCODE_HEADSETHOOK :
+ sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HEADSETHOOK);
+ return true;
default:
+ Slog.e(TAG, "Invalid action id: " + actionId);
return false;
}
} finally {
@@ -395,7 +401,8 @@ public class SystemActionPerformer {
ScreenshotHelper screenshotHelper = (mScreenshotHelperSupplier != null)
? mScreenshotHelperSupplier.get() : new ScreenshotHelper(mContext);
screenshotHelper.takeScreenshot(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true, true, new Handler(Looper.getMainLooper()), null);
+ true, true, SCREENSHOT_ACCESSIBILITY_ACTIONS,
+ new Handler(Looper.getMainLooper()), null);
return true;
}
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 9d1ad4239a24..35089d6f5de7 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -117,6 +117,7 @@ import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* A session for a given activity.
@@ -717,10 +718,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer =
mAssistReceiver.newAutofillRequestLocked(/*isInlineRequest=*/ true);
if (inlineSuggestionsRequestConsumer != null) {
+ final AutofillId focusedId = mCurrentViewId;
remoteRenderService.getInlineSuggestionsRendererInfo(
new RemoteCallback((extras) -> {
mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
- mCurrentViewId, inlineSuggestionsRequestConsumer, extras);
+ focusedId, inlineSuggestionsRequestConsumer, extras);
}
));
}
@@ -2786,6 +2788,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
*/
private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response,
@Nullable String filterText) {
+ if (mCurrentViewId == null) {
+ Log.w(TAG, "requestShowInlineSuggestionsLocked(): no view currently focused");
+ return false;
+ }
+ final AutofillId focusedId = mCurrentViewId;
+
final Optional<InlineSuggestionsRequest> inlineSuggestionsRequest =
mInlineSessionController.getInlineSuggestionsRequestLocked();
if (!inlineSuggestionsRequest.isPresent()) {
@@ -2800,17 +2808,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
- final ViewState currentView = mViewStates.get(mCurrentViewId);
+ final ViewState currentView = mViewStates.get(focusedId);
if ((currentView.getState() & ViewState.STATE_INLINE_DISABLED) != 0) {
response.getDatasets().clear();
}
InlineSuggestionsResponse inlineSuggestionsResponse =
InlineSuggestionFactory.createInlineSuggestionsResponse(
- inlineSuggestionsRequest.get(), response, filterText, mCurrentViewId,
+ inlineSuggestionsRequest.get(), response, filterText, focusedId,
this, () -> {
synchronized (mLock) {
mInlineSessionController.hideInlineSuggestionsUiLocked(
- mCurrentViewId);
+ focusedId);
}
}, remoteRenderService);
if (inlineSuggestionsResponse == null) {
@@ -2818,7 +2826,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return false;
}
- return mInlineSessionController.onInlineSuggestionsResponseLocked(mCurrentViewId,
+ return mInlineSessionController.onInlineSuggestionsResponseLocked(focusedId,
inlineSuggestionsResponse);
}
@@ -3072,19 +3080,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final boolean isWhitelisted = mService
.isWhitelistedForAugmentedAutofillLocked(mComponentName);
- final String historyItem =
- "aug:id=" + id + " u=" + uid + " m=" + mode
- + " a=" + ComponentName.flattenToShortString(mComponentName)
- + " f=" + mCurrentViewId
- + " s=" + remoteService.getComponentName()
- + " w=" + isWhitelisted;
- mService.getMaster().logRequestLocked(historyItem);
-
if (!isWhitelisted) {
if (sVerbose) {
Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
+ ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
}
+ logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+ mCurrentViewId, isWhitelisted, /*isInline*/null);
return null;
}
@@ -3107,26 +3109,29 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
remoteService.getComponentName().getPackageName());
mAugmentedRequestsLogs.add(log);
- final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
+ final AutofillId focusedId = mCurrentViewId;
+ final Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsResponseCallback =
+ response -> {
+ synchronized (mLock) {
+ return mInlineSessionController.onInlineSuggestionsResponseLocked(
+ focusedId, response);
+ }
+ };
final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
(inlineSuggestionsRequest) -> {
- remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
- focusedId,
- currentValue, inlineSuggestionsRequest,
- /*inlineSuggestionsCallback=*/
- response -> {
- synchronized (mLock) {
- return mInlineSessionController
- .onInlineSuggestionsResponseLocked(
- mCurrentViewId, response);
- }
- },
- /*onErrorCallback=*/ () -> {
- synchronized (mLock) {
- cancelAugmentedAutofillLocked();
- }
- }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+ synchronized (mLock) {
+ logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+ focusedId, isWhitelisted, inlineSuggestionsRequest != null);
+ remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
+ AutofillId.withoutSession(focusedId), currentValue,
+ inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
+ /*onErrorCallback=*/ () -> {
+ synchronized (mLock) {
+ cancelAugmentedAutofillLocked();
+ }
+ }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+ }
};
// When the inline suggestion render service is available, there are 2 cases when
@@ -3143,9 +3148,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
(extras) -> {
- mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
- mCurrentViewId, /*requestConsumer=*/ requestAugmentedAutofill,
- extras);
+ synchronized (mLock) {
+ mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+ focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
+ extras);
+ }
}, mHandler));
} else {
requestAugmentedAutofill.accept(
@@ -3158,6 +3165,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
@GuardedBy("mLock")
+ private void logAugmentedAutofillRequestLocked(int mode,
+ ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
+ Boolean isInline) {
+ final String historyItem =
+ "aug:id=" + id + " u=" + uid + " m=" + mode
+ + " a=" + ComponentName.flattenToShortString(mComponentName)
+ + " f=" + focusedId
+ + " s=" + augmentedRemoteServiceName
+ + " w=" + isWhitelisted
+ + " i=" + isInline;
+ mService.getMaster().logRequestLocked(historyItem);
+ }
+
+ @GuardedBy("mLock")
private void cancelAugmentedAutofillLocked() {
final RemoteAugmentedAutofillService remoteService = mService
.getRemoteAugmentedAutofillServiceLocked();
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index c27ec66b5db3..81de29c4ee4d 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -480,6 +480,12 @@ public abstract class PackageManagerInternal {
public abstract void pruneInstantApps();
/**
+ * Prunes the cache of the APKs in the given APEXes.
+ * @param apexPackages The list of APEX packages that may contain APK-in-APEX.
+ */
+ public abstract void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages);
+
+ /**
* @return The SetupWizard package name.
*/
public abstract String getSetupWizardPackageName();
@@ -977,4 +983,9 @@ public abstract class PackageManagerInternal {
* Returns if a package name is a valid system package.
*/
public abstract boolean isSystemPackage(@NonNull String packageName);
+
+ /**
+ * Unblocks uninstall for all packages for the user.
+ */
+ public abstract void clearBlockUninstallForUser(@UserIdInt int userId);
}
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index ce651100699f..4009cafd04fb 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -603,8 +603,7 @@ class AlarmManagerService extends SystemService {
}
pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA); pw.print("=");
- TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_QUOTA, pw);
- pw.println();
+ pw.println(APP_STANDBY_RESTRICTED_QUOTA);
pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW); pw.print("=");
TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2bbf27849005..97a5cfe6006d 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1146,7 +1146,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
&& registrationLimit >= 1
&& numRecordsForPid >= registrationLimit) {
String errorMsg = "Pid " + callingPid + " has exceeded the number of permissible"
- + "registered listeners. Ignoring request to add.";
+ + " registered listeners. Ignoring request to add.";
loge(errorMsg);
if (mConfigurationProvider
.isRegistrationLimitEnabledInPlatformCompat(callingUid)) {
@@ -1157,7 +1157,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
// Log the warning independently of the dynamically set limit -- apps shouldn't be
// doing this regardless of whether we're throwing them an exception for it.
Rlog.w(TAG, "Pid " + callingPid + " has exceeded half the number of permissible"
- + "registered listeners. Now at " + numRecordsForPid);
+ + " registered listeners. Now at " + numRecordsForPid);
}
r = new Record();
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index ac4a42ca7024..e066d99147ba 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -1034,6 +1034,9 @@ public class VibratorService extends IVibratorService.Stub
VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
waveform = waveform.resolve(mDefaultVibrationAmplitude);
scaledEffect = waveform.scale(scale.gamma, scale.maxAmplitude);
+ } else if (vib.effect instanceof VibrationEffect.Composed) {
+ VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect;
+ scaledEffect = composed.scale(scale.gamma, scale.maxAmplitude);
} else {
Slog.w(TAG, "Unable to apply intensity scaling, unknown VibrationEffect type");
}
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index dbad562c0271..b647818e3f7a 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1013,9 +1013,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
@Override
public void noteNetworkInterfaceType(String iface, int networkType) {
enforceCallingPermission();
- synchronized (mStats) {
- mStats.noteNetworkInterfaceTypeLocked(iface, networkType);
- }
+ mStats.noteNetworkInterfaceType(iface, networkType);
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b4f7cdbd5694..02d499fbd81f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -28,6 +28,8 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOUL
import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import android.Manifest;
import android.annotation.NonNull;
@@ -1363,8 +1365,7 @@ public final class DisplayManagerService extends SystemService {
return null;
}
- private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId,
- boolean captureSecureLayer) {
+ private SurfaceControl.ScreenshotGraphicBuffer systemScreenshotInternal(int displayId) {
synchronized (mSyncRoot) {
final IBinder token = getDisplayToken(displayId);
if (token == null) {
@@ -1376,15 +1377,42 @@ public final class DisplayManagerService extends SystemService {
}
final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
- if (captureSecureLayer) {
- return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
- displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
- false /* useIdentityTransform */, 0 /* rotation */);
- } else {
- return SurfaceControl.screenshotToBuffer(token, new Rect(),
- displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
- false /* useIdentityTransform */, 0 /* rotation */);
+ return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
+ displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
+ false /* useIdentityTransform */, 0 /* rotation */);
+ }
+ }
+
+ private SurfaceControl.ScreenshotGraphicBuffer userScreenshotInternal(int displayId) {
+ synchronized (mSyncRoot) {
+ final IBinder token = getDisplayToken(displayId);
+ if (token == null) {
+ return null;
+ }
+ final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
+ if (logicalDisplay == null) {
+ return null;
+ }
+
+ final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
+ // Takes screenshot based on current device orientation.
+ final Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(displayId);
+ if (display == null) {
+ return null;
}
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+
+ int rotation = displayInfo.rotation;
+ // TODO (b/153382624) : This workaround solution would be removed after
+ // SurfaceFlinger fixes the inconsistency with rotation direction issue.
+ if (rotation == ROTATION_90 || rotation == ROTATION_270) {
+ rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
+ }
+
+ return SurfaceControl.screenshotToBuffer(token, new Rect(), displaySize.x,
+ displaySize.y, false /* useIdentityTransform */, rotation /* rotation */);
}
}
@@ -2502,13 +2530,13 @@ public final class DisplayManagerService extends SystemService {
}
@Override
- public SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId) {
- return screenshotInternal(displayId, true);
+ public SurfaceControl.ScreenshotGraphicBuffer systemScreenshot(int displayId) {
+ return systemScreenshotInternal(displayId);
}
@Override
- public SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(int displayId) {
- return screenshotInternal(displayId, false);
+ public SurfaceControl.ScreenshotGraphicBuffer userScreenshot(int displayId) {
+ return userScreenshotInternal(displayId);
}
@Override
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index fbee6f4bcbf0..848019738abe 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -26,6 +26,7 @@ import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.content.Context;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -36,6 +37,7 @@ import android.util.Slog;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
@@ -57,11 +59,14 @@ public class LockSettingsStrongAuth {
private static final int MSG_STRONG_BIOMETRIC_UNLOCK = 8;
private static final int MSG_SCHEDULE_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT = 9;
- private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
+ @VisibleForTesting
+ protected static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
"LockSettingsStrongAuth.timeoutForUser";
- private static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
+ @VisibleForTesting
+ protected static final String NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG =
"LockSettingsPrimaryAuth.nonStrongBiometricTimeoutForUser";
- private static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
+ @VisibleForTesting
+ protected static final String NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG =
"LockSettingsPrimaryAuth.nonStrongBiometricIdleTimeoutForUser";
/**
@@ -73,28 +78,71 @@ public class LockSettingsStrongAuth {
4 * 60 * 60 * 1000; // 4h
private final RemoteCallbackList<IStrongAuthTracker> mTrackers = new RemoteCallbackList<>();
- private final SparseIntArray mStrongAuthForUser = new SparseIntArray();
- private final SparseBooleanArray mIsNonStrongBiometricAllowedForUser = new SparseBooleanArray();
- private final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
+ @VisibleForTesting
+ protected final SparseIntArray mStrongAuthForUser = new SparseIntArray();
+ @VisibleForTesting
+ protected final SparseBooleanArray mIsNonStrongBiometricAllowedForUser =
+ new SparseBooleanArray();
+ @VisibleForTesting
+ protected final ArrayMap<Integer, StrongAuthTimeoutAlarmListener>
mStrongAuthTimeoutAlarmListenerForUser = new ArrayMap<>();
// Track non-strong biometric timeout
- private final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
+ @VisibleForTesting
+ protected final ArrayMap<Integer, NonStrongBiometricTimeoutAlarmListener>
mNonStrongBiometricTimeoutAlarmListener = new ArrayMap<>();
// Track non-strong biometric idle timeout
- private final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
+ @VisibleForTesting
+ protected final ArrayMap<Integer, NonStrongBiometricIdleTimeoutAlarmListener>
mNonStrongBiometricIdleTimeoutAlarmListener = new ArrayMap<>();
private final int mDefaultStrongAuthFlags;
private final boolean mDefaultIsNonStrongBiometricAllowed = true;
private final Context mContext;
-
- private AlarmManager mAlarmManager;
+ private final Injector mInjector;
+ private final AlarmManager mAlarmManager;
public LockSettingsStrongAuth(Context context) {
+ this(context, new Injector());
+ }
+
+ @VisibleForTesting
+ protected LockSettingsStrongAuth(Context context, Injector injector) {
mContext = context;
- mDefaultStrongAuthFlags = StrongAuthTracker.getDefaultFlags(context);
- mAlarmManager = context.getSystemService(AlarmManager.class);
+ mInjector = injector;
+ mDefaultStrongAuthFlags = mInjector.getDefaultStrongAuthFlags(context);
+ mAlarmManager = mInjector.getAlarmManager(context);
+ }
+
+ /**
+ * Class for injecting dependencies into LockSettingsStrongAuth.
+ */
+ @VisibleForTesting
+ public static class Injector {
+
+ /**
+ * Allows to mock AlarmManager for testing.
+ */
+ @VisibleForTesting
+ public AlarmManager getAlarmManager(Context context) {
+ return context.getSystemService(AlarmManager.class);
+ }
+
+ /**
+ * Allows to get different default StrongAuthFlags for testing.
+ */
+ @VisibleForTesting
+ public int getDefaultStrongAuthFlags(Context context) {
+ return StrongAuthTracker.getDefaultFlags(context);
+ }
+
+ /**
+ * Allows to get different triggerAtMillis values when setting alarms for testing.
+ */
+ @VisibleForTesting
+ public long getNextAlarmTimeMs(long timeout) {
+ return SystemClock.elapsedRealtime() + timeout;
+ }
}
private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
@@ -186,7 +234,8 @@ public class LockSettingsStrongAuth {
private void handleScheduleStrongAuthTimeout(int userId) {
final DevicePolicyManager dpm =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
- long when = SystemClock.elapsedRealtime() + dpm.getRequiredStrongAuthTimeout(null, userId);
+ long nextAlarmTime =
+ mInjector.getNextAlarmTimeMs(dpm.getRequiredStrongAuthTimeout(null, userId));
// cancel current alarm listener for the user (if there was one)
StrongAuthTimeoutAlarmListener alarm = mStrongAuthTimeoutAlarmListenerForUser.get(userId);
if (alarm != null) {
@@ -196,8 +245,8 @@ public class LockSettingsStrongAuth {
mStrongAuthTimeoutAlarmListenerForUser.put(userId, alarm);
}
// schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when, STRONG_AUTH_TIMEOUT_ALARM_TAG,
- alarm, mHandler);
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
+ STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm, mHandler);
// cancel current non-strong biometric alarm listener for the user (if there was one)
cancelNonStrongBiometricAlarmListener(userId);
@@ -209,7 +258,7 @@ public class LockSettingsStrongAuth {
private void handleScheduleNonStrongBiometricTimeout(int userId) {
if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricTimeout for userId=" + userId);
- long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
+ long nextAlarmTime = mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS);
NonStrongBiometricTimeoutAlarmListener alarm = mNonStrongBiometricTimeoutAlarmListener
.get(userId);
if (alarm != null) {
@@ -226,7 +275,7 @@ public class LockSettingsStrongAuth {
alarm = new NonStrongBiometricTimeoutAlarmListener(userId);
mNonStrongBiometricTimeoutAlarmListener.put(userId, alarm);
// schedule a new alarm listener for the user
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
@@ -268,7 +317,8 @@ public class LockSettingsStrongAuth {
}
}
- private void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
+ @VisibleForTesting
+ protected void setIsNonStrongBiometricAllowed(boolean allowed, int userId) {
if (DEBUG) {
Slog.d(TAG, "setIsNonStrongBiometricAllowed for allowed=" + allowed
+ ", userId=" + userId);
@@ -302,7 +352,8 @@ public class LockSettingsStrongAuth {
private void handleScheduleNonStrongBiometricIdleTimeout(int userId) {
if (DEBUG) Slog.d(TAG, "handleScheduleNonStrongBiometricIdleTimeout for userId=" + userId);
- long when = SystemClock.elapsedRealtime() + DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
+ long nextAlarmTime =
+ mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS);
// cancel current alarm listener for the user (if there was one)
NonStrongBiometricIdleTimeoutAlarmListener alarm =
mNonStrongBiometricIdleTimeoutAlarmListener.get(userId);
@@ -315,7 +366,7 @@ public class LockSettingsStrongAuth {
}
// schedule a new alarm listener for the user
if (DEBUG) Slog.d(TAG, "Schedule a new alarm for non-strong biometric idle timeout");
- mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, when,
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, nextAlarmTime,
NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm, mHandler);
}
@@ -435,7 +486,8 @@ public class LockSettingsStrongAuth {
/**
* Alarm of fallback timeout for primary auth
*/
- private class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
+ @VisibleForTesting
+ protected class StrongAuthTimeoutAlarmListener implements OnAlarmListener {
private final int mUserId;
@@ -452,7 +504,8 @@ public class LockSettingsStrongAuth {
/**
* Alarm of fallback timeout for non-strong biometric (i.e. weak or convenience)
*/
- private class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {
+ @VisibleForTesting
+ protected class NonStrongBiometricTimeoutAlarmListener implements OnAlarmListener {
private final int mUserId;
@@ -469,7 +522,8 @@ public class LockSettingsStrongAuth {
/**
* Alarm of idle timeout for non-strong biometric (i.e. weak or convenience biometric)
*/
- private class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {
+ @VisibleForTesting
+ protected class NonStrongBiometricIdleTimeoutAlarmListener implements OnAlarmListener {
private final int mUserId;
@@ -484,7 +538,8 @@ public class LockSettingsStrongAuth {
}
}
- private final Handler mHandler = new Handler() {
+ @VisibleForTesting
+ protected final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 28f838044907..3cf22c85f924 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -215,7 +215,6 @@ class BluetoothRouteProvider {
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
.setDescription(mContext.getResources().getText(
R.string.bluetooth_a2dp_audio_route_name).toString())
- //TODO: Set type correctly (BLUETOOTH_A2DP or HEARING_AID)
.setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.build();
@@ -236,6 +235,8 @@ class BluetoothRouteProvider {
// Update volume when the connection state is changed.
MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route)
.setConnectionState(state);
+ builder.setType(btRoute.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)
+ ? MediaRoute2Info.TYPE_HEARING_AID : MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 0d899974cf93..d8bf9edee2b7 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,6 +16,8 @@
package com.android.server.media;
+import static android.media.MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE;
+import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static android.media.MediaRoute2ProviderService.REQUEST_ID_NONE;
import static android.media.MediaRouter2Utils.getOriginalId;
import static android.media.MediaRouter2Utils.getProviderId;
@@ -247,6 +249,22 @@ class MediaRouter2ServiceImpl {
}
}
+ public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
+ long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
+ Objects.requireNonNull(router, "router must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ notifySessionHintsForCreatingSessionLocked(uniqueRequestId,
+ router, route, sessionHints);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void selectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
MediaRoute2Info route) {
Objects.requireNonNull(router, "router must not be null");
@@ -265,7 +283,6 @@ class MediaRouter2ServiceImpl {
}
}
-
public void deselectRouteWithRouter2(IMediaRouter2 router, String uniqueSessionId,
MediaRoute2Info route) {
Objects.requireNonNull(router, "router must not be null");
@@ -634,12 +651,30 @@ class MediaRouter2ServiceImpl {
long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestCreateSessionOnHandler,
+ obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, /* managerRecord= */ null, route,
+ uniqueRequestId, routerRecord, route,
sessionHints));
}
+ private void notifySessionHintsForCreatingSessionLocked(long uniqueRequestId,
+ @NonNull IMediaRouter2 router,
+ @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+ final IBinder binder = router.asBinder();
+ final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+ if (routerRecord == null) {
+ Slog.w(TAG, "requestCreateSessionWithRouter2ByManagerRequestLocked: "
+ + "Ignoring unknown router.");
+ return;
+ }
+
+ routerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestCreateSessionWithManagerOnHandler,
+ routerRecord.mUserRecord.mHandler,
+ uniqueRequestId, routerRecord, route, sessionHints));
+ }
+
private void selectRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
final IBinder binder = router.asBinder();
@@ -826,12 +861,13 @@ class MediaRouter2ServiceImpl {
}
long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
- //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints.
+
+ // Before requesting to the provider, get session hints from the media router.
+ // As a return, media router will request to create a session.
routerRecord.mUserRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::requestCreateSessionOnHandler,
+ obtainMessage(UserHandler::getSessionHintsForCreatingSessionOnHandler,
routerRecord.mUserRecord.mHandler,
- uniqueRequestId, routerRecord, managerRecord, route,
- /* sessionHints= */ null));
+ uniqueRequestId, routerRecord, managerRecord, route));
}
private void selectRouteWithManagerLocked(int requestId, @NonNull IMediaRouter2Manager manager,
@@ -1149,7 +1185,6 @@ class MediaRouter2ServiceImpl {
this, provider, uniqueRequestId, sessionInfo));
}
-
@Override
public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
@NonNull RoutingSessionInfo sessionInfo) {
@@ -1267,8 +1302,26 @@ class MediaRouter2ServiceImpl {
return -1;
}
- private void requestCreateSessionOnHandler(long uniqueRequestId,
- @NonNull RouterRecord routerRecord, @Nullable ManagerRecord managerRecord,
+ private void getSessionHintsForCreatingSessionOnHandler(long uniqueRequestId,
+ @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
+ @NonNull MediaRoute2Info route) {
+ SessionCreationRequest request =
+ new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
+ mSessionCreationRequests.add(request);
+
+ try {
+ routerRecord.mRouter.getSessionHintsForCreatingSession(uniqueRequestId, route);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "requestGetSessionHintsOnHandler: "
+ + "Failed to request. Router probably died.");
+ mSessionCreationRequests.remove(request);
+ notifyRequestFailedToManager(managerRecord.mManager,
+ toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
+ }
+ }
+
+ private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
+ @NonNull RouterRecord routerRecord,
@NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
@@ -1281,13 +1334,50 @@ class MediaRouter2ServiceImpl {
}
SessionCreationRequest request =
- new SessionCreationRequest(routerRecord, uniqueRequestId, route, managerRecord);
+ new SessionCreationRequest(routerRecord, uniqueRequestId, route, null);
mSessionCreationRequests.add(request);
provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
route.getOriginalId(), sessionHints);
}
+ private void requestCreateSessionWithManagerOnHandler(long uniqueRequestId,
+ @NonNull RouterRecord routerRecord,
+ @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+ SessionCreationRequest matchingRequest = null;
+ for (SessionCreationRequest request : mSessionCreationRequests) {
+ if (request.mUniqueRequestId == uniqueRequestId) {
+ matchingRequest = request;
+ break;
+ }
+ }
+ if (matchingRequest == null) {
+ Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: "
+ + "Ignoring an unknown request.");
+ return;
+ }
+
+ if (!TextUtils.equals(matchingRequest.mRoute.getId(), route.getId())) {
+ Slog.w(TAG, "requestCreateSessionWithKnownRequestOnHandler: "
+ + "The given route is different from the requested route.");
+ return;
+ }
+
+ final MediaRoute2Provider provider = findProvider(route.getProviderId());
+ if (provider == null) {
+ Slog.w(TAG, "Ignoring session creation request since no provider found for"
+ + " given route=" + route);
+
+ mSessionCreationRequests.remove(matchingRequest);
+ notifyRequestFailedToManager(matchingRequest.mRequestedManagerRecord.mManager,
+ toOriginalRequestId(uniqueRequestId), REASON_ROUTE_NOT_AVAILABLE);
+ return;
+ }
+
+ provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
+ route.getOriginalId(), sessionHints);
+ }
+
// routerRecord can be null if the session is system's or RCN.
private void selectRouteOnHandler(long uniqueRequestId, @Nullable RouterRecord routerRecord,
@NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index d6bf9fb4d9b5..bf2cc5e68fac 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -487,6 +487,14 @@ public final class MediaRouterService extends IMediaRouterService.Stub
// Binder call
@Override
+ public void notifySessionHintsForCreatingSession(IMediaRouter2 router,
+ long uniqueRequestId, MediaRoute2Info route, Bundle sessionHints) {
+ mService2.notifySessionHintsForCreatingSession(router,
+ uniqueRequestId, route, sessionHints);
+ }
+
+ // Binder call
+ @Override
public void selectRouteWithRouter2(IMediaRouter2 router, String sessionId,
MediaRoute2Info route) {
mService2.selectRouteWithRouter2(router, sessionId, route);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 1345e3759d2f..41d7fff52a91 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -19,6 +19,11 @@ package com.android.server.media;
import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
import static android.media.MediaRoute2Info.FEATURE_LIVE_VIDEO;
import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER;
+import static android.media.MediaRoute2Info.TYPE_DOCK;
+import static android.media.MediaRoute2Info.TYPE_HDMI;
+import static android.media.MediaRoute2Info.TYPE_USB_DEVICE;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES;
+import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -194,19 +199,27 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
private void updateDeviceRoute(AudioRoutesInfo newRoutes) {
int name = R.string.default_audio_route_name;
+ int type = TYPE_BUILTIN_SPEAKER;
if (newRoutes != null) {
mCurAudioRoutesInfo.mainType = newRoutes.mainType;
- if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0
- || (newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADPHONES) != 0) {
+ type = TYPE_WIRED_HEADPHONES;
+ name = com.android.internal.R.string.default_audio_route_name_headphones;
+ } else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HEADSET) != 0) {
+ type = TYPE_WIRED_HEADSET;
name = com.android.internal.R.string.default_audio_route_name_headphones;
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_DOCK_SPEAKERS) != 0) {
+ type = TYPE_DOCK;
name = com.android.internal.R.string.default_audio_route_name_dock_speakers;
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_HDMI) != 0) {
+ type = TYPE_HDMI;
name = com.android.internal.R.string.default_audio_route_name_hdmi;
} else if ((newRoutes.mainType & AudioRoutesInfo.MAIN_USB) != 0) {
+ type = TYPE_USB_DEVICE;
name = com.android.internal.R.string.default_audio_route_name_usb;
}
}
+
mDeviceRoute = new MediaRoute2Info.Builder(
DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
.setVolumeHandling(mAudioManager.isVolumeFixed()
@@ -214,8 +227,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider {
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- //TODO: Guess the exact type using AudioDevice
- .setType(TYPE_BUILTIN_SPEAKER)
+ .setType(type)
.addFeature(FEATURE_LIVE_AUDIO)
.addFeature(FEATURE_LIVE_VIDEO)
.setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d432fc83b52a..3e6d6f5fe192 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -819,7 +819,9 @@ public class PreferencesHelper implements RankingConfig {
}
if (fromTargetApp) {
channel.setLockscreenVisibility(r.visibility);
- channel.setAllowBubbles(existing != null && existing.canBubble());
+ channel.setAllowBubbles(existing != null
+ ? existing.getAllowBubbles()
+ : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
}
clearLockedFieldsLocked(channel);
channel.setImportanceLockedByOEM(r.oemLockedImportance);
@@ -1704,7 +1706,7 @@ public class PreferencesHelper implements RankingConfig {
if (original.canShowBadge() != update.canShowBadge()) {
update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
}
- if (original.canBubble() != update.canBubble()) {
+ if (original.getAllowBubbles() != update.getAllowBubbles()) {
update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
}
}
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index 1d4843822931..96da649350b0 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -21,7 +21,6 @@ import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
import android.annotation.NonNull;
-import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
@@ -41,9 +40,18 @@ import java.util.List;
/**
* Helper for querying shortcuts.
*/
-class ShortcutHelper {
+public class ShortcutHelper {
private static final String TAG = "ShortcutHelper";
+ private static final IntentFilter SHARING_FILTER = new IntentFilter();
+ static {
+ try {
+ SHARING_FILTER.addDataType("*/*");
+ } catch (IntentFilter.MalformedMimeTypeException e) {
+ Slog.e(TAG, "Bad mime type", e);
+ }
+ }
+
/**
* Listener to call when a shortcut we're tracking has been removed.
*/
@@ -54,7 +62,6 @@ class ShortcutHelper {
private LauncherApps mLauncherAppsService;
private ShortcutListener mShortcutListener;
private ShortcutServiceInternal mShortcutServiceInternal;
- private IntentFilter mSharingFilter;
// Key: packageName Value: <shortcutId, notifId>
private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
@@ -122,12 +129,6 @@ class ShortcutHelper {
ShortcutServiceInternal shortcutServiceInternal) {
mLauncherAppsService = launcherApps;
mShortcutListener = listener;
- mSharingFilter = new IntentFilter();
- try {
- mSharingFilter.addDataType("*/*");
- } catch (IntentFilter.MalformedMimeTypeException e) {
- Slog.e(TAG, "Bad mime type", e);
- }
mShortcutServiceInternal = shortcutServiceInternal;
}
@@ -142,7 +143,21 @@ class ShortcutHelper {
}
/**
- * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}.
+ * Returns whether the given shortcut info is a conversation shortcut.
+ */
+ public static boolean isConversationShortcut(
+ ShortcutInfo shortcutInfo, ShortcutServiceInternal mShortcutServiceInternal,
+ int callingUserId) {
+ if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) {
+ return false;
+ }
+ return mShortcutServiceInternal.isSharingShortcut(callingUserId, "android",
+ shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(),
+ SHARING_FILTER);
+ }
+
+ /**
+ * Only returns shortcut info if it's found and if it's a conversation shortcut.
*/
ShortcutInfo getValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
if (mLauncherAppsService == null) {
@@ -161,11 +176,7 @@ class ShortcutHelper {
ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
? shortcuts.get(0)
: null;
- if (info == null || !info.isLongLived() || !info.isEnabled()) {
- return null;
- }
- if (mShortcutServiceInternal.isSharingShortcut(user.getIdentifier(),
- "android", packageName, shortcutId, user.getIdentifier(), mSharingFilter)) {
+ if (isConversationShortcut(info, mShortcutServiceInternal, user.getIdentifier())) {
return info;
}
return null;
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 7c47cf0450d4..d36d038058f1 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -612,7 +612,7 @@ public class AppsFilter {
}
final int insert = ~loc;
System.arraycopy(appIds, insert, buffer, 0, whitelistSize - insert);
- appIds[insert] = existingUid;
+ appIds[insert] = existingAppId;
System.arraycopy(buffer, 0, appIds, insert + 1, whitelistSize - insert);
whitelistSize++;
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 2221644bff47..6b1ef3acdf61 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -150,7 +150,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -2099,11 +2098,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
continue;
}
- mResolvedInstructionSets.add(archSubDir.getName());
- List<File> oatFiles = Arrays.asList(archSubDir.listFiles());
- if (!oatFiles.isEmpty()) {
- mResolvedInheritedFiles.addAll(oatFiles);
+ File[] files = archSubDir.listFiles();
+ if (files == null || files.length == 0) {
+ continue;
}
+
+ mResolvedInstructionSets.add(archSubDir.getName());
+ mResolvedInheritedFiles.addAll(Arrays.asList(files));
}
}
}
@@ -2117,7 +2118,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (!libDir.exists() || !libDir.isDirectory()) {
continue;
}
- final List<File> libDirsToInherit = new LinkedList<>();
+ final List<String> libDirsToInherit = new ArrayList<>();
+ final List<File> libFilesToInherit = new ArrayList<>();
for (File archSubDir : libDir.listFiles()) {
if (!archSubDir.isDirectory()) {
continue;
@@ -2129,14 +2131,24 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
Slog.e(TAG, "Skipping linking of native library directory!", e);
// shouldn't be possible, but let's avoid inheriting these to be safe
libDirsToInherit.clear();
+ libFilesToInherit.clear();
break;
}
- if (!mResolvedNativeLibPaths.contains(relLibPath)) {
- mResolvedNativeLibPaths.add(relLibPath);
+
+ File[] files = archSubDir.listFiles();
+ if (files == null || files.length == 0) {
+ continue;
+ }
+
+ libDirsToInherit.add(relLibPath);
+ libFilesToInherit.addAll(Arrays.asList(files));
+ }
+ for (String subDir : libDirsToInherit) {
+ if (!mResolvedNativeLibPaths.contains(subDir)) {
+ mResolvedNativeLibPaths.add(subDir);
}
- libDirsToInherit.addAll(Arrays.asList(archSubDir.listFiles()));
}
- mResolvedInheritedFiles.addAll(libDirsToInherit);
+ mResolvedInheritedFiles.addAll(libFilesToInherit);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 07078f26d0b4..7adafe3ed658 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -351,6 +351,7 @@ import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.PackageDexUsage;
import com.android.server.pm.dex.ViewCompiler;
+import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.library.PackageBackwardCompatibility;
@@ -1346,13 +1347,6 @@ public class PackageManagerService extends IPackageManager.Stub
int updatedStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
boolean needUpdate = false;
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG,
- "Updating IntentFilterVerificationInfo for package " + packageName
- + " verificationId:" + verificationId
- + " verified=" + verified);
- }
-
// In a success case, we promote from undefined or ASK to ALWAYS. This
// supports a flow where the app fails validation but then ships an updated
// APK that passes, and therefore deserves to be in ALWAYS.
@@ -17639,65 +17633,120 @@ public class PackageManagerService extends IPackageManager.Stub
+ " Activities needs verification ...");
int count = 0;
-
+ boolean handlesWebUris = false;
+ ArraySet<String> domains = new ArraySet<>();
+ final boolean previouslyVerified;
+ boolean hostSetExpanded = false;
+ boolean needToRunVerify = false;
synchronized (mLock) {
// If this is a new install and we see that we've already run verification for this
// package, we have nothing to do: it means the state was restored from backup.
- if (!replacing) {
- IntentFilterVerificationInfo ivi =
- mSettings.getIntentFilterVerificationLPr(packageName);
- if (ivi != null) {
- if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.i(TAG, "Package " + packageName+ " already verified: status="
- + ivi.getStatusString());
- }
- return;
+ IntentFilterVerificationInfo ivi =
+ mSettings.getIntentFilterVerificationLPr(packageName);
+ previouslyVerified = (ivi != null);
+ if (!replacing && previouslyVerified) {
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.i(TAG, "Package " + packageName + " already verified: status="
+ + ivi.getStatusString());
}
+ return;
}
- // If any filters need to be verified, then all need to be.
- boolean needToVerify = false;
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.i(TAG, " Previous verified hosts: "
+ + (ivi == null ? "[none]" : ivi.getDomainsString()));
+ }
+
+ // If any filters need to be verified, then all need to be. In addition, we need to
+ // know whether an updating app has any web navigation intent filters, to re-
+ // examine handling policy even if not re-verifying.
+ final boolean needsVerification = needsNetworkVerificationLPr(packageName);
for (ParsedActivity a : activities) {
for (ParsedIntentInfo filter : a.getIntents()) {
- if (filter.needsVerification()
- && needsNetworkVerificationLPr(a.getPackageName())) {
+ if (filter.handlesWebUris(true)) {
+ handlesWebUris = true;
+ }
+ if (needsVerification && filter.needsVerification()) {
if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG,
- "Intent filter needs verification, so processing all filters");
+ Slog.d(TAG, "autoVerify requested, processing all filters");
}
- needToVerify = true;
+ needToRunVerify = true;
+ // It's safe to break out here because filter.needsVerification()
+ // can only be true if filter.handlesWebUris(true) returned true, so
+ // we've already noted that.
break;
}
}
}
- if (needToVerify) {
- final boolean needsVerification = needsNetworkVerificationLPr(packageName);
+ // Compare the new set of recognized hosts if the app is either requesting
+ // autoVerify or has previously used autoVerify but no longer does.
+ if (needToRunVerify || previouslyVerified) {
final int verificationId = mIntentFilterVerificationToken++;
for (ParsedActivity a : activities) {
for (ParsedIntentInfo filter : a.getIntents()) {
// Run verification against hosts mentioned in any web-nav intent filter,
// even if the filter matches non-web schemes as well
- if (needsVerification && filter.handlesWebUris(false)) {
+ if (filter.handlesWebUris(false /*onlyWebSchemes*/)) {
if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG,
"Verification needed for IntentFilter:" + filter.toString());
mIntentFilterVerifier.addOneIntentFilterVerification(
verifierUid, userId, verificationId, filter, packageName);
+ domains.addAll(filter.getHostsList());
count++;
}
}
}
}
+
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.i(TAG, " Update published hosts: " + domains.toString());
+ }
+
+ // If we've previously verified this same host set (or a subset), we can trust that
+ // a current ALWAYS policy is still applicable. If this is the case, we're done.
+ // (If we aren't in ALWAYS, we want to reverify to allow for apps that had failing
+ // hosts in their intent filters, then pushed a new apk that removed them and now
+ // passes.)
+ //
+ // Cases:
+ // + still autoVerify (needToRunVerify):
+ // - preserve current state if all of: unexpanded, in always
+ // - otherwise rerun as usual (fall through)
+ // + no longer autoVerify (alreadyVerified && !needToRunVerify)
+ // - wipe verification history always
+ // - preserve current state if all of: unexpanded, in always
+ hostSetExpanded = !previouslyVerified
+ || (ivi != null && !ivi.getDomains().containsAll(domains));
+ final int currentPolicy =
+ mSettings.getIntentFilterVerificationStatusLPr(packageName, userId);
+ final boolean keepCurState = !hostSetExpanded
+ && currentPolicy == INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
+
+ if (needToRunVerify && keepCurState) {
+ if (DEBUG_DOMAIN_VERIFICATION) {
+ Slog.i(TAG, "Host set not expanding + ALWAYS -> no need to reverify");
+ }
+ ivi.setDomains(domains);
+ scheduleWriteSettingsLocked();
+ return;
+ } else if (previouslyVerified && !needToRunVerify) {
+ // Prior autoVerify state but not requesting it now. Clear autoVerify history,
+ // and preserve the always policy iff the host set is not expanding.
+ clearIntentFilterVerificationsLPw(packageName, userId, !keepCurState);
+ return;
+ }
}
- if (count > 0) {
+ if (needToRunVerify && count > 0) {
+ // app requested autoVerify and has at least one matching intent filter
if (DEBUG_DOMAIN_VERIFICATION) Slog.d(TAG, "Starting " + count
+ " IntentFilter verification" + (count > 1 ? "s" : "")
+ " for userId:" + userId);
mIntentFilterVerifier.startVerifications(userId);
} else {
if (DEBUG_DOMAIN_VERIFICATION) {
- Slog.d(TAG, "No filters or not all autoVerify for " + packageName);
+ Slog.d(TAG, "No web filters or no new host policy for " + packageName);
}
}
}
@@ -18402,7 +18451,7 @@ public class PackageManagerService extends IPackageManager.Stub
if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mLock) {
- clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL);
+ clearIntentFilterVerificationsLPw(deletedPs.name, UserHandle.USER_ALL, true);
clearDefaultBrowserIfNeeded(packageName);
mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
removedAppId = mSettings.removePackageLPw(packageName);
@@ -19495,13 +19544,14 @@ public class PackageManagerService extends IPackageManager.Stub
final int packageCount = mPackages.size();
for (int i = 0; i < packageCount; i++) {
AndroidPackage pkg = mPackages.valueAt(i);
- clearIntentFilterVerificationsLPw(pkg.getPackageName(), userId);
+ clearIntentFilterVerificationsLPw(pkg.getPackageName(), userId, true);
}
}
/** This method takes a specific user id as well as UserHandle.USER_ALL. */
@GuardedBy("mLock")
- void clearIntentFilterVerificationsLPw(String packageName, int userId) {
+ void clearIntentFilterVerificationsLPw(String packageName, int userId,
+ boolean alsoResetStatus) {
if (userId == UserHandle.USER_ALL) {
if (mSettings.removeIntentFilterVerificationLPw(packageName,
mUserManager.getUserIds())) {
@@ -19510,7 +19560,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
} else {
- if (mSettings.removeIntentFilterVerificationLPw(packageName, userId)) {
+ if (mSettings.removeIntentFilterVerificationLPw(packageName, userId,
+ alsoResetStatus)) {
scheduleWritePackageRestrictionsLocked(userId);
}
}
@@ -24182,6 +24233,25 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
+ public void pruneCachedApksInApex(@NonNull List<PackageInfo> apexPackages) {
+ if (mCacheDir == null) {
+ return;
+ }
+
+ final PackageCacher cacher = new PackageCacher(mCacheDir);
+ synchronized (mLock) {
+ for (int i = 0, size = apexPackages.size(); i < size; i++) {
+ final List<String> apkNames =
+ mApexManager.getApksInApex(apexPackages.get(i).packageName);
+ for (int j = 0, apksInApex = apkNames.size(); j < apksInApex; j++) {
+ final AndroidPackage pkg = getPackage(apkNames.get(j));
+ cacher.cleanCachedResult(new File(pkg.getCodePath()));
+ }
+ }
+ }
+ }
+
+ @Override
public String getSetupWizardPackageName() {
return mSetupWizardPackage;
}
@@ -24651,6 +24721,14 @@ public class PackageManagerService extends IPackageManager.Stub
return packageName.equals(
PackageManagerService.this.ensureSystemPackageName(packageName));
}
+
+ @Override
+ public void clearBlockUninstallForUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ mSettings.clearBlockUninstallLPw(userId);
+ mSettings.writePackageRestrictionsLPr(userId);
+ }
+ }
}
@GuardedBy("mLock")
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 53c057a58a15..ddeab29c5b78 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -1282,7 +1282,8 @@ public final class Settings {
return result;
}
- boolean removeIntentFilterVerificationLPw(String packageName, int userId) {
+ boolean removeIntentFilterVerificationLPw(String packageName, int userId,
+ boolean alsoResetStatus) {
PackageSetting ps = mPackages.get(packageName);
if (ps == null) {
if (DEBUG_DOMAIN_VERIFICATION) {
@@ -1290,14 +1291,17 @@ public final class Settings {
}
return false;
}
- ps.clearDomainVerificationStatusForUser(userId);
+ if (alsoResetStatus) {
+ ps.clearDomainVerificationStatusForUser(userId);
+ }
+ ps.setIntentFilterVerificationInfo(null);
return true;
}
boolean removeIntentFilterVerificationLPw(String packageName, int[] userIds) {
boolean result = false;
for (int userId : userIds) {
- result |= removeIntentFilterVerificationLPw(packageName, userId);
+ result |= removeIntentFilterVerificationLPw(packageName, userId, true);
}
return result;
}
@@ -1829,6 +1833,10 @@ public final class Settings {
}
}
+ void clearBlockUninstallLPw(int userId) {
+ mBlockUninstallPackages.remove(userId);
+ }
+
boolean getBlockUninstallLPr(int userId, String packageName) {
ArraySet<String> packages = mBlockUninstallPackages.get(userId);
if (packages == null) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index d1d98775c4b6..1c1e64d70bbd 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -1226,8 +1226,9 @@ public class StagingManager {
// APEX checks. For single-package sessions, check if they contain an APEX. For
// multi-package sessions, find all the child sessions that contain an APEX.
if (hasApex) {
+ final List<PackageInfo> apexPackages;
try {
- final List<PackageInfo> apexPackages = submitSessionToApexService(session);
+ apexPackages = submitSessionToApexService(session);
for (int i = 0, size = apexPackages.size(); i < size; i++) {
validateApexSignature(apexPackages.get(i));
}
@@ -1235,6 +1236,10 @@ public class StagingManager {
session.setStagedSessionFailed(e.error, e.getMessage());
return;
}
+
+ final PackageManagerInternal packageManagerInternal =
+ LocalServices.getService(PackageManagerInternal.class);
+ packageManagerInternal.pruneCachedApksInApex(apexPackages);
}
notifyPreRebootVerification_Apex_Complete(session.sessionId);
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index fc70af4e7bd4..4561d2e7a8b2 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -104,6 +104,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
@@ -3153,13 +3154,17 @@ public class UserManagerService extends IUserManager.Stub {
/**
* Removes the app restrictions file for a specific package and user id, if it exists.
+ *
+ * @return whether there were any restrictions.
*/
- private static void cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) {
- File dir = Environment.getUserSystemDirectory(userId);
- File resFile = new File(dir, packageToRestrictionsFileName(pkg));
+ private static boolean cleanAppRestrictionsForPackageLAr(String pkg, @UserIdInt int userId) {
+ final File dir = Environment.getUserSystemDirectory(userId);
+ final File resFile = new File(dir, packageToRestrictionsFileName(pkg));
if (resFile.exists()) {
resFile.delete();
+ return true;
}
+ return false;
}
/**
@@ -4003,17 +4008,24 @@ public class UserManagerService extends IUserManager.Stub {
if (restrictions != null) {
restrictions.setDefusable(true);
}
+ final boolean changed;
synchronized (mAppRestrictionsLock) {
if (restrictions == null || restrictions.isEmpty()) {
- cleanAppRestrictionsForPackageLAr(packageName, userId);
+ changed = cleanAppRestrictionsForPackageLAr(packageName, userId);
} else {
// Write the restrictions to XML
writeApplicationRestrictionsLAr(packageName, restrictions, userId);
+ // TODO(b/154323615): avoid unnecessary broadcast when there is no change.
+ changed = true;
}
}
+ if (!changed) {
+ return;
+ }
+
// Notify package of changes via an intent - only sent to explicitly registered receivers.
- Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+ final Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
changeIntent.setPackage(packageName);
changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
@@ -4505,6 +4517,8 @@ public class UserManagerService extends IUserManager.Stub {
switch(cmd) {
case "list":
return runList(pw, shell);
+ case "list-missing-system-packages":
+ return runListMissingSystemPackages(pw, shell);
default:
return shell.handleDefaultCommands(cmd);
}
@@ -4571,6 +4585,30 @@ public class UserManagerService extends IUserManager.Stub {
}
}
+ private int runListMissingSystemPackages(PrintWriter pw, Shell shell) {
+ boolean verbose = false;
+ boolean force = false;
+ String opt;
+ while ((opt = shell.getNextOption()) != null) {
+ switch (opt) {
+ case "-v":
+ verbose = true;
+ break;
+ case "--force":
+ force = true;
+ break;
+ default:
+ pw.println("Invalid option: " + opt);
+ return -1;
+ }
+ }
+
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose);
+ }
+ return 0;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -5143,6 +5181,9 @@ public class UserManagerService extends IUserManager.Stub {
pw.println("");
pw.println(" list [-v] [-all]");
pw.println(" Prints all users on the system.");
+ pw.println(" list-missing-system-packages [-v] [--force]");
+ pw.println(" Prints all system packages that were not explicitly configured to be "
+ + "installed.");
}
}
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 85c2306f6d89..cd1087f5fcd7 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -22,15 +22,16 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
import android.content.res.Resources;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.SystemConfig;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -38,7 +39,9 @@ import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -263,37 +266,85 @@ class UserSystemPackageInstaller {
if (!isLogMode(mode) && !isEnforceMode(mode)) {
return;
}
- Slog.v(TAG, "Checking that all system packages are whitelisted.");
+ final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
+ final int size = warnings.size();
+ if (size == 0) {
+ Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings");
+ return;
+ }
+
+ if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+ // Only shows whether all whitelisted packages are indeed on the system.
+ for (int i = 0; i < size; i++) {
+ final Pair<Boolean, String> pair = warnings.get(i);
+ final boolean isSevere = pair.first;
+ if (!isSevere) {
+ final String msg = pair.second;
+ Slog.w(TAG, msg);
+ }
+ }
+ return;
+ }
+
+ Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings");
+ boolean doWtf = !isImplicitWhitelistMode(mode);
+ for (int i = 0; i < size; i++) {
+ final Pair<Boolean, String> pair = warnings.get(i);
+ final boolean isSevere = pair.first;
+ final String msg = pair.second;
+ if (isSevere) {
+ if (doWtf) {
+ Slog.wtf(TAG, msg);
+ } else {
+ Slog.e(TAG, msg);
+ }
+ } else {
+ Slog.w(TAG, msg);
+ }
+ }
+ }
+
+ // TODO: method below was created to refactor the one-time logging logic so it can be used on
+ // dump / cmd as well. It could to be further refactored (for example, creating a new
+ // structure for the warnings so it doesn't need a Pair).
+ /**
+ * Gets warnings for system user whitelisting.
+ *
+ * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF,
+ * {@code false} for WARN) and {@code Pair.second} the message.
+ */
+ @NonNull
+ private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings(
+ @PackageWhitelistMode int mode) {
final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
- PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ final List<Pair<Boolean, String>> warnings = new ArrayList<>();
+ final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
// Check whether all whitelisted packages are indeed on the system.
+ final String notPresentFmt = "%s is whitelisted but not present.";
+ final String notSystemFmt = "%s is whitelisted and present but not a system package.";
for (String pkgName : allWhitelistedPackages) {
- AndroidPackage pkg = pmInt.getPackage(pkgName);
+ final AndroidPackage pkg = pmInt.getPackage(pkgName);
if (pkg == null) {
- Slog.w(TAG, pkgName + " is whitelisted but not present.");
+ warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName)));
} else if (!pkg.isSystem()) {
- Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
+ warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName)));
}
}
// Check whether all system packages are indeed whitelisted.
- if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
- return;
- }
- final boolean doWtf = isEnforceMode(mode);
+ final String logMessageFmt = "System package %s is not whitelisted using "
+ + "'install-in-user-type' in SystemConfig for any user types!";
+ final boolean isSevere = isEnforceMode(mode);
pmInt.forEachPackage(pkg -> {
- if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.getManifestPackageName())) {
- final String msg = "System package " + pkg.getManifestPackageName()
- + " is not whitelisted using 'install-in-user-type' in SystemConfig "
- + "for any user types!";
- if (doWtf) {
- Slog.wtf(TAG, msg);
- } else {
- Slog.e(TAG, msg);
- }
+ if (!pkg.isSystem()) return;
+ final String pkgName = pkg.getManifestPackageName();
+ if (!allWhitelistedPackages.contains(pkgName)) {
+ warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName)));
}
});
+
+ return warnings;
}
/** Whether to only install system packages in new users for which they are whitelisted. */
@@ -602,32 +653,45 @@ class UserSystemPackageInstaller {
}
void dump(PrintWriter pw) {
- final String prefix = " ";
- final String prefix2 = prefix + prefix;
+ try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ")) {
+ dumpIndented(ipw);
+ }
+ }
+
+ private void dumpIndented(IndentingPrintWriter pw) {
final int mode = getWhitelistMode();
pw.println("Whitelisted packages per user type");
- pw.print(prefix); pw.print("Mode: ");
+
+ pw.increaseIndent();
+ pw.print("Mode: ");
pw.print(mode);
pw.print(isEnforceMode(mode) ? " (enforced)" : "");
pw.print(isLogMode(mode) ? " (logged)" : "");
pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : "");
pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : "");
pw.println();
+ pw.decreaseIndent();
- pw.print(prefix); pw.println("Legend");
+ pw.increaseIndent();
+ pw.println("Legend");
+ pw.increaseIndent();
for (int idx = 0; idx < mUserTypes.length; idx++) {
- pw.print(prefix2); pw.println(idx + " -> " + mUserTypes[idx]);
+ pw.println(idx + " -> " + mUserTypes[idx]);
}
+ pw.decreaseIndent(); pw.decreaseIndent();
+ pw.increaseIndent();
final int size = mWhitelistedPackagesForUserTypes.size();
if (size == 0) {
- pw.print(prefix); pw.println("No packages");
+ pw.println("No packages");
+ pw.decreaseIndent();
return;
}
- pw.print(prefix); pw.print(size); pw.println(" packages:");
+ pw.print(size); pw.println(" packages:");
+ pw.increaseIndent();
for (int pkgIdx = 0; pkgIdx < size; pkgIdx++) {
final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(pkgIdx);
- pw.print(prefix2); pw.print(pkgName); pw.print(": ");
+ pw.print(pkgName); pw.print(": ");
final long userTypesBitSet = mWhitelistedPackagesForUserTypes.valueAt(pkgIdx);
for (int idx = 0; idx < mUserTypes.length; idx++) {
if ((userTypesBitSet & (1 << idx)) != 0) {
@@ -636,5 +700,40 @@ class UserSystemPackageInstaller {
}
pw.println();
}
+ pw.decreaseIndent(); pw.decreaseIndent();
+
+ pw.increaseIndent();
+ dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true);
+ pw.decreaseIndent();
+ }
+
+ void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) {
+ final int mode = getWhitelistMode();
+ final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode));
+ if (!show) return;
+
+ final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
+ final int size = warnings.size();
+
+ if (size == 0) {
+ if (verbose) {
+ pw.println("All system packages are accounted for");
+ }
+ return;
+ }
+
+ if (verbose) {
+ pw.print(size); pw.println(" warnings for system user:");
+ pw.increaseIndent();
+ }
+ for (int i = 0; i < size; i++) {
+ final Pair<Boolean, String> pair = warnings.get(i);
+ final String lvl = pair.first ? "WTF" : "WARN";
+ final String msg = pair.second;
+ pw.print(lvl); pw.print(": "); pw.println(msg);
+ }
+ if (verbose) {
+ pw.decreaseIndent();
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index e5e1b0b20955..99c6dd1f0312 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -18,6 +18,7 @@ package com.android.server.pm.parsing;
import android.annotation.NonNull;
import android.content.pm.PackageParserCacheHelper;
+import android.os.FileUtils;
import android.os.Parcel;
import android.system.ErrnoException;
import android.system.Os;
@@ -197,4 +198,18 @@ public class PackageCacher {
Slog.w(TAG, "Error saving package cache.", e);
}
}
+
+ /**
+ * Delete the cache files for the given {@code packageFile}.
+ */
+ public void cleanCachedResult(@NonNull File packageFile) {
+ final String packageName = packageFile.getName();
+ final File[] files = FileUtils.listFilesOrEmpty(mCacheDir,
+ (dir, name) -> name.startsWith(packageName));
+ for (File file : files) {
+ if (!file.delete()) {
+ Slog.e(TAG, "Unable to clean cache file: " + file);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4624e9ea0209..2f84a99774f4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -62,6 +62,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
import static android.view.WindowManagerGlobal.ADD_OKAY;
@@ -1328,6 +1330,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mScreenshotChordVolumeDownKeyConsumed = true;
cancelPendingPowerKeyAction();
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
+ mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_CHORD);
mHandler.postDelayed(mScreenshotRunnable, getScreenshotChordLongPressDelay());
}
}
@@ -1411,14 +1414,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private class ScreenshotRunnable implements Runnable {
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
+ private int mScreenshotSource = SCREENSHOT_KEY_OTHER;
public void setScreenshotType(int screenshotType) {
mScreenshotType = screenshotType;
}
+ public void setScreenshotSource(int screenshotSource) {
+ mScreenshotSource = screenshotSource;
+ }
+
@Override
public void run() {
- mDefaultDisplayPolicy.takeScreenshot(mScreenshotType);
+ mDefaultDisplayPolicy.takeScreenshot(mScreenshotType, mScreenshotSource);
}
}
@@ -2693,6 +2701,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
: TAKE_SCREENSHOT_FULLSCREEN;
mScreenshotRunnable.setScreenshotType(type);
+ mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
mHandler.post(mScreenshotRunnable);
return -1;
}
@@ -2709,6 +2718,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else if (keyCode == KeyEvent.KEYCODE_SYSRQ) {
if (down && repeatCount == 0) {
mScreenshotRunnable.setScreenshotType(TAKE_SCREENSHOT_FULLSCREEN);
+ mScreenshotRunnable.setScreenshotSource(SCREENSHOT_KEY_OTHER);
mHandler.post(mScreenshotRunnable);
}
return -1;
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
index 1ab6adee2320..b69c45070487 100644
--- a/services/core/java/com/android/server/power/AttentionDetector.java
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -17,7 +17,6 @@
package com.android.server.power;
import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
-import static android.provider.Settings.Secure.ADAPTIVE_SLEEP;
import android.Manifest;
import android.app.ActivityManager;
@@ -192,9 +191,6 @@ public class AttentionDetector {
}
if (!isAttentionServiceSupported() || !serviceHasSufficientPermissions()) {
- // Turns off adaptive sleep in settings for all users if attention service is not
- // available. The setting itself should also be grayed out in this case.
- Settings.Secure.putInt(mContentResolver, ADAPTIVE_SLEEP, 0);
return nextScreenDimming;
}
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
index 059861b65e20..701197e690fc 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverPolicy.java
@@ -214,7 +214,7 @@ public class BatterySaverPolicy extends ContentObserver {
* required adjustments.
*/
@GuardedBy("mLock")
- private Policy mEffectivePolicy = OFF_POLICY;
+ private Policy mEffectivePolicyRaw = OFF_POLICY;
@IntDef(prefix = {"POLICY_LEVEL_"}, value = {
POLICY_LEVEL_OFF,
@@ -228,12 +228,8 @@ public class BatterySaverPolicy extends ContentObserver {
static final int POLICY_LEVEL_ADAPTIVE = 1;
static final int POLICY_LEVEL_FULL = 2;
- /**
- * Do not access directly; always use {@link #setPolicyLevel}
- * and {@link #getPolicyLevelLocked}
- */
@GuardedBy("mLock")
- private int mPolicyLevelRaw = POLICY_LEVEL_OFF;
+ private int mPolicyLevel = POLICY_LEVEL_OFF;
private final Context mContext;
private final ContentResolver mContentResolver;
@@ -338,7 +334,7 @@ public class BatterySaverPolicy extends ContentObserver {
private void maybeNotifyListenersOfPolicyChange() {
final BatterySaverPolicyListener[] listeners;
synchronized (mLock) {
- if (getPolicyLevelLocked() == POLICY_LEVEL_OFF) {
+ if (mPolicyLevel == POLICY_LEVEL_OFF) {
// Current policy is OFF, so there's no change to notify listeners of.
return;
}
@@ -428,14 +424,14 @@ public class BatterySaverPolicy extends ContentObserver {
boolean changed = false;
Policy newFullPolicy = Policy.fromSettings(setting, deviceSpecificSetting,
DEFAULT_FULL_POLICY);
- if (getPolicyLevelLocked() == POLICY_LEVEL_FULL && !mFullPolicy.equals(newFullPolicy)) {
+ if (mPolicyLevel == POLICY_LEVEL_FULL && !mFullPolicy.equals(newFullPolicy)) {
changed = true;
}
mFullPolicy = newFullPolicy;
mDefaultAdaptivePolicy = Policy.fromSettings(adaptiveSetting, adaptiveDeviceSpecificSetting,
DEFAULT_ADAPTIVE_POLICY);
- if (getPolicyLevelLocked() == POLICY_LEVEL_ADAPTIVE
+ if (mPolicyLevel == POLICY_LEVEL_ADAPTIVE
&& !mAdaptivePolicy.equals(mDefaultAdaptivePolicy)) {
changed = true;
}
@@ -451,8 +447,9 @@ public class BatterySaverPolicy extends ContentObserver {
@GuardedBy("mLock")
private void updatePolicyDependenciesLocked() {
final Policy rawPolicy = getCurrentRawPolicyLocked();
-
final int locationMode;
+
+ invalidatePowerSaveModeCaches();
if (mCarModeEnabled
&& rawPolicy.locationMode != PowerManager.LOCATION_MODE_NO_CHANGE
&& rawPolicy.locationMode != PowerManager.LOCATION_MODE_FOREGROUND_ONLY) {
@@ -461,7 +458,8 @@ public class BatterySaverPolicy extends ContentObserver {
} else {
locationMode = rawPolicy.locationMode;
}
- mEffectivePolicy = new Policy(
+
+ mEffectivePolicyRaw = new Policy(
rawPolicy.adjustBrightnessFactor,
rawPolicy.advertiseIsEnabled,
rawPolicy.deferFullBackup,
@@ -489,24 +487,24 @@ public class BatterySaverPolicy extends ContentObserver {
final StringBuilder sb = new StringBuilder();
- if (mEffectivePolicy.forceAllAppsStandby) sb.append("A");
- if (mEffectivePolicy.forceBackgroundCheck) sb.append("B");
+ if (mEffectivePolicyRaw.forceAllAppsStandby) sb.append("A");
+ if (mEffectivePolicyRaw.forceBackgroundCheck) sb.append("B");
- if (mEffectivePolicy.disableVibration) sb.append("v");
- if (mEffectivePolicy.disableAnimation) sb.append("a");
- if (mEffectivePolicy.disableSoundTrigger) sb.append("s");
- if (mEffectivePolicy.deferFullBackup) sb.append("F");
- if (mEffectivePolicy.deferKeyValueBackup) sb.append("K");
- if (mEffectivePolicy.enableFirewall) sb.append("f");
- if (mEffectivePolicy.enableDataSaver) sb.append("d");
- if (mEffectivePolicy.enableAdjustBrightness) sb.append("b");
+ if (mEffectivePolicyRaw.disableVibration) sb.append("v");
+ if (mEffectivePolicyRaw.disableAnimation) sb.append("a");
+ if (mEffectivePolicyRaw.disableSoundTrigger) sb.append("s");
+ if (mEffectivePolicyRaw.deferFullBackup) sb.append("F");
+ if (mEffectivePolicyRaw.deferKeyValueBackup) sb.append("K");
+ if (mEffectivePolicyRaw.enableFirewall) sb.append("f");
+ if (mEffectivePolicyRaw.enableDataSaver) sb.append("d");
+ if (mEffectivePolicyRaw.enableAdjustBrightness) sb.append("b");
- if (mEffectivePolicy.disableLaunchBoost) sb.append("l");
- if (mEffectivePolicy.disableOptionalSensors) sb.append("S");
- if (mEffectivePolicy.disableAod) sb.append("o");
- if (mEffectivePolicy.enableQuickDoze) sb.append("q");
+ if (mEffectivePolicyRaw.disableLaunchBoost) sb.append("l");
+ if (mEffectivePolicyRaw.disableOptionalSensors) sb.append("S");
+ if (mEffectivePolicyRaw.disableAod) sb.append("o");
+ if (mEffectivePolicyRaw.enableQuickDoze) sb.append("q");
- sb.append(mEffectivePolicy.locationMode);
+ sb.append(mEffectivePolicyRaw.locationMode);
mEventLogKeys = sb.toString();
}
@@ -969,14 +967,14 @@ public class BatterySaverPolicy extends ContentObserver {
*/
boolean setPolicyLevel(@PolicyLevel int level) {
synchronized (mLock) {
- if (getPolicyLevelLocked() == level) {
+ if (mPolicyLevel == level) {
return false;
}
switch (level) {
case POLICY_LEVEL_FULL:
case POLICY_LEVEL_ADAPTIVE:
case POLICY_LEVEL_OFF:
- setPolicyLevelLocked(level);
+ mPolicyLevel = level;
break;
default:
Slog.wtf(TAG, "setPolicyLevel invalid level given: " + level);
@@ -998,7 +996,7 @@ public class BatterySaverPolicy extends ContentObserver {
}
mAdaptivePolicy = p;
- if (getPolicyLevelLocked() == POLICY_LEVEL_ADAPTIVE) {
+ if (mPolicyLevel == POLICY_LEVEL_ADAPTIVE) {
updatePolicyDependenciesLocked();
return true;
}
@@ -1011,11 +1009,11 @@ public class BatterySaverPolicy extends ContentObserver {
}
private Policy getCurrentPolicyLocked() {
- return mEffectivePolicy;
+ return mEffectivePolicyRaw;
}
private Policy getCurrentRawPolicyLocked() {
- switch (getPolicyLevelLocked()) {
+ switch (mPolicyLevel) {
case POLICY_LEVEL_FULL:
return mFullPolicy;
case POLICY_LEVEL_ADAPTIVE:
@@ -1077,12 +1075,12 @@ public class BatterySaverPolicy extends ContentObserver {
pw.println(" mAccessibilityEnabled=" + mAccessibilityEnabled);
pw.println(" mCarModeEnabled=" + mCarModeEnabled);
- pw.println(" mPolicyLevel=" + getPolicyLevelLocked());
+ pw.println(" mPolicyLevel=" + mPolicyLevel);
dumpPolicyLocked(pw, " ", "full", mFullPolicy);
dumpPolicyLocked(pw, " ", "default adaptive", mDefaultAdaptivePolicy);
dumpPolicyLocked(pw, " ", "current adaptive", mAdaptivePolicy);
- dumpPolicyLocked(pw, " ", "effective", mEffectivePolicy);
+ dumpPolicyLocked(pw, " ", "effective", mEffectivePolicyRaw);
}
}
@@ -1170,20 +1168,4 @@ public class BatterySaverPolicy extends ContentObserver {
}
}
}
-
- /** Non-blocking getter exists as a reminder not to modify cached fields directly */
- @GuardedBy("mLock")
- private int getPolicyLevelLocked() {
- return mPolicyLevelRaw;
- }
-
- @GuardedBy("mLock")
- private void setPolicyLevelLocked(int level) {
- if (mPolicyLevelRaw == level) {
- return;
- }
- // Under lock, invalidate before set ensures caches won't return stale values.
- invalidatePowerSaveModeCaches();
- mPolicyLevelRaw = level;
- }
}
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 801d75b90a54..f6d46e24246c 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.VersionedPackage;
import android.content.rollback.PackageRollbackInfo;
import android.content.rollback.RollbackInfo;
@@ -41,10 +42,13 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
import com.android.server.PackageWatchdog;
import com.android.server.PackageWatchdog.FailureReasons;
import com.android.server.PackageWatchdog.PackageHealthObserver;
import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.pm.ApexManager;
+import com.android.server.pm.parsing.pkg.AndroidPackage;
import java.io.BufferedReader;
import java.io.File;
@@ -71,6 +75,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
private final Context mContext;
private final Handler mHandler;
+ private final ApexManager mApexManager;
private final File mLastStagedRollbackIdsFile;
// Staged rollback ids that have been committed but their session is not yet ready
@GuardedBy("mPendingStagedRollbackIds")
@@ -85,6 +90,7 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
dataDir.mkdirs();
mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
PackageWatchdog.getInstance(mContext).registerHealthObserver(this);
+ mApexManager = ApexManager.getInstance();
}
@Override
@@ -302,6 +308,18 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
* Returns true if the package name is the name of a module.
*/
private boolean isModule(String packageName) {
+ // Check if the package is an APK inside an APEX. If it is, use the parent APEX package when
+ // querying PackageManager.
+ PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage apkPackage = pmi.getPackage(packageName);
+ if (apkPackage != null) {
+ String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
+ apkPackage);
+ if (apexPackageName != null) {
+ packageName = apexPackageName;
+ }
+ }
+
PackageManager pm = mContext.getPackageManager();
try {
return pm.getModuleInfo(packageName, 0) != null;
diff --git a/services/core/java/com/android/server/tv/TvRemoteServiceInput.java b/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
index 8fe6da5e8dbe..390340a13e51 100644
--- a/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
+++ b/services/core/java/com/android/server/tv/TvRemoteServiceInput.java
@@ -88,6 +88,47 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
}
@Override
+ public void openGamepadBridge(IBinder token, String name) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, String.format("openGamepadBridge(), token: %s, name: %s", token, name));
+ }
+
+ synchronized (mLock) {
+ if (mBridgeMap.containsKey(token)) {
+ if (DEBUG) {
+ Slog.d(TAG, "InputBridge already exists");
+ }
+ } else {
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ mBridgeMap.put(token, UinputBridge.openGamepad(token, name));
+ token.linkToDeath(new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ closeInputBridge(token);
+ }
+ }, 0);
+ } catch (IOException e) {
+ Slog.e(TAG, "Cannot create device for " + name);
+ return;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Token is already dead");
+ closeInputBridge(token);
+ return;
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ try {
+ mProvider.onInputBridgeConnected(token);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed remote call to onInputBridgeConnected");
+ }
+ }
+
+ @Override
public void closeInputBridge(IBinder token) {
if (DEBUG) {
Slog.d(TAG, "closeInputBridge(), token: " + token);
@@ -96,6 +137,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.remove(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -117,6 +159,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -145,6 +188,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -166,6 +210,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -188,6 +233,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -209,6 +255,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -230,6 +277,7 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
synchronized (mLock) {
UinputBridge inputBridge = mBridgeMap.get(token);
if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
return;
}
@@ -241,4 +289,67 @@ final class TvRemoteServiceInput extends ITvRemoteServiceInput.Stub {
}
}
}
+
+ @Override
+ public void sendGamepadKeyUp(IBinder token, int keyIndex) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, String.format("sendGamepadKeyUp(), token: %s", token));
+ }
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendGamepadKey(token, keyIndex, false);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendGamepadKeyDown(IBinder token, int keyCode) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, String.format("sendGamepadKeyDown(), token: %s", token));
+ }
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendGamepadKey(token, keyCode, true);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
+
+ @Override
+ public void sendGamepadAxisValue(IBinder token, int axis, float value) {
+ if (DEBUG_KEYS) {
+ Slog.d(TAG, String.format("sendGamepadAxisValue(), token: %s", token));
+ }
+ synchronized (mLock) {
+ UinputBridge inputBridge = mBridgeMap.get(token);
+ if (inputBridge == null) {
+ Slog.w(TAG, String.format("Input bridge not found for token: %s", token));
+ return;
+ }
+
+ final long idToken = Binder.clearCallingIdentity();
+ try {
+ inputBridge.sendGamepadAxisValue(token, axis, value);
+ } finally {
+ Binder.restoreCallingIdentity(idToken);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/tv/UinputBridge.java b/services/core/java/com/android/server/tv/UinputBridge.java
index a2fe5fcde8c2..1dc201d4ee6b 100644
--- a/services/core/java/com/android/server/tv/UinputBridge.java
+++ b/services/core/java/com/android/server/tv/UinputBridge.java
@@ -42,21 +42,27 @@ public final class UinputBridge {
/** Opens a gamepad - will support gamepad key and axis sending */
private static native long nativeGamepadOpen(String name, String uniqueId);
- /** Marks the specified key up/down for a gamepad */
- private static native void nativeSendGamepadKey(long ptr, int keyIndex, boolean down);
+ /**
+ * Marks the specified key up/down for a gamepad.
+ *
+ * @param keyCode - a code like BUTTON_MODE, BUTTON_A, BUTTON_B, ...
+ */
+ private static native void nativeSendGamepadKey(long ptr, int keyCode, boolean down);
/**
- * Gamepads pre-define the following axes:
- * - Left joystick X, axis == ABS_X == 0, range [0, 254]
- * - Left joystick Y, axis == ABS_Y == 1, range [0, 254]
- * - Right joystick X, axis == ABS_RX == 3, range [0, 254]
- * - Right joystick Y, axis == ABS_RY == 4, range [0, 254]
- * - Left trigger, axis == ABS_Z == 2, range [0, 254]
- * - Right trigger, axis == ABS_RZ == 5, range [0, 254]
- * - DPad X, axis == ABS_HAT0X == 0x10, range [-1, 1]
- * - DPad Y, axis == ABS_HAT0Y == 0x11, range [-1, 1]
+ * Send an axis value.
+ *
+ * Available axes are:
+ * <li> Left joystick: AXIS_X, AXIS_Y
+ * <li> Right joystick: AXIS_Z, AXIS_RZ
+ * <li> Analog triggers: AXIS_LTRIGGER, AXIS_RTRIGGER
+ * <li> DPad: AXIS_HAT_X, AXIS_HAT_Y
+ *
+ * @param axis is a MotionEvent.AXIS_* value.
+ * @param value is a value between -1 and 1 (inclusive)
+ *
*/
- private static native void nativeSendGamepadAxisValue(long ptr, int axis, int value);
+ private static native void nativeSendGamepadAxisValue(long ptr, int axis, float value);
public UinputBridge(IBinder token, String name, int width, int height, int maxPointers)
throws IOException {
@@ -163,26 +169,19 @@ public final class UinputBridge {
* @param keyIndex - the index of the w3-spec key
* @param down - is the key pressed ?
*/
- public void sendGamepadKey(IBinder token, int keyIndex, boolean down) {
+ public void sendGamepadKey(IBinder token, int keyCode, boolean down) {
if (isTokenValid(token)) {
- nativeSendGamepadKey(mPtr, keyIndex, down);
+ nativeSendGamepadKey(mPtr, keyCode, down);
}
}
- /** Send a gamepad axis value.
- * - Left joystick X, axis == ABS_X == 0, range [0, 254]
- * - Left joystick Y, axis == ABS_Y == 1, range [0, 254]
- * - Right joystick X, axis == ABS_RX == 3, range [0, 254]
- * - Right joystick Y, axis == ABS_RY == 4, range [0, 254]
- * - Left trigger, axis == ABS_Z == 2, range [0, 254]
- * - Right trigger, axis == ABS_RZ == 5, range [0, 254]
- * - DPad X, axis == ABS_HAT0X == 0x10, range [-1, 1]
- * - DPad Y, axis == ABS_HAT0Y == 0x11, range [-1, 1]
+ /**
+ * Send a gamepad axis value.
*
- * @param axis is the axis index
- * @param value is the value to set for that axis
+ * @param axis is the axis code (MotionEvent.AXIS_*)
+ * @param value is the value to set for that axis in [-1, 1]
*/
- public void sendGamepadAxisValue(IBinder token, int axis, int value) {
+ public void sendGamepadAxisValue(IBinder token, int axis, float value) {
if (isTokenValid(token)) {
nativeSendGamepadAxisValue(mPtr, axis, value);
}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
new file mode 100644
index 000000000000..54ad1d268e56
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.tv.tunerresourcemanager;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Cas resource object used by the Tuner Resource Manager to record the cas
+ * information.
+ *
+ * @hide
+ */
+public final class CasResource {
+
+ private final int mSystemId;
+
+ private int mMaxSessionNum;
+
+ private int mAvailableSessionNum;
+
+ /**
+ * The owner clients' ids when part of the Cas is occupied.
+ */
+ private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>();
+
+ private CasResource(Builder builder) {
+ this.mSystemId = builder.mSystemId;
+ this.mMaxSessionNum = builder.mMaxSessionNum;
+ this.mAvailableSessionNum = builder.mMaxSessionNum;
+ }
+
+ public int getSystemId() {
+ return mSystemId;
+ }
+
+ public int getMaxSessionNum() {
+ return mMaxSessionNum;
+ }
+
+ public int getUsedSessionNum() {
+ return (mMaxSessionNum - mAvailableSessionNum);
+ }
+
+ public boolean isFullyUsed() {
+ return mAvailableSessionNum == 0;
+ }
+
+ /**
+ * Update max session number.
+ *
+ * @param maxSessionNum the new max session num.
+ */
+ public void updateMaxSessionNum(int maxSessionNum) {
+ mAvailableSessionNum = Math.max(
+ 0, mAvailableSessionNum + (maxSessionNum - mMaxSessionNum));
+ mMaxSessionNum = maxSessionNum;
+ }
+
+ /**
+ * Set an owner for the cas
+ *
+ * @param ownerId the client id of the owner.
+ */
+ public void setOwner(int ownerId) {
+ int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId) == null
+ ? 1 : (mOwnerClientIdsToSessionNum.get(ownerId) + 1);
+ mOwnerClientIdsToSessionNum.put(ownerId, sessionNum);
+ mAvailableSessionNum--;
+ }
+
+ /**
+ * Remove an owner of the Cas.
+ *
+ * @param ownerId the removing client id of the owner.
+ */
+ public void removeOwner(int ownerId) {
+ mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+ mOwnerClientIdsToSessionNum.remove(ownerId);
+ }
+
+ public Set<Integer> getOwnerClientIds() {
+ return mOwnerClientIdsToSessionNum.keySet();
+ }
+
+ @Override
+ public String toString() {
+ return "CasResource[systemId=" + this.mSystemId
+ + ", isFullyUsed=" + (this.mAvailableSessionNum == 0)
+ + ", maxSessionNum=" + this.mMaxSessionNum
+ + ", ownerClients=" + ownersMapToString() + "]";
+ }
+
+ /**
+ * Builder class for {@link CasResource}.
+ */
+ public static class Builder {
+
+ private int mSystemId;
+ private int mMaxSessionNum;
+
+ Builder(int systemId) {
+ this.mSystemId = systemId;
+ }
+
+ /**
+ * Builder for {@link CasResource}.
+ *
+ * @param maxSessionNum the max session num the current Cas has.
+ */
+ public Builder maxSessionNum(int maxSessionNum) {
+ this.mMaxSessionNum = maxSessionNum;
+ return this;
+ }
+
+ /**
+ * Build a {@link CasResource}.
+ *
+ * @return {@link CasResource}.
+ */
+ public CasResource build() {
+ CasResource cas = new CasResource(this);
+ return cas;
+ }
+ }
+
+ private String ownersMapToString() {
+ StringBuilder string = new StringBuilder("{");
+ for (int clienId : mOwnerClientIdsToSessionNum.keySet()) {
+ string.append(" clientId=")
+ .append(clienId)
+ .append(", owns session num=")
+ .append(mOwnerClientIdsToSessionNum.get(clienId))
+ .append(",");
+ }
+ return string.append("}").toString();
+ }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 4cdc17292ac4..2b0fe8a2602b 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -27,6 +27,7 @@ import java.util.Set;
public final class ClientProfile {
public static final int INVALID_GROUP_ID = -1;
+ public static final int INVALID_RESOURCE_ID = -1;
/**
* Client id sent to the client when registering with
@@ -56,7 +57,6 @@ public final class ClientProfile {
* also lose their resources.
*/
private int mGroupId = INVALID_GROUP_ID;
-
/**
* Optional nice value for TRM to reduce client’s priority.
*/
@@ -73,6 +73,11 @@ public final class ClientProfile {
private Set<Integer> mUsingLnbIds = new HashSet<>();
/**
+ * List of the Cas system ids that are used by the current client.
+ */
+ private int mUsingCasSystemId = INVALID_RESOURCE_ID;
+
+ /**
* Optional arbitrary priority value given by the client.
*
* <p>This value can override the default priorotiy calculated from
@@ -172,11 +177,32 @@ public final class ClientProfile {
}
/**
+ * Set when the client starts to use a Cas system.
+ *
+ * @param casSystemId cas being used.
+ */
+ public void useCas(int casSystemId) {
+ mUsingCasSystemId = casSystemId;
+ }
+
+ public int getInUseCasSystemId() {
+ return mUsingCasSystemId;
+ }
+
+ /**
+ * Called when the client released a Cas System.
+ */
+ public void releaseCas() {
+ mUsingCasSystemId = INVALID_RESOURCE_ID;
+ }
+
+ /**
* Called to reclaim all the resources being used by the current client.
*/
public void reclaimAllResources() {
mUsingFrontendIds.clear();
mUsingLnbIds.clear();
+ mUsingCasSystemId = INVALID_RESOURCE_ID;
}
@Override
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 7231813dd949..2f70840cfc8b 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -65,6 +65,8 @@ public class TunerResourceManagerService extends SystemService {
private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
// Map of the current available lnb resources
private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
+ // Map of the current available Cas resources
+ private Map<Integer, CasResource> mCasResources = new HashMap<>();
@GuardedBy("mLock")
private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
@@ -158,10 +160,8 @@ public class TunerResourceManagerService extends SystemService {
@Override
public void updateCasInfo(int casSystemId, int maxSessionNum) {
enforceTrmAccessPermission("updateCasInfo");
- if (DEBUG) {
- Slog.d(TAG,
- "updateCasInfo(casSystemId=" + casSystemId
- + ", maxSessionNum=" + maxSessionNum + ")");
+ synchronized (mLock) {
+ updateCasInfoInternal(casSystemId, maxSessionNum);
}
}
@@ -185,11 +185,11 @@ public class TunerResourceManagerService extends SystemService {
throw new RemoteException("frontendHandle can't be null");
}
synchronized (mLock) {
- try {
- return requestFrontendInternal(request, frontendHandle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (!checkClientExists(request.getClientId())) {
+ throw new RemoteException("Request frontend from unregistered client:"
+ + request.getClientId());
}
+ return requestFrontendInternal(request, frontendHandle);
}
}
@@ -211,32 +211,45 @@ public class TunerResourceManagerService extends SystemService {
throw new RemoteException("demuxHandle can't be null");
}
synchronized (mLock) {
+ if (!checkClientExists(request.getClientId())) {
+ throw new RemoteException("Request demux from unregistered client:"
+ + request.getClientId());
+ }
return requestDemuxInternal(request, demuxHandle);
}
}
@Override
public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
- @NonNull int[] descrambleHandle) throws RemoteException {
+ @NonNull int[] descramblerHandle) throws RemoteException {
enforceDescramblerAccessPermission("requestDescrambler");
enforceTrmAccessPermission("requestDescrambler");
- if (descrambleHandle == null) {
- throw new RemoteException("descrambleHandle can't be null");
+ if (descramblerHandle == null) {
+ throw new RemoteException("descramblerHandle can't be null");
}
synchronized (mLock) {
- return requestDescramblerInternal(request, descrambleHandle);
+ if (!checkClientExists(request.getClientId())) {
+ throw new RemoteException("Request descrambler from unregistered client:"
+ + request.getClientId());
+ }
+ return requestDescramblerInternal(request, descramblerHandle);
}
}
@Override
- public boolean requestCasSession(
- @NonNull CasSessionRequest request, @NonNull int[] sessionResourceHandle) {
+ public boolean requestCasSession(@NonNull CasSessionRequest request,
+ @NonNull int[] casSessionHandle) throws RemoteException {
enforceTrmAccessPermission("requestCasSession");
- if (DEBUG) {
- Slog.d(TAG, "requestCasSession(request=" + request + ")");
+ if (casSessionHandle == null) {
+ throw new RemoteException("casSessionHandle can't be null");
+ }
+ synchronized (mLock) {
+ if (!checkClientExists(request.getClientId())) {
+ throw new RemoteException("Request cas from unregistered client:"
+ + request.getClientId());
+ }
+ return requestCasSessionInternal(request, casSessionHandle);
}
-
- return true;
}
@Override
@@ -248,11 +261,11 @@ public class TunerResourceManagerService extends SystemService {
throw new RemoteException("lnbHandle can't be null");
}
synchronized (mLock) {
- try {
- return requestLnbInternal(request, lnbHandle);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ if (!checkClientExists(request.getClientId())) {
+ throw new RemoteException("Request lnb from unregistered client:"
+ + request.getClientId());
}
+ return requestLnbInternal(request, lnbHandle);
}
}
@@ -264,15 +277,20 @@ public class TunerResourceManagerService extends SystemService {
frontendHandle)) {
throw new RemoteException("frontendHandle can't be invalid");
}
- int frontendId = getResourceIdFromHandle(frontendHandle);
- FrontendResource fe = getFrontendResource(frontendId);
- if (fe == null) {
- throw new RemoteException("Releasing frontend does not exist.");
- }
- if (fe.getOwnerClientId() != clientId) {
- throw new RemoteException("Client is not the current owner of the releasing fe.");
- }
synchronized (mLock) {
+ if (!checkClientExists(clientId)) {
+ throw new RemoteException("Release frontend from unregistered client:"
+ + clientId);
+ }
+ int frontendId = getResourceIdFromHandle(frontendHandle);
+ FrontendResource fe = getFrontendResource(frontendId);
+ if (fe == null) {
+ throw new RemoteException("Releasing frontend does not exist.");
+ }
+ if (fe.getOwnerClientId() != clientId) {
+ throw new RemoteException(
+ "Client is not the current owner of the releasing fe.");
+ }
releaseFrontendInternal(fe);
}
}
@@ -296,10 +314,26 @@ public class TunerResourceManagerService extends SystemService {
}
@Override
- public void releaseCasSession(int sessionResourceId, int clientId) {
+ public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
enforceTrmAccessPermission("releaseCasSession");
- if (DEBUG) {
- Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
+ if (!validateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
+ throw new RemoteException("casSessionHandle can't be invalid");
+ }
+ synchronized (mLock) {
+ if (!checkClientExists(clientId)) {
+ throw new RemoteException("Release cas from unregistered client:" + clientId);
+ }
+ int casSystemId = getClientProfile(clientId).getInUseCasSystemId();
+ CasResource cas = getCasResource(casSystemId);
+ if (cas == null) {
+ throw new RemoteException("Releasing cas does not exist.");
+ }
+ if (!cas.getOwnerClientIds().contains(clientId)) {
+ throw new RemoteException(
+ "Client is not the current owner of the releasing cas.");
+ }
+ releaseCasSessionInternal(cas, clientId);
}
}
@@ -310,6 +344,9 @@ public class TunerResourceManagerService extends SystemService {
if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
throw new RemoteException("lnbHandle can't be invalid");
}
+ if (!checkClientExists(clientId)) {
+ throw new RemoteException("Release lnb from unregistered client:" + clientId);
+ }
int lnbId = getResourceIdFromHandle(lnbHandle);
LnbResource lnb = getLnbResource(lnbId);
if (lnb == null) {
@@ -465,17 +502,42 @@ public class TunerResourceManagerService extends SystemService {
}
@VisibleForTesting
- protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)
- throws RemoteException {
+ protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "updateCasInfo(casSystemId=" + casSystemId
+ + ", maxSessionNum=" + maxSessionNum + ")");
+ }
+ // If maxSessionNum is 0, removing the Cas Resource.
+ if (maxSessionNum == 0) {
+ removeCasResource(casSystemId);
+ return;
+ }
+ // If the Cas exists, updates the Cas Resource accordingly.
+ CasResource cas = getCasResource(casSystemId);
+ if (cas != null) {
+ if (cas.getUsedSessionNum() > maxSessionNum) {
+ // Sort and release the short number of Cas resources.
+ int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum;
+ releaseLowerPriorityClientCasResources(releasingCasResourceNum);
+ }
+ cas.updateMaxSessionNum(maxSessionNum);
+ return;
+ }
+ // Add the new Cas Resource.
+ cas = new CasResource.Builder(casSystemId)
+ .maxSessionNum(maxSessionNum)
+ .build();
+ addCasResource(cas);
+ }
+
+ @VisibleForTesting
+ protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
if (DEBUG) {
Slog.d(TAG, "requestFrontend(request=" + request + ")");
}
frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- if (!checkClientExists(request.getClientId())) {
- Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId());
- return false;
- }
ClientProfile requestClient = getClientProfile(request.getClientId());
int grantingFrontendId = -1;
int inUseLowestPriorityFrId = -1;
@@ -496,7 +558,7 @@ public class TunerResourceManagerService extends SystemService {
} else if (grantingFrontendId < 0) {
// Record the frontend id with the lowest client priority among all the
// in use frontends when no available frontend has been found.
- int priority = getOwnerClientPriority(fr);
+ int priority = getOwnerClientPriority(fr.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityFrId = fr.getId();
currentLowestPriority = priority;
@@ -530,17 +592,12 @@ public class TunerResourceManagerService extends SystemService {
}
@VisibleForTesting
- protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
- throws RemoteException {
+ protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
if (DEBUG) {
Slog.d(TAG, "requestLnb(request=" + request + ")");
}
lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
- if (!checkClientExists(request.getClientId())) {
- Slog.e(TAG, "Request lnb from unregistered client:" + request.getClientId());
- return false;
- }
ClientProfile requestClient = getClientProfile(request.getClientId());
int grantingLnbId = -1;
int inUseLowestPriorityLnbId = -1;
@@ -554,7 +611,7 @@ public class TunerResourceManagerService extends SystemService {
} else {
// Record the lnb id with the lowest client priority among all the
// in use lnb when no available lnb has been found.
- int priority = getOwnerClientPriority(lnb);
+ int priority = getOwnerClientPriority(lnb.getOwnerClientId());
if (currentLowestPriority > priority) {
inUseLowestPriorityLnbId = lnb.getId();
currentLowestPriority = priority;
@@ -588,7 +645,55 @@ public class TunerResourceManagerService extends SystemService {
}
@VisibleForTesting
- void releaseFrontendInternal(FrontendResource fe) {
+ protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
+ if (DEBUG) {
+ Slog.d(TAG, "requestCasSession(request=" + request + ")");
+ }
+ CasResource cas = getCasResource(request.getCasSystemId());
+ // Unregistered Cas System is treated as having unlimited sessions.
+ if (cas == null) {
+ cas = new CasResource.Builder(request.getCasSystemId())
+ .maxSessionNum(Integer.MAX_VALUE)
+ .build();
+ addCasResource(cas);
+ }
+ casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+ ClientProfile requestClient = getClientProfile(request.getClientId());
+ int lowestPriorityOwnerId = -1;
+ // Priority max value is 1000
+ int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+ if (!cas.isFullyUsed()) {
+ casSessionHandle[0] = generateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
+ updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId());
+ return true;
+ }
+ for (int ownerId : cas.getOwnerClientIds()) {
+ // Record the client id with lowest priority that is using the current Cas system.
+ int priority = getOwnerClientPriority(ownerId);
+ if (currentLowestPriority > priority) {
+ lowestPriorityOwnerId = ownerId;
+ currentLowestPriority = priority;
+ }
+ }
+
+ // When all the Cas sessions are occupied, reclaim the lowest priority client if the
+ // request client has higher priority.
+ if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+ if (!reclaimResource(lowestPriorityOwnerId,
+ TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
+ return false;
+ }
+ casSessionHandle[0] = generateResourceHandle(
+ TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
+ updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId());
+ return true;
+ }
+ return false;
+ }
+
+ @VisibleForTesting
+ protected void releaseFrontendInternal(FrontendResource fe) {
if (DEBUG) {
Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")");
}
@@ -596,7 +701,7 @@ public class TunerResourceManagerService extends SystemService {
}
@VisibleForTesting
- void releaseLnbInternal(LnbResource lnb) {
+ protected void releaseLnbInternal(LnbResource lnb) {
if (DEBUG) {
Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")");
}
@@ -604,7 +709,15 @@ public class TunerResourceManagerService extends SystemService {
}
@VisibleForTesting
- boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+ protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) {
+ if (DEBUG) {
+ Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")");
+ }
+ updateCasClientMappingOnRelease(cas, ownerClientId);
+ }
+
+ @VisibleForTesting
+ protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
if (DEBUG) {
Slog.d(TAG, "requestDemux(request=" + request + ")");
}
@@ -614,7 +727,8 @@ public class TunerResourceManagerService extends SystemService {
}
@VisibleForTesting
- boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) {
+ protected boolean requestDescramblerInternal(
+ TunerDescramblerRequest request, int[] descramblerHandle) {
if (DEBUG) {
Slog.d(TAG, "requestDescrambler(request=" + request + ")");
}
@@ -742,14 +856,28 @@ public class TunerResourceManagerService extends SystemService {
ownerProfile.releaseLnb(releasingLnb.getId());
}
+ private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) {
+ CasResource grantingCas = getCasResource(grantingId);
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ grantingCas.setOwner(ownerClientId);
+ ownerProfile.useCas(grantingId);
+ }
+
+ private void updateCasClientMappingOnRelease(
+ @NonNull CasResource releasingCas, int ownerClientId) {
+ ClientProfile ownerProfile = getClientProfile(ownerClientId);
+ releasingCas.removeOwner(ownerClientId);
+ ownerProfile.releaseCas();
+ }
+
/**
* Get the owner client's priority from the resource id.
*
- * @param resource a in use tuner resource.
+ * @param clientId the owner client id.
* @return the priority of the owner client of the resource.
*/
- private int getOwnerClientPriority(TunerResourceBasic resource) {
- return getClientProfile(resource.getOwnerClientId()).getPriority();
+ private int getOwnerClientPriority(int clientId) {
+ return getClientProfile(clientId).getPriority();
}
@VisibleForTesting
@@ -783,6 +911,9 @@ public class TunerResourceManagerService extends SystemService {
private void removeFrontendResource(int removingId) {
FrontendResource fe = getFrontendResource(removingId);
+ if (fe == null) {
+ return;
+ }
if (fe.isInUse()) {
releaseFrontendInternal(fe);
}
@@ -811,6 +942,9 @@ public class TunerResourceManagerService extends SystemService {
private void removeLnbResource(int removingId) {
LnbResource lnb = getLnbResource(removingId);
+ if (lnb == null) {
+ return;
+ }
if (lnb.isInUse()) {
releaseLnbInternal(lnb);
}
@@ -819,6 +953,39 @@ public class TunerResourceManagerService extends SystemService {
@VisibleForTesting
@Nullable
+ protected CasResource getCasResource(int systemId) {
+ return mCasResources.get(systemId);
+ }
+
+ @VisibleForTesting
+ protected Map<Integer, CasResource> getCasResources() {
+ return mCasResources;
+ }
+
+ private void addCasResource(CasResource newCas) {
+ // Update resource list and available id list
+ mCasResources.put(newCas.getSystemId(), newCas);
+ }
+
+ private void removeCasResource(int removingId) {
+ CasResource cas = getCasResource(removingId);
+ if (cas == null) {
+ return;
+ }
+ for (int ownerId : cas.getOwnerClientIds()) {
+ getClientProfile(ownerId).releaseCas();
+ }
+ mCasResources.remove(removingId);
+ }
+
+ private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) {
+ // TODO: Sort with a treemap
+
+ // select the first num client to release
+ }
+
+ @VisibleForTesting
+ @Nullable
protected ClientProfile getClientProfile(int clientId) {
return mClientProfiles.get(clientId);
}
@@ -830,12 +997,7 @@ public class TunerResourceManagerService extends SystemService {
}
private void removeClientProfile(int clientId) {
- for (int id : getClientProfile(clientId).getInUseFrontendIds()) {
- getFrontendResource(id).removeOwner();
- for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) {
- getFrontendResource(groupMemberId).removeOwner();
- }
- }
+ reclaimingResourcesFromClient(getClientProfile(clientId));
mClientProfiles.remove(clientId);
mListeners.remove(clientId);
}
@@ -847,6 +1009,9 @@ public class TunerResourceManagerService extends SystemService {
for (Integer lnbId : profile.getInUseLnbIds()) {
getLnbResource(lnbId).removeOwner();
}
+ if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
+ getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
+ }
profile.reclaimAllResources();
}
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 3f9f95cf8370..abccf99579b7 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1245,6 +1245,16 @@ final class AccessibilityController {
}
}
+ for (int i = dc.mShellRoots.size() - 1; i >= 0; --i) {
+ final WindowInfo info = dc.mShellRoots.valueAt(i).getWindowInfo();
+ if (info == null) {
+ continue;
+ }
+ info.layer = addedWindows.size();
+ windows.add(info);
+ addedWindows.add(info.token);
+ }
+
// Remove child/parent references to windows that were not added.
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e76eda06d2d3..2648c86d3c6a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4712,7 +4712,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private boolean shouldBeResumed(ActivityRecord activeActivity) {
return shouldMakeActive(activeActivity) && isFocusable()
- && getRootTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
+ && getTask().getVisibility(activeActivity) == STACK_VISIBILITY_VISIBLE
&& canResumeByCompat();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 0f574963eb34..ff43e77a5c47 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -18,11 +18,8 @@ package com.android.server.wm;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.PINNED_WINDOWING_MODE_ELEVATION_IN_DIP;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
@@ -142,13 +139,11 @@ import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayInfo;
-import android.view.SurfaceControl;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -157,7 +152,6 @@ import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
import com.android.server.am.ActivityManagerService.ItemMatcher;
@@ -276,11 +270,6 @@ class ActivityStack extends Task {
Rect mPreAnimationBounds = new Rect();
- /**
- * For {@link #prepareSurfaces}.
- */
- private final Point mLastSurfaceSize = new Point();
-
private final AnimatingActivityRegistry mAnimatingActivityRegistry =
new AnimatingActivityRegistry();
@@ -606,10 +595,6 @@ class ActivityStack extends Task {
super.onConfigurationChanged(newParentConfig);
- // Only need to update surface size here since the super method will handle updating
- // surface position.
- updateSurfaceSize(getPendingTransaction());
-
final TaskDisplayArea taskDisplayArea = getDisplayArea();
if (taskDisplayArea == null) {
return;
@@ -3262,61 +3247,14 @@ class ActivityStack extends Task {
scheduleAnimation();
}
- /**
- * Calculate an amount by which to expand the stack bounds in each direction.
- * Used to make room for shadows in the pinned windowing mode.
- */
- int getStackOutset() {
- // If we are drawing shadows on the task then don't outset the stack.
- if (mWmService.mRenderShadowsInCompositor) {
- return 0;
- }
- DisplayContent displayContent = getDisplayContent();
- if (inPinnedWindowingMode() && displayContent != null) {
- final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
-
- // We multiply by two to match the client logic for converting view elevation
- // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
- return (int) Math.ceil(
- mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
- * 2);
- }
- return 0;
- }
-
@Override
void getRelativePosition(Point outPos) {
super.getRelativePosition(outPos);
- final int outset = getStackOutset();
+ final int outset = getTaskOutset();
outPos.x -= outset;
outPos.y -= outset;
}
- private void updateSurfaceSize(SurfaceControl.Transaction transaction) {
- if (mSurfaceControl == null) {
- return;
- }
-
- final Rect stackBounds = getBounds();
- int width = stackBounds.width();
- int height = stackBounds.height();
-
- final int outset = getStackOutset();
- width += 2 * outset;
- height += 2 * outset;
-
- if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
- return;
- }
- transaction.setWindowCrop(mSurfaceControl, width, height);
- mLastSurfaceSize.set(width, height);
- }
-
- @VisibleForTesting
- Point getLastSurfaceSize() {
- return mLastSurfaceSize;
- }
-
@Override
void onDisplayChanged(DisplayContent dc) {
super.onDisplayChanged(dc);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 736bc9f8bc22..93a757449564 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5400,14 +5400,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
&& config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
&& config.navigation == Configuration.NAVIGATION_NONAV);
- int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
- final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
- && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
- && modeType != Configuration.UI_MODE_TYPE_TELEVISION
- && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
HIDE_ERROR_DIALOGS, 0) != 0;
- mShowDialogs = inputMethodExists && uiModeSupportsDialogs && !hideDialogsSet;
+ mShowDialogs = inputMethodExists
+ && ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext)
+ && !hideDialogsSet;
}
private void updateFontScaleIfNeeded(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a47cdc66fbd8..e26163247020 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -26,7 +26,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -574,7 +573,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
/** Corner radius that windows should have in order to match the display. */
private final float mWindowCornerRadius;
- private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+ final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
RemoteInsetsControlTarget mRemoteInsetsControlTarget = null;
private final IBinder.DeathRecipient mRemoteInsetsDeath =
() -> {
@@ -5450,6 +5449,46 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mWmService.mDisplayManagerInternal.getDisplayPosition(getDisplayId());
}
+ /**
+ * Locates the appropriate target window for scroll capture. The search progresses top to
+ * bottom.
+ * If {@code searchBehind} is non-null, the search will only consider windows behind this one.
+ * If a valid taskId is specified, the target window must belong to the given task.
+ *
+ * @param searchBehind a window used to filter the search to windows behind it, or null to begin
+ * the search at the top window of the display
+ * @param taskId specifies the id of a task the result must belong to or
+ * {@link android.app.ActivityTaskManager#INVALID_TASK_ID INVALID_TASK_ID}
+ * to match any window
+ * @return the located window or null if none could be found matching criteria
+ */
+ @Nullable
+ WindowState findScrollCaptureTargetWindow(@Nullable WindowState searchBehind, int taskId) {
+ return getWindow(new Predicate<WindowState>() {
+ boolean behindTopWindow = (searchBehind == null); // optional filter
+ @Override
+ public boolean test(WindowState nextWindow) {
+ // Skip through all windows until we pass topWindow (if specified)
+ if (!behindTopWindow) {
+ if (nextWindow == searchBehind) {
+ behindTopWindow = true;
+ }
+ return false; /* continue */
+ }
+ if (taskId != INVALID_TASK_ID) {
+ Task task = nextWindow.getTask();
+ if (task == null || !task.isTaskId(taskId)) {
+ return false; /* continue */
+ }
+ }
+ if (!nextWindow.canReceiveKeys()) {
+ return false; /* continue */
+ }
+ return true; /* stop */
+ }
+ });
+ }
+
class RemoteInsetsControlTarget implements InsetsControlTarget {
private final IDisplayWindowInsetsController mRemoteInsetsController;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e9d3d56ee283..8aace212d094 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -261,6 +261,8 @@ public class DisplayPolicy {
@Px
private int mRightGestureInset;
+ private boolean mNavButtonForcedVisible;
+
StatusBarManagerInternal getStatusBarManagerInternal() {
synchronized (mServiceAcquireLock) {
if (mStatusBarManagerInternal == null) {
@@ -1046,12 +1048,14 @@ public class DisplayPolicy {
// calculate inset.
if (navigationBarPosition(displayFrames.mDisplayWidth,
displayFrames.mDisplayHeight,
- displayFrames.mRotation) == NAV_BAR_BOTTOM) {
+ displayFrames.mRotation) == NAV_BAR_BOTTOM
+ && !mNavButtonForcedVisible) {
+
sTmpRect.set(displayFrames.mUnrestricted);
sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
inOutFrame.top = sTmpRect.bottom
- getNavigationBarHeight(displayFrames.mRotation,
- mDisplayContent.getConfiguration().uiMode);
+ mDisplayContent.getConfiguration().uiMode);
}
},
@@ -2810,6 +2814,8 @@ public class DisplayPolicy {
mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+ mNavButtonForcedVisible =
+ mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
mNavigationBarAlwaysShowOnSideGesture =
res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
@@ -3783,13 +3789,14 @@ public class DisplayPolicy {
* @param screenshotType The type of screenshot, for example either
* {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
* {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+ * @param source Where the screenshot originated from (see WindowManager.ScreenshotSource)
*/
- public void takeScreenshot(int screenshotType) {
+ public void takeScreenshot(int screenshotType, int source) {
if (mScreenshotHelper != null) {
mScreenshotHelper.takeScreenshot(screenshotType,
mStatusBar != null && mStatusBar.isVisibleLw(),
mNavigationBar != null && mNavigationBar.isVisibleLw(),
- mHandler, null /* completionConsumer */);
+ source, mHandler, null /* completionConsumer */);
}
}
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 20738ed29470..803bec8941a8 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -45,6 +45,11 @@ public class DockedStackDividerController {
void setTouchRegion(Rect touchRegion) {
mTouchRegion.set(touchRegion);
+ // We need to report touchable region changes to accessibility.
+ if (mDisplayContent.mWmService.mAccessibilityController != null) {
+ mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMovedLocked(
+ mDisplayContent.getDisplayId());
+ }
}
void getTouchRegion(Rect outRegion) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 8b34b9b8dd8f..656dca531a22 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -487,7 +487,8 @@ final class InputMonitor {
|| w.cantReceiveTouchInput()) {
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
- w.mWinAnimator.mSurfaceController.mSurfaceControl, mInvalidInputWindow);
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ mInvalidInputWindow);
}
// Skip this window because it cannot possibly receive input.
return;
@@ -560,7 +561,8 @@ final class InputMonitor {
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
- w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
+ w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
+ inputWindowHandle);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 5f33ea170923..86e081854597 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -212,7 +212,7 @@ class ScreenRotationAnimation {
final Surface surface = mService.mSurfaceFactory.get();
surface.copyFrom(mScreenshotLayer);
SurfaceControl.ScreenshotGraphicBuffer gb =
- mService.mDisplayManagerInternal.screenshot(displayId);
+ mService.mDisplayManagerInternal.systemScreenshot(displayId);
if (gb != null) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"ScreenRotationAnimation#getMedianBorderLuma");
@@ -597,8 +597,8 @@ class ScreenRotationAnimation {
return startAnimation(initializeBuilder()
.setSurfaceControl(mScreenshotLayer)
.setAnimationLeashParent(mDisplayContent.getOverlayLayer())
- .setWidth(mWidth)
- .setHeight(mHeight)
+ .setWidth(mDisplayContent.getSurfaceWidth())
+ .setHeight(mDisplayContent.getSurfaceHeight())
.build(),
createWindowAnimationSpec(mRotateAlphaAnimation),
this::onAnimationEnd);
diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java
index 701feff8c6be..0b1760dc5a1c 100644
--- a/services/core/java/com/android/server/wm/ShellRoot.java
+++ b/services/core/java/com/android/server/wm/ShellRoot.java
@@ -23,12 +23,14 @@ import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
import android.annotation.NonNull;
import android.graphics.Point;
+import android.graphics.Rect;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Slog;
import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.SurfaceControl;
+import android.view.WindowInfo;
import android.view.animation.Animation;
/**
@@ -102,5 +104,27 @@ public class ShellRoot {
mToken.startAnimation(mToken.getPendingTransaction(), adapter, false /* hidden */,
ANIMATION_TYPE_WINDOW_ANIMATION, null /* animationFinishedCallback */);
}
+
+ WindowInfo getWindowInfo() {
+ if (mToken.windowType != TYPE_DOCK_DIVIDER) {
+ return null;
+ }
+ if (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated()) {
+ return null;
+ }
+ WindowInfo windowInfo = WindowInfo.obtain();
+ windowInfo.displayId = mToken.getDisplayArea().getDisplayContent().mDisplayId;
+ windowInfo.type = mToken.windowType;
+ windowInfo.layer = mToken.getWindowLayerFromType();
+ windowInfo.token = mClient.asBinder();
+ windowInfo.title = "Splitscreen Divider";
+ windowInfo.focused = false;
+ windowInfo.inPictureInPicture = false;
+ windowInfo.hasFlagWatchOutsideTouch = false;
+ final Rect regionRect = new Rect();
+ mDisplayContent.getDockedDividerController().getTouchRegion(regionRect);
+ windowInfo.regionInScreen.set(regionRect);
+ return windowInfo;
+ }
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 66ca0ac85143..66e1b1758d85 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -120,6 +120,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
@@ -213,7 +214,6 @@ class Task extends WindowContainer<WindowContainer> {
static final int INVALID_MIN_SIZE = -1;
private float mShadowRadius = 0;
- private final Rect mLastSurfaceCrop = new Rect();
/**
* The modes to control how the stack is moved to the front when calling {@link Task#reparent}.
@@ -397,6 +397,7 @@ class Task extends WindowContainer<WindowContainer> {
private Dimmer mDimmer = new Dimmer(this);
private final Rect mTmpDimBoundsRect = new Rect();
+ private final Point mLastSurfaceSize = new Point();
/** @see #setCanAffectSystemUiFlags */
private boolean mCanAffectSystemUiFlags = true;
@@ -1943,6 +1944,10 @@ class Task extends WindowContainer<WindowContainer> {
mTmpPrevBounds.set(getBounds());
final boolean wasInMultiWindowMode = inMultiWindowMode();
super.onConfigurationChanged(newParentConfig);
+ // Only need to update surface size here since the super method will handle updating
+ // surface position.
+ updateSurfaceSize(getPendingTransaction());
+
if (wasInMultiWindowMode != inMultiWindowMode()) {
mStackSupervisor.scheduleUpdateMultiWindowMode(this);
}
@@ -1995,6 +2000,57 @@ class Task extends WindowContainer<WindowContainer> {
return (prevWinMode == WINDOWING_MODE_FREEFORM) != (newWinMode == WINDOWING_MODE_FREEFORM);
}
+ void updateSurfaceSize(SurfaceControl.Transaction transaction) {
+ if (mSurfaceControl == null || mCreatedByOrganizer) {
+ return;
+ }
+
+ // Apply crop to root tasks only and clear the crops of the descendant tasks.
+ int width = 0;
+ int height = 0;
+ if (isRootTask()) {
+ final Rect taskBounds = getBounds();
+ width = taskBounds.width();
+ height = taskBounds.height();
+
+ final int outset = getTaskOutset();
+ width += 2 * outset;
+ height += 2 * outset;
+ }
+ if (width == mLastSurfaceSize.x && height == mLastSurfaceSize.y) {
+ return;
+ }
+ transaction.setWindowCrop(mSurfaceControl, width, height);
+ mLastSurfaceSize.set(width, height);
+ }
+
+ /**
+ * Calculate an amount by which to expand the task bounds in each direction.
+ * Used to make room for shadows in the pinned windowing mode.
+ */
+ int getTaskOutset() {
+ // If we are drawing shadows on the task then don't outset the stack.
+ if (mWmService.mRenderShadowsInCompositor) {
+ return 0;
+ }
+ DisplayContent displayContent = getDisplayContent();
+ if (inPinnedWindowingMode() && displayContent != null) {
+ final DisplayMetrics displayMetrics = displayContent.getDisplayMetrics();
+
+ // We multiply by two to match the client logic for converting view elevation
+ // to insets, as in {@link WindowManager.LayoutParams#setSurfaceInsets}
+ return (int) Math.ceil(
+ mWmService.dipToPixel(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP, displayMetrics)
+ * 2);
+ }
+ return 0;
+ }
+
+ @VisibleForTesting
+ Point getLastSurfaceSize() {
+ return mLastSurfaceSize;
+ }
+
@VisibleForTesting
boolean isInChangeTransition() {
return mSurfaceFreezer.hasLeash() || AppTransition.isChangeTransit(mTransit);
@@ -2225,14 +2281,16 @@ class Task extends WindowContainer<WindowContainer> {
}
density *= DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ // If bounds have been overridden at this level, restrict config resources to these bounds
+ // rather than the parent because the overridden bounds can be larger than the parent.
+ boolean hasOverrideBounds = false;
+
final Rect resolvedBounds = inOutConfig.windowConfiguration.getBounds();
- if (resolvedBounds == null) {
- mTmpFullBounds.setEmpty();
+ if (resolvedBounds == null || resolvedBounds.isEmpty()) {
+ mTmpFullBounds.set(parentConfig.windowConfiguration.getBounds());
} else {
mTmpFullBounds.set(resolvedBounds);
- }
- if (mTmpFullBounds.isEmpty()) {
- mTmpFullBounds.set(parentConfig.windowConfiguration.getBounds());
+ hasOverrideBounds = true;
}
Rect outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
@@ -2244,7 +2302,16 @@ class Task extends WindowContainer<WindowContainer> {
// the out bounds doesn't need to be restricted by the parent.
final boolean insideParentBounds = compatInsets == null;
if (insideParentBounds && windowingMode != WINDOWING_MODE_FREEFORM) {
- final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ Rect parentAppBounds;
+ if (hasOverrideBounds) {
+ // Since we overrode the bounds, restrict appBounds to display non-decor rather
+ // than parent. Otherwise, it won't match the overridden bounds.
+ final TaskDisplayArea displayArea = getDisplayArea();
+ parentAppBounds = displayArea != null
+ ? displayArea.getConfiguration().windowConfiguration.getAppBounds() : null;
+ } else {
+ parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
+ }
if (parentAppBounds != null && !parentAppBounds.isEmpty()) {
outAppBounds.intersect(parentAppBounds);
}
@@ -2291,13 +2358,13 @@ class Task extends WindowContainer<WindowContainer> {
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
- inOutConfig.screenWidthDp = insideParentBounds
+ inOutConfig.screenWidthDp = (insideParentBounds && !hasOverrideBounds)
? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
: overrideScreenWidthDp;
}
if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
- inOutConfig.screenHeightDp = insideParentBounds
+ inOutConfig.screenHeightDp = (insideParentBounds && !hasOverrideBounds)
? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
: overrideScreenHeightDp;
}
@@ -2344,27 +2411,27 @@ class Task extends WindowContainer<WindowContainer> {
mTmpBounds.set(getResolvedOverrideConfiguration().windowConfiguration.getBounds());
super.resolveOverrideConfiguration(newParentConfig);
- // Resolve override windowing mode to fullscreen for home task (even on freeform
- // display), or split-screen-secondary if in split-screen mode.
int windowingMode =
getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
+
+ // Resolve override windowing mode to fullscreen for home task (even on freeform
+ // display), or split-screen if in split-screen mode.
if (getActivityType() == ACTIVITY_TYPE_HOME && windowingMode == WINDOWING_MODE_UNDEFINED) {
final int parentWindowingMode = newParentConfig.windowConfiguration.getWindowingMode();
- windowingMode = parentWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
- : WINDOWING_MODE_FULLSCREEN;
+ windowingMode = WindowConfiguration.isSplitScreenWindowingMode(parentWindowingMode)
+ ? parentWindowingMode : WINDOWING_MODE_FULLSCREEN;
getResolvedOverrideConfiguration().windowConfiguration.setWindowingMode(windowingMode);
}
- if (!isLeafTask()) {
- // Compute configuration overrides for tasks that created by organizer, so that
- // organizer can get the correct configuration from those tasks.
- if (mCreatedByOrganizer) {
- computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
- }
- return;
+ if (isLeafTask()) {
+ resolveLeafOnlyOverrideConfigs(newParentConfig);
}
+ computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
+ }
+ void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig) {
+ int windowingMode =
+ getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode();
if (windowingMode == WINDOWING_MODE_UNDEFINED) {
windowingMode = newParentConfig.windowConfiguration.getWindowingMode();
}
@@ -2404,7 +2471,6 @@ class Task extends WindowContainer<WindowContainer> {
outOverrideBounds.offset(0, offsetTop);
}
}
- computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig);
}
/**
@@ -2803,28 +2869,6 @@ class Task extends WindowContainer<WindowContainer> {
return boundsChange;
}
- private void updateSurfaceCrop() {
- // Only update the crop if we are drawing shadows on the task.
- if (mSurfaceControl == null || !mWmService.mRenderShadowsInCompositor || !isRootTask()) {
- return;
- }
-
- if (inSplitScreenWindowingMode()) {
- // inherit crop from parent
- mTmpRect.setEmpty();
- } else {
- getBounds(mTmpRect);
- }
-
- mTmpRect.offsetTo(0, 0);
- if (mLastSurfaceCrop.equals(mTmpRect)) {
- return;
- }
-
- getPendingTransaction().setWindowCrop(mSurfaceControl, mTmpRect);
- mLastSurfaceCrop.set(mTmpRect);
- }
-
@Override
public boolean onDescendantOrientationChanged(IBinder freezeDisplayToken,
ConfigurationContainer requestingContainer) {
@@ -3453,7 +3497,6 @@ class Task extends WindowContainer<WindowContainer> {
mTmpDimBoundsRect.offsetTo(0, 0);
}
- updateSurfaceCrop();
updateShadowsRadius(isFocused(), getPendingTransaction());
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
@@ -4392,19 +4435,20 @@ class Task extends WindowContainer<WindowContainer> {
// Let the old organizer know it has lost control.
sendTaskVanished();
mTaskOrganizer = organizer;
- sendTaskAppeared();
- onTaskOrganizerChanged();
- return true;
- }
- void taskOrganizerUnregistered() {
- mTaskOrganizer = null;
- mTaskAppearedSent = false;
- mLastTaskOrganizerWindowingMode = -1;
- onTaskOrganizerChanged();
- if (mCreatedByOrganizer) {
- removeImmediately();
+ if (mTaskOrganizer != null) {
+ sendTaskAppeared();
+ } else {
+ // No longer managed by any organizer.
+ mTaskAppearedSent = false;
+ mLastTaskOrganizerWindowingMode = -1;
+ setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
+ if (mCreatedByOrganizer) {
+ removeImmediately();
+ }
}
+
+ return true;
}
/**
@@ -4441,14 +4485,6 @@ class Task extends WindowContainer<WindowContainer> {
return result;
}
- private void onTaskOrganizerChanged() {
- if (mTaskOrganizer == null) {
- // If this task is no longer controlled by a task organizer, then reset the force hidden
- // state
- setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
- }
- }
-
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index adc50bf70446..306c100e651c 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -218,18 +218,24 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
void dispose() {
- releaseTasks();
+ // Move organizer from managing specific windowing modes
for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) {
mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder());
}
- }
- private void releaseTasks() {
- for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) {
- final Task t = mOrganizedTasks.get(i);
- removeTask(t);
- t.taskOrganizerUnregistered();
+ // Update tasks currently managed by this organizer to the next one available if
+ // possible.
+ while (!mOrganizedTasks.isEmpty()) {
+ final Task t = mOrganizedTasks.get(0);
+ t.updateTaskOrganizerState(true /* forceUpdate */);
+ if (mOrganizedTasks.contains(t)) {
+ removeTask(t);
+ }
}
+
+ // Remove organizer state after removing tasks so we get a chance to send
+ // onTaskVanished.
+ mTaskOrganizerStates.remove(asBinder());
}
void unlinkDeath() {
@@ -313,16 +319,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
new TaskOrganizerState(organizer, uid));
}
- if (orgs.size() == 1) {
- // Only in the case where this is the root task organizer for the given
- // windowing mode, we add report all existing tasks in that mode to the new
- // task organizer.
- mService.mRootWindowContainer.forAllTasks((task) -> {
- if (task.getWindowingMode() == windowingMode) {
- task.updateTaskOrganizerState(true /* forceUpdate */);
- }
- });
- }
+ mService.mRootWindowContainer.forAllTasks((task) -> {
+ if (task.getWindowingMode() == windowingMode) {
+ task.updateTaskOrganizerState(true /* forceUpdate */);
+ }
+ });
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -335,7 +336,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
- final TaskOrganizerState state = mTaskOrganizerStates.remove(organizer.asBinder());
+ final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state == null) {
return;
}
@@ -367,7 +368,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
void onTaskVanished(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
- state.removeTask(task);
+ if (state != null) {
+ state.removeTask(task);
+ }
}
@Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index f55a1b3f6ab3..51095ee85eea 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -219,6 +219,7 @@ import android.view.IOnKeyguardExitResult;
import android.view.IPinnedStackListener;
import android.view.IRecentsAnimationRunner;
import android.view.IRotationWatcher;
+import android.view.IScrollCaptureController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWallpaperVisibilityListener;
import android.view.IWindow;
@@ -2445,17 +2446,15 @@ public class WindowManagerService extends IWindowManager.Stub
if (controls != null) {
final int length = Math.min(controls.length, outControls.length);
for (int i = 0; i < length; i++) {
- final InsetsSourceControl control = controls[i];
-
- // Check if we are sending invalid leashes.
- final SurfaceControl leash = control != null ? control.getLeash() : null;
- if (leash != null && !leash.isValid()) {
- Slog.wtf(TAG, leash + " is not valid before sending to " + win,
- leash.getReleaseStack());
- }
-
- outControls[i] = win.isClientLocal() && control != null
- ? new InsetsSourceControl(control) : control;
+ // We will leave the critical section before returning the leash to the client,
+ // so we need to copy the leash to prevent others release the one that we are
+ // about to return.
+ // TODO: We will have an extra copy if the client is not local.
+ // For now, we rely on GC to release it.
+ // Maybe we can modify InsetsSourceControl.writeToParcel so it can release
+ // the extra leash as soon as possible.
+ outControls[i] = controls[i] != null
+ ? new InsetsSourceControl(controls[i]) : null;
}
}
}
@@ -6837,6 +6836,58 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ /**
+ * Forwards a scroll capture request to the appropriate window, if available.
+ *
+ * @param displayId the display for the request
+ * @param behindClient token for a window, used to filter the search to windows behind it
+ * @param taskId specifies the id of a task the result must belong to or -1 to ignore task ids
+ * @param controller the controller to receive results; a call to either
+ * {@link IScrollCaptureController#onClientConnected} or
+ * {@link IScrollCaptureController#onClientUnavailable}.
+ */
+ public void requestScrollCapture(int displayId, @Nullable IBinder behindClient, int taskId,
+ IScrollCaptureController controller) {
+ if (!checkCallingPermission(READ_FRAME_BUFFER, "requestScrollCapture()")) {
+ throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ ProtoLog.e(WM_ERROR,
+ "Invalid displayId for requestScrollCapture: %d", displayId);
+ controller.onClientUnavailable();
+ return;
+ }
+ WindowState topWindow = null;
+ if (behindClient != null) {
+ topWindow = windowForClientLocked(null, behindClient, /* throwOnError*/ true);
+ }
+ WindowState targetWindow = dc.findScrollCaptureTargetWindow(topWindow, taskId);
+ if (targetWindow == null) {
+ controller.onClientUnavailable();
+ return;
+ }
+ // Forward to the window for handling.
+ try {
+ targetWindow.mClient.requestScrollCapture(controller);
+ } catch (RemoteException e) {
+ ProtoLog.w(WM_ERROR,
+ "requestScrollCapture: caught exception dispatching to window."
+ + "token=%s", targetWindow.mClient.asBinder());
+ controller.onClientUnavailable();
+ }
+ }
+ } catch (RemoteException e) {
+ ProtoLog.w(WM_ERROR,
+ "requestScrollCapture: caught exception dispatching callback: %s", e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
@Override
public void dontOverrideDisplayInfo(int displayId) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 83e7ad57e68d..ef690e1db396 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -5289,7 +5289,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// to account for it. If we actually have shadows we will
// then un-inset ourselves by the surfaceInsets.
if (stack != null) {
- final int outset = stack.getStackOutset();
+ final int outset = stack.getTaskOutset();
outPoint.offset(outset, outset);
}
diff --git a/services/core/jni/com_android_server_tv_GamepadKeys.h b/services/core/jni/com_android_server_tv_GamepadKeys.h
index 11fc9031da3b..127010f907ff 100644
--- a/services/core/jni/com_android_server_tv_GamepadKeys.h
+++ b/services/core/jni/com_android_server_tv_GamepadKeys.h
@@ -1,77 +1,104 @@
#ifndef ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
#define ANDROIDTVREMOTE_SERVICE_JNI_GAMEPAD_KEYS_H_
+#include <android/input.h>
+#include <android/keycodes.h>
#include <linux/input.h>
namespace android {
-// Follows the W3 spec for gamepad buttons and their corresponding mapping into
-// Linux keycodes. Note that gamepads are generally not very well standardized
-// and various controllers will result in different buttons. This mapping tries
-// to be reasonable.
+// The constant array below defines a mapping between "Android" IDs (key code
+// within events) and what is being sent through /dev/uinput.
//
-// W3 Button spec: https://www.w3.org/TR/gamepad/#remapping
+// The translation back from uinput key codes into android key codes is done through
+// the corresponding key layout files. This file and
//
-// Standard gamepad keycodes are added plus 2 additional buttons (e.g. Stadia
-// has "Assistant" and "Share", PS4 has the touchpad button).
+// data/keyboards/Vendor_18d1_Product_0200.kl
//
-// To generate this list, PS4, XBox, Stadia and Nintendo Switch Pro were tested.
-static const int GAMEPAD_KEY_CODES[19] = {
- // Right-side buttons. A/B/X/Y or circle/triangle/square/X or similar
- BTN_A, // "South", A, GAMEPAD and SOUTH have the same constant
- BTN_B, // "East", BTN_B, BTN_EAST have the same constant
- BTN_X, // "West", Note that this maps to X and NORTH in constants
- BTN_Y, // "North", Note that this maps to Y and WEST in constants
+// MUST be kept in sync.
+//
+// see https://source.android.com/devices/input/key-layout-files for documentation.
- BTN_TL, // "Left Bumper" / "L1" - Nintendo sends BTN_WEST instead
- BTN_TR, // "Right Bumper" / "R1" - Nintendo sends BTN_Z instead
+// Defines axis mapping information between android and
+// uinput axis.
+struct GamepadKey {
+ int32_t androidKeyCode;
+ int linuxUinputKeyCode;
+};
+
+static const GamepadKey GAMEPAD_KEYS[] = {
+ // Right-side buttons. A/B/X/Y or circle/triangle/square/X or similar
+ {AKEYCODE_BUTTON_A, BTN_A},
+ {AKEYCODE_BUTTON_B, BTN_B},
+ {AKEYCODE_BUTTON_X, BTN_X},
+ {AKEYCODE_BUTTON_Y, BTN_Y},
- // For triggers, gamepads vary:
- // - Stadia sends analog values over ABS_GAS/ABS_BRAKE and sends
- // TriggerHappy3/4 as digital presses
- // - PS4 and Xbox send analog values as ABS_Z/ABS_RZ
- // - Nintendo Pro sends BTN_TL/BTN_TR (since bumpers behave differently)
- // As placeholders we chose the stadia trigger-happy values since TL/TR are
- // sent for bumper button presses
- BTN_TRIGGER_HAPPY4, // "Left Trigger" / "L2"
- BTN_TRIGGER_HAPPY3, // "Right Trigger" / "R2"
+ // Bumper buttons and digital triggers. Triggers generally have
+ // both analog versions (GAS and BRAKE output) and digital ones
+ {AKEYCODE_BUTTON_L1, BTN_TL2},
+ {AKEYCODE_BUTTON_L2, BTN_TL},
+ {AKEYCODE_BUTTON_R1, BTN_TR2},
+ {AKEYCODE_BUTTON_R2, BTN_TR},
- BTN_SELECT, // "Select/Back". Often "options" or similar
- BTN_START, // "Start/forward". Often "hamburger" icon
+ // general actions for controllers
+ {AKEYCODE_BUTTON_SELECT, BTN_SELECT}, // Options or "..."
+ {AKEYCODE_BUTTON_START, BTN_START}, // Menu/Hamburger menu
+ {AKEYCODE_BUTTON_MODE, BTN_MODE}, // "main" button
- BTN_THUMBL, // "Left Joystick Pressed"
- BTN_THUMBR, // "Right Joystick Pressed"
+ // Pressing on the joyticks themselves
+ {AKEYCODE_BUTTON_THUMBL, BTN_THUMBL},
+ {AKEYCODE_BUTTON_THUMBR, BTN_THUMBR},
- // For DPads, gamepads generally only send axis changes
- // on ABS_HAT0X and ABS_HAT0Y.
- KEY_UP, // "Digital Pad up"
- KEY_DOWN, // "Digital Pad down"
- KEY_LEFT, // "Digital Pad left"
- KEY_RIGHT, // "Digital Pad right"
+ // DPAD digital keys. HAT axis events are generally also sent.
+ {AKEYCODE_DPAD_UP, KEY_UP},
+ {AKEYCODE_DPAD_DOWN, KEY_DOWN},
+ {AKEYCODE_DPAD_LEFT, KEY_LEFT},
+ {AKEYCODE_DPAD_RIGHT, KEY_RIGHT},
- BTN_MODE, // "Main button" (Stadia/PS/XBOX/Home)
+ // "Extra" controller buttons: some devices have "share" and "assistant"
+ {AKEYCODE_BUTTON_1, BTN_TRIGGER_HAPPY1},
+ {AKEYCODE_BUTTON_2, BTN_TRIGGER_HAPPY2},
+ {AKEYCODE_BUTTON_3, BTN_TRIGGER_HAPPY3},
+ {AKEYCODE_BUTTON_4, BTN_TRIGGER_HAPPY4},
+ {AKEYCODE_BUTTON_5, BTN_TRIGGER_HAPPY5},
+ {AKEYCODE_BUTTON_6, BTN_TRIGGER_HAPPY6},
+ {AKEYCODE_BUTTON_7, BTN_TRIGGER_HAPPY7},
+ {AKEYCODE_BUTTON_8, BTN_TRIGGER_HAPPY8},
+ {AKEYCODE_BUTTON_9, BTN_TRIGGER_HAPPY9},
+ {AKEYCODE_BUTTON_10, BTN_TRIGGER_HAPPY10},
+ {AKEYCODE_BUTTON_11, BTN_TRIGGER_HAPPY11},
+ {AKEYCODE_BUTTON_12, BTN_TRIGGER_HAPPY12},
+ {AKEYCODE_BUTTON_13, BTN_TRIGGER_HAPPY13},
+ {AKEYCODE_BUTTON_14, BTN_TRIGGER_HAPPY14},
+ {AKEYCODE_BUTTON_15, BTN_TRIGGER_HAPPY15},
+ {AKEYCODE_BUTTON_16, BTN_TRIGGER_HAPPY16},
- BTN_TRIGGER_HAPPY1, // Extra button: "Assistant" for Stadia
- BTN_TRIGGER_HAPPY2, // Extra button: "Share" for Stadia
+ // Assignment to support global assistant for devices that support it.
+ {AKEYCODE_ASSIST, KEY_ASSISTANT},
+ {AKEYCODE_VOICE_ASSIST, KEY_VOICECOMMAND},
};
-// Defines information for an axis.
-struct Axis {
- int number;
- int rangeMin;
- int rangeMax;
+// Defines axis mapping information between android and
+// uinput axis.
+struct GamepadAxis {
+ int32_t androidAxis;
+ float androidRangeMin;
+ float androidRangeMax;
+ int linuxUinputAxis;
+ int linuxUinputRangeMin;
+ int linuxUinputRangeMax;
};
// List of all axes supported by a gamepad
-static const Axis GAMEPAD_AXES[] = {
- {ABS_X, 0, 254}, // Left joystick X
- {ABS_Y, 0, 254}, // Left joystick Y
- {ABS_RX, 0, 254}, // Right joystick X
- {ABS_RY, 0, 254}, // Right joystick Y
- {ABS_Z, 0, 254}, // Left trigger
- {ABS_RZ, 0, 254}, // Right trigger
- {ABS_HAT0X, -1, 1}, // DPad X
- {ABS_HAT0Y, -1, 1}, // DPad Y
+static const GamepadAxis GAMEPAD_AXES[] = {
+ {AMOTION_EVENT_AXIS_X, -1, 1, ABS_X, 0, 254}, // Left joystick X
+ {AMOTION_EVENT_AXIS_Y, -1, 1, ABS_Y, 0, 254}, // Left joystick Y
+ {AMOTION_EVENT_AXIS_Z, -1, 1, ABS_Z, 0, 254}, // Right joystick X
+ {AMOTION_EVENT_AXIS_RZ, -1, 1, ABS_RZ, 0, 254}, // Right joystick Y
+ {AMOTION_EVENT_AXIS_LTRIGGER, 0, 1, ABS_GAS, 0, 254}, // Left trigger
+ {AMOTION_EVENT_AXIS_RTRIGGER, 0, 1, ABS_BRAKE, 0, 254}, // Right trigger
+ {AMOTION_EVENT_AXIS_HAT_X, -1, 1, ABS_HAT0X, -1, 1}, // DPad X
+ {AMOTION_EVENT_AXIS_HAT_Y, -1, 1, ABS_HAT0Y, -1, 1}, // DPad Y
};
} // namespace android
diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
index 0e96bd7ae47e..6e2e2c54518b 100644
--- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
+++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
@@ -31,27 +31,38 @@
#include <utils/String8.h>
#include <ctype.h>
-#include <linux/input.h>
-#include <unistd.h>
-#include <sys/time.h>
-#include <time.h>
-#include <stdint.h>
-#include <map>
#include <fcntl.h>
+#include <linux/input.h>
#include <linux/uinput.h>
#include <signal.h>
+#include <stdint.h>
#include <sys/inotify.h>
#include <sys/stat.h>
+#include <sys/time.h>
#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+#include <unordered_map>
#define SLOT_UNKNOWN -1
namespace android {
-static std::map<int32_t,int> keysMap;
-static std::map<int32_t,int32_t> slotsMap;
+#define GOOGLE_VENDOR_ID 0x18d1
+
+#define GOOGLE_VIRTUAL_REMOTE_PRODUCT_ID 0x0100
+#define GOOGLE_VIRTUAL_GAMEPAD_PROUCT_ID 0x0200
+
+static std::unordered_map<int32_t, int> keysMap;
+static std::unordered_map<int32_t, int32_t> slotsMap;
static BitSet32 mtSlots;
+// Maps android key code to linux key code.
+static std::unordered_map<int32_t, int> gamepadAndroidToLinuxKeyMap;
+
+// Maps an android gamepad axis to the index within the GAMEPAD_AXES array.
+static std::unordered_map<int32_t, int> gamepadAndroidAxisToIndexMap;
+
static void initKeysMap() {
if (keysMap.empty()) {
for (size_t i = 0; i < NELEM(KEYS); i++) {
@@ -60,16 +71,49 @@ static void initKeysMap() {
}
}
+static void initGamepadKeyMap() {
+ if (gamepadAndroidToLinuxKeyMap.empty()) {
+ for (size_t i = 0; i < NELEM(GAMEPAD_KEYS); i++) {
+ gamepadAndroidToLinuxKeyMap[GAMEPAD_KEYS[i].androidKeyCode] =
+ GAMEPAD_KEYS[i].linuxUinputKeyCode;
+ }
+ }
+
+ if (gamepadAndroidAxisToIndexMap.empty()) {
+ for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) {
+ gamepadAndroidAxisToIndexMap[GAMEPAD_AXES[i].androidAxis] = i;
+ }
+ }
+}
+
static int32_t getLinuxKeyCode(int32_t androidKeyCode) {
- std::map<int,int>::iterator it = keysMap.find(androidKeyCode);
+ std::unordered_map<int, int>::iterator it = keysMap.find(androidKeyCode);
if (it != keysMap.end()) {
return it->second;
}
return KEY_UNKNOWN;
}
+static int getGamepadkeyCode(int32_t androidKeyCode) {
+ std::unordered_map<int32_t, int>::iterator it =
+ gamepadAndroidToLinuxKeyMap.find(androidKeyCode);
+ if (it != gamepadAndroidToLinuxKeyMap.end()) {
+ return it->second;
+ }
+ return KEY_UNKNOWN;
+}
+
+static const GamepadAxis* getGamepadAxis(int32_t androidAxisCode) {
+ std::unordered_map<int32_t, int>::iterator it =
+ gamepadAndroidAxisToIndexMap.find(androidAxisCode);
+ if (it == gamepadAndroidToLinuxKeyMap.end()) {
+ return nullptr;
+ }
+ return &GAMEPAD_AXES[it->second];
+}
+
static int findSlot(int32_t pointerId) {
- std::map<int,int>::iterator it = slotsMap.find(pointerId);
+ std::unordered_map<int, int>::iterator it = slotsMap.find(pointerId);
if (it != slotsMap.end()) {
return it->second;
}
@@ -107,7 +151,7 @@ public:
// Open /dev/uinput and prepare to register
// the device with the given name and unique Id
- bool Open(const char* name, const char* uniqueId);
+ bool Open(const char* name, const char* uniqueId, uint16_t product);
// Checks if the current file descriptor is valid
bool IsValid() const { return mFd != kInvalidFileDescriptor; }
@@ -141,7 +185,7 @@ int UInputDescriptor::Detach() {
return fd;
}
-bool UInputDescriptor::Open(const char* name, const char* uniqueId) {
+bool UInputDescriptor::Open(const char* name, const char* uniqueId, uint16_t product) {
if (IsValid()) {
ALOGE("UInput device already open");
return false;
@@ -161,6 +205,8 @@ bool UInputDescriptor::Open(const char* name, const char* uniqueId) {
strlcpy(mUinputDescriptor.name, name, UINPUT_MAX_NAME_SIZE);
mUinputDescriptor.id.version = 1;
mUinputDescriptor.id.bustype = BUS_VIRTUAL;
+ mUinputDescriptor.id.vendor = GOOGLE_VENDOR_ID;
+ mUinputDescriptor.id.product = product;
// All UInput devices we use process keys
ioctl(mFd, UI_SET_EVBIT, EV_KEY);
@@ -258,7 +304,7 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId,
initKeysMap();
UInputDescriptor descriptor;
- if (!descriptor.Open(name, uniqueId)) {
+ if (!descriptor.Open(name, uniqueId, GOOGLE_VIRTUAL_REMOTE_PRODUCT_ID)) {
return nullptr;
}
@@ -277,21 +323,24 @@ NativeConnection* NativeConnection::open(const char* name, const char* uniqueId,
NativeConnection* NativeConnection::openGamepad(const char* name, const char* uniqueId) {
ALOGI("Registering uinput device %s: gamepad", name);
+ initGamepadKeyMap();
+
UInputDescriptor descriptor;
- if (!descriptor.Open(name, uniqueId)) {
+ if (!descriptor.Open(name, uniqueId, GOOGLE_VIRTUAL_GAMEPAD_PROUCT_ID)) {
return nullptr;
}
// set the keys mapped for gamepads
- for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) {
- descriptor.EnableKey(GAMEPAD_KEY_CODES[i]);
+ for (size_t i = 0; i < NELEM(GAMEPAD_KEYS); i++) {
+ descriptor.EnableKey(GAMEPAD_KEYS[i].linuxUinputKeyCode);
}
// define the axes that are required
descriptor.EnableAxesEvents();
for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) {
- const Axis& axis = GAMEPAD_AXES[i];
- descriptor.EnableAxis(axis.number, axis.rangeMin, axis.rangeMax);
+ const GamepadAxis& axis = GAMEPAD_AXES[i];
+ descriptor.EnableAxis(axis.linuxUinputAxis, axis.linuxUinputRangeMin,
+ axis.linuxUinputRangeMax);
}
if (!descriptor.Create()) {
@@ -350,7 +399,7 @@ static void nativeSendKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode, jb
}
}
-static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyIndex,
+static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyCode,
jboolean down) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
@@ -359,16 +408,16 @@ static void nativeSendGamepadKey(JNIEnv* env, jclass clazz, jlong ptr, jint keyI
return;
}
- if ((keyIndex < 0) || (keyIndex >= NELEM(GAMEPAD_KEY_CODES))) {
- ALOGE("Invalid gamepad key index: %d", keyIndex);
+ int linuxKeyCode = getGamepadkeyCode(keyCode);
+ if (linuxKeyCode == KEY_UNKNOWN) {
+ ALOGE("Gamepad: received an unknown keycode of %d.", keyCode);
return;
}
-
- connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[keyIndex], down ? 1 : 0);
+ connection->sendEvent(EV_KEY, linuxKeyCode, down ? 1 : 0);
}
static void nativeSendGamepadAxisValue(JNIEnv* env, jclass clazz, jlong ptr, jint axis,
- jint value) {
+ jfloat value) {
NativeConnection* connection = reinterpret_cast<NativeConnection*>(ptr);
if (!connection->IsGamepad()) {
@@ -376,7 +425,25 @@ static void nativeSendGamepadAxisValue(JNIEnv* env, jclass clazz, jlong ptr, jin
return;
}
- connection->sendEvent(EV_ABS, axis, value);
+ const GamepadAxis* axisInfo = getGamepadAxis(axis);
+ if (axisInfo == nullptr) {
+ ALOGE("Invalid axis: %d", axis);
+ return;
+ }
+
+ if (value > axisInfo->androidRangeMax) {
+ value = axisInfo->androidRangeMax;
+ } else if (value < axisInfo->androidRangeMin) {
+ value = axisInfo->androidRangeMin;
+ }
+
+ // Converts the android range into the device range
+ float movementPercent = (value - axisInfo->androidRangeMin) /
+ (axisInfo->androidRangeMax - axisInfo->androidRangeMin);
+ int axisRawValue = axisInfo->linuxUinputRangeMin +
+ movementPercent * (axisInfo->linuxUinputRangeMax - axisInfo->linuxUinputRangeMin);
+
+ connection->sendEvent(EV_ABS, axisInfo->linuxUinputAxis, axisRawValue);
}
static void nativeSendPointerDown(JNIEnv* env, jclass clazz, jlong ptr,
@@ -441,18 +508,20 @@ static void nativeClear(JNIEnv* env, jclass clazz, jlong ptr) {
}
}
} else {
- for (size_t i = 0; i < NELEM(GAMEPAD_KEY_CODES); i++) {
- connection->sendEvent(EV_KEY, GAMEPAD_KEY_CODES[i], 0);
+ for (size_t i = 0; i < NELEM(GAMEPAD_KEYS); i++) {
+ connection->sendEvent(EV_KEY, GAMEPAD_KEYS[i].linuxUinputKeyCode, 0);
}
for (size_t i = 0; i < NELEM(GAMEPAD_AXES); i++) {
- const Axis& axis = GAMEPAD_AXES[i];
- if ((axis.number == ABS_Z) || (axis.number == ABS_RZ)) {
+ const GamepadAxis& axis = GAMEPAD_AXES[i];
+
+ if ((axis.linuxUinputAxis == ABS_Z) || (axis.linuxUinputAxis == ABS_RZ)) {
// Mark triggers unpressed
- connection->sendEvent(EV_ABS, axis.number, 0);
+ connection->sendEvent(EV_ABS, axis.linuxUinputAxis, axis.linuxUinputRangeMin);
} else {
// Joysticks and dpad rests on center
- connection->sendEvent(EV_ABS, axis.number, (axis.rangeMin + axis.rangeMax) / 2);
+ connection->sendEvent(EV_ABS, axis.linuxUinputAxis,
+ (axis.linuxUinputRangeMin + axis.linuxUinputRangeMax) / 2);
}
}
}
@@ -475,7 +544,7 @@ static JNINativeMethod gUinputBridgeMethods[] = {
{"nativeClear", "(J)V", (void*)nativeClear},
{"nativeSendPointerSync", "(J)V", (void*)nativeSendPointerSync},
{"nativeSendGamepadKey", "(JIZ)V", (void*)nativeSendGamepadKey},
- {"nativeSendGamepadAxisValue", "(JII)V", (void*)nativeSendGamepadAxisValue},
+ {"nativeSendGamepadAxisValue", "(JIF)V", (void*)nativeSendGamepadAxisValue},
};
int register_android_server_tv_TvUinputBridge(JNIEnv* env) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2c0d4c0c9208..7b845557cf73 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -161,8 +161,11 @@ import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.PermissionChecker;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -171,6 +174,7 @@ import android.content.pm.CrossProfileAppsInternal;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -2703,8 +2707,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
final ComponentName doAdminReceiver = doAdmin.info.getComponent();
clearDeviceOwnerLocked(doAdmin, doUserId);
Slog.i(LOG_TAG, "Removing admin artifacts...");
- // TODO(b/149075700): Clean up application restrictions in UserManager.
removeAdminArtifacts(doAdminReceiver, doUserId);
+ Slog.i(LOG_TAG, "Uninstalling the DO...");
+ uninstallOrDisablePackage(doAdminComponent.getPackageName(), doUserId);
Slog.i(LOG_TAG, "Migration complete.");
// Note: KeyChain keys are not removed and will remain accessible for the apps that have
@@ -2716,6 +2721,47 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.write();
}
+ private void uninstallOrDisablePackage(String packageName, int userHandle) {
+ final ApplicationInfo appInfo;
+ try {
+ appInfo = mIPackageManager.getApplicationInfo(
+ packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+ } catch (RemoteException e) {
+ // Shouldn't happen.
+ return;
+ }
+ if (appInfo == null) {
+ Slog.wtf(LOG_TAG, "Failed to get package info for " + packageName);
+ return;
+ }
+ if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ Slog.i(LOG_TAG, String.format(
+ "Package %s is pre-installed, marking disabled until used", packageName));
+ mContext.getPackageManager().setApplicationEnabledSetting(packageName,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* flags */);
+ return;
+ }
+
+ final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+ @Override
+ public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+ IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+ final int status = intent.getIntExtra(
+ PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+ if (status == PackageInstaller.STATUS_SUCCESS) {
+ Slog.i(LOG_TAG, String.format(
+ "Package %s uninstalled for user %d", packageName, userHandle));
+ } else {
+ Slog.e(LOG_TAG, String.format(
+ "Failed to uninstall %s; status: %d", packageName, status));
+ }
+ }
+ };
+
+ final PackageInstaller pi = mInjector.getPackageManager(userHandle).getPackageInstaller();
+ pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender));
+ }
+
private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
// The following policies can be already controlled via parent instance, skip if so.
if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
@@ -8766,6 +8812,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
saveSettingsLocked(UserHandle.USER_SYSTEM);
clearUserPoliciesLocked(userId);
clearOverrideApnUnchecked();
+ clearApplicationRestrictions(userId);
+ mInjector.getPackageManagerInternal().clearBlockUninstallForUser(userId);
mOwners.clearDeviceOwner();
mOwners.writeDeviceOwner();
@@ -8779,6 +8827,19 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
toggleBackupServiceActive(UserHandle.USER_SYSTEM, true);
}
+ private void clearApplicationRestrictions(int userId) {
+ // Changing app restrictions involves disk IO, offload it to the background thread.
+ mBackgroundHandler.post(() -> {
+ final List<PackageInfo> installedPackageInfos = mInjector.getPackageManager(userId)
+ .getInstalledPackages(MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE);
+ final UserHandle userHandle = UserHandle.of(userId);
+ for (final PackageInfo packageInfo : installedPackageInfos) {
+ mInjector.getUserManager().setApplicationRestrictions(
+ packageInfo.packageName, null /* restrictions */, userHandle);
+ }
+ });
+ }
+
@Override
public boolean setProfileOwner(ComponentName who, String ownerName, int userHandle) {
if (!mHasFeature) {
@@ -8898,6 +8959,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
policyData.mOwnerInstalledCaCerts.clear();
saveSettingsLocked(userId);
clearUserPoliciesLocked(userId);
+ clearApplicationRestrictions(userId);
mOwners.removeProfileOwner(userId);
mOwners.writeProfileOwner(userId);
deleteTransferOwnershipBundleLocked(userId);
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 79699bb77a92..f423119d240a 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1610,6 +1610,8 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount
fsmStep();
+ mStatusCondition.notify_all();
+
return binder::Status::ok();
}
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index c87ece29800c..763e19bd14ab 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -67,6 +67,7 @@ import com.android.internal.os.BackgroundThread;
import com.android.internal.telephony.SmsApplication;
import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.notification.ShortcutHelper;
import java.util.ArrayList;
import java.util.Collections;
@@ -497,10 +498,6 @@ public class DataManager {
EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
}
- private boolean isPersonShortcut(@NonNull ShortcutInfo shortcutInfo) {
- return shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0;
- }
-
@VisibleForTesting
@WorkerThread
void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
@@ -712,7 +709,8 @@ public class DataManager {
@NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
mInjector.getBackgroundExecutor().execute(() -> {
for (ShortcutInfo shortcut : shortcuts) {
- if (isPersonShortcut(shortcut)) {
+ if (ShortcutHelper.isConversationShortcut(
+ shortcut, mShortcutServiceInternal, user.getIdentifier())) {
addOrUpdateConversationInfo(shortcut);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index 335217719cc9..064e3486823a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
@@ -308,7 +310,7 @@ public class SystemActionPerformerTest {
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
- anyBoolean(), any(Handler.class), any());
+ anyBoolean(), eq(SCREENSHOT_ACCESSIBILITY_ACTIONS), any(Handler.class), any());
}
// PendingIntent is a final class and cannot be mocked. So we are using this
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index f8bcff55ba41..6fe259e7fc85 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -45,6 +45,8 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -54,9 +56,11 @@ import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.IUidObserver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -70,6 +74,7 @@ import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
+import com.android.server.LocalServices;
import com.android.server.am.ProcessList.IsolatedUidRange;
import com.android.server.am.ProcessList.IsolatedUidRangeAllocator;
import com.android.server.appop.AppOpsService;
@@ -77,6 +82,7 @@ import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
@@ -115,6 +121,18 @@ public class ActivityManagerServiceTest {
UidRecord.CHANGE_ACTIVE
};
+ private static PackageManagerInternal sPackageManagerInternal;
+
+ @BeforeClass
+ public static void setUpOnce() {
+ sPackageManagerInternal = mock(PackageManagerInternal.class);
+ doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
+ .getSystemUiServiceComponent();
+ // Remove stale instance of PackageManagerInternal if there is any
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
+ }
+
@Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
private Context mContext = getInstrumentation().getTargetContext();
@@ -258,8 +276,11 @@ public class ActivityManagerServiceTest {
uidRec.hasInternetPermission = true;
mAms.mProcessList.mActiveUids.put(uid, uidRec);
- final ProcessRecord appRec = new ProcessRecord(mAms, new ApplicationInfo(), TAG, uid);
- appRec.thread = Mockito.mock(IApplicationThread.class);
+ ApplicationInfo info = new ApplicationInfo();
+ info.packageName = "";
+
+ final ProcessRecord appRec = new ProcessRecord(mAms, info, TAG, uid);
+ appRec.thread = mock(IApplicationThread.class);
mAms.mProcessList.mLruProcesses.add(appRec);
return uidRec;
@@ -497,7 +518,7 @@ public class ActivityManagerServiceTest {
};
final IUidObserver[] observers = new IUidObserver.Stub[changesToObserve.length];
for (int i = 0; i < observers.length; ++i) {
- observers[i] = Mockito.mock(IUidObserver.Stub.class);
+ observers[i] = mock(IUidObserver.Stub.class);
when(observers[i].asBinder()).thenReturn((IBinder) observers[i]);
mAms.registerUidObserver(observers[i], changesToObserve[i] /* which */,
ActivityManager.PROCESS_STATE_UNKNOWN /* cutpoint */, null /* caller */);
@@ -610,7 +631,7 @@ public class ActivityManagerServiceTest {
*/
@Test
public void testDispatchUidChanges_procStateCutpoint() throws RemoteException {
- final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
+ final IUidObserver observer = mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
mAms.registerUidObserver(observer, ActivityManager.UID_OBSERVER_PROCSTATE /* which */,
@@ -704,7 +725,7 @@ public class ActivityManagerServiceTest {
assertEquals("No observers registered, so validateUids should be empty",
0, mAms.mValidateUids.size());
- final IUidObserver observer = Mockito.mock(IUidObserver.Stub.class);
+ final IUidObserver observer = mock(IUidObserver.Stub.class);
when(observer.asBinder()).thenReturn((IBinder) observer);
mAms.registerUidObserver(observer, 0, 0, null);
// Verify that when observers are registered, then validateUids is correctly updated.
diff --git a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
index d12d8040183a..b2d7177e04eb 100644
--- a/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/OomAdjusterTests.java
@@ -22,12 +22,15 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import android.app.ActivityManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.pm.PackageManagerInternal;
import com.android.server.LocalServices;
import com.android.server.wm.ActivityTaskManagerService;
@@ -45,6 +48,7 @@ import org.junit.Test;
public class OomAdjusterTests {
private static Context sContext;
private static ActivityManagerService sService;
+ private static PackageManagerInternal sPackageManagerInternal;
private ProcessRecord mProcessRecord;
@@ -56,6 +60,13 @@ public class OomAdjusterTests {
public static void setUpOnce() {
sContext = getInstrumentation().getTargetContext();
+ sPackageManagerInternal = mock(PackageManagerInternal.class);
+ doReturn(new ComponentName("", "")).when(sPackageManagerInternal)
+ .getSystemUiServiceComponent();
+ // Remove stale instance of PackageManagerInternal if there is any
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, sPackageManagerInternal);
+
// We need to run with dexmaker share class loader to make use of
// ActivityTaskManagerService from wm package.
runWithDexmakerShareClassLoader(() -> {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 74e7f8c44d1a..a0b9d9d2a875 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -62,6 +62,10 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
mContext = getContext();
+ // Make createContextAsUser to work.
+ mContext.packageName = "com.android.frameworks.servicestests";
+ getServices().addPackageContext(UserHandle.of(0), mContext);
+
when(getServices().packageManager.hasSystemFeature(eq(PackageManager.FEATURE_DEVICE_ADMIN)))
.thenReturn(true);
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 09d1d3a270ba..57039e53429e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -196,6 +196,11 @@ public class DevicePolicyManagerTest extends DpmTestBase {
anyInt(),
any(UserHandle.class));
+ // Make createContextAsUser to work.
+ mContext.packageName = "com.android.frameworks.servicestests";
+ getServices().addPackageContext(UserHandle.of(0), mContext);
+ getServices().addPackageContext(UserHandle.of(DpmMockContext.CALLER_USER_HANDLE), mContext);
+
// By default, pretend all users are running and unlocked.
when(getServices().userManager.isUserUnlocked(anyInt())).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 8625a1ed9fda..20716ab501df 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -460,6 +460,15 @@ public class DpmMockContext extends MockContext {
}
@Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ try {
+ return mMockSystemServices.createPackageContextAsUser(packageName, flags, user);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
public ContentResolver getContentResolver() {
return mMockSystemServices.contentResolver;
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
new file mode 100644
index 000000000000..c9dbdd2364cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStrongAuthTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
+import static com.android.server.locksettings.LockSettingsStrongAuth.DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS;
+import static com.android.server.locksettings.LockSettingsStrongAuth.DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS;
+import static com.android.server.locksettings.LockSettingsStrongAuth.NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG;
+import static com.android.server.locksettings.LockSettingsStrongAuth.NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG;
+import static com.android.server.locksettings.LockSettingsStrongAuth.STRONG_AUTH_TIMEOUT_ALARM_TAG;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricIdleTimeoutAlarmListener;
+import com.android.server.locksettings.LockSettingsStrongAuth.NonStrongBiometricTimeoutAlarmListener;
+import com.android.server.locksettings.LockSettingsStrongAuth.StrongAuthTimeoutAlarmListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class LockSettingsStrongAuthTest {
+
+ private static final String TAG = LockSettingsStrongAuthTest.class.getSimpleName();
+
+ private static final int PRIMARY_USER_ID = 0;
+
+ private LockSettingsStrongAuth mStrongAuth;
+ private final int mDefaultStrongAuthFlags = STRONG_AUTH_NOT_REQUIRED;
+ private final boolean mDefaultIsNonStrongBiometricAllowed = true;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private LockSettingsStrongAuth.Injector mInjector;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private DevicePolicyManager mDPM;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mInjector.getAlarmManager(mContext)).thenReturn(mAlarmManager);
+ when(mInjector.getDefaultStrongAuthFlags(mContext)).thenReturn(mDefaultStrongAuthFlags);
+ when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDPM);
+
+ mStrongAuth = new LockSettingsStrongAuth(mContext, mInjector);
+ }
+
+ @Test
+ public void testScheduleNonStrongBiometricIdleTimeout() {
+ final long nextAlarmTime = 1000;
+ when(mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_MS))
+ .thenReturn(nextAlarmTime);
+ mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
+
+ waitForIdle();
+ NonStrongBiometricIdleTimeoutAlarmListener alarm = mStrongAuth
+ .mNonStrongBiometricIdleTimeoutAlarmListener.get(PRIMARY_USER_ID);
+ // verify that a new alarm for idle timeout is added for the user
+ assertNotNull(alarm);
+ // verify that the alarm is scheduled
+ verifyAlarm(nextAlarmTime, NON_STRONG_BIOMETRIC_IDLE_TIMEOUT_ALARM_TAG, alarm);
+ }
+
+ @Test
+ public void testSetIsNonStrongBiometricAllowed_disallowed() {
+ mStrongAuth.setIsNonStrongBiometricAllowed(false /* allowed */, PRIMARY_USER_ID);
+
+ waitForIdle();
+ // verify that unlocking with non-strong biometrics is not allowed
+ assertFalse(mStrongAuth.mIsNonStrongBiometricAllowedForUser
+ .get(PRIMARY_USER_ID, mDefaultIsNonStrongBiometricAllowed));
+ }
+
+ @Test
+ public void testReportSuccessfulBiometricUnlock_nonStrongBiometric_fallbackTimeout() {
+ final long nextAlarmTime = 1000;
+ when(mInjector.getNextAlarmTimeMs(DEFAULT_NON_STRONG_BIOMETRIC_TIMEOUT_MS))
+ .thenReturn(nextAlarmTime);
+ mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
+
+ waitForIdle();
+ NonStrongBiometricTimeoutAlarmListener alarm =
+ mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(PRIMARY_USER_ID);
+ // verify that a new alarm for fallback timeout is added for the user
+ assertNotNull(alarm);
+ // verify that the alarm is scheduled
+ verifyAlarm(nextAlarmTime, NON_STRONG_BIOMETRIC_TIMEOUT_ALARM_TAG, alarm);
+ }
+
+ @Test
+ public void testRequireStrongAuth_nonStrongBiometric_fallbackTimeout() {
+ mStrongAuth.requireStrongAuth(
+ STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT /* strongAuthReason */,
+ PRIMARY_USER_ID);
+
+ waitForIdle();
+ // verify that the StrongAuthFlags for the user contains the expected flag
+ final int expectedFlag = STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT;
+ verifyStrongAuthFlags(expectedFlag, PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testReportSuccessfulBiometricUnlock_nonStrongBiometric_cancelIdleTimeout() {
+ // lock device and schedule an alarm for non-strong biometric idle timeout
+ mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
+ // unlock with non-strong biometric
+ mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
+
+ waitForIdle();
+
+ // verify that the current alarm for idle timeout is cancelled after a successful unlock
+ verify(mAlarmManager).cancel(any(NonStrongBiometricIdleTimeoutAlarmListener.class));
+ }
+
+ @Test
+ public void testReportSuccessfulBiometricUnlock_strongBio_cancelAlarmsAndAllowNonStrongBio() {
+ setupAlarms(PRIMARY_USER_ID);
+ mStrongAuth.reportSuccessfulBiometricUnlock(true /* isStrongBiometric */, PRIMARY_USER_ID);
+
+ waitForIdle();
+ // verify that unlocking with strong biometric cancels alarms for fallback and idle timeout
+ // and re-allow unlocking with non-strong biometric
+ verifyAlarmsCancelledAndNonStrongBiometricAllowed(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testReportSuccessfulStrongAuthUnlock_schedulePrimaryAuthTimeout() {
+ final long nextAlarmTime = 1000;
+ when(mInjector.getNextAlarmTimeMs(mDPM.getRequiredStrongAuthTimeout(null, PRIMARY_USER_ID)))
+ .thenReturn(nextAlarmTime);
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);
+
+ waitForIdle();
+ StrongAuthTimeoutAlarmListener alarm =
+ mStrongAuth.mStrongAuthTimeoutAlarmListenerForUser.get(PRIMARY_USER_ID);
+ // verify that a new alarm for primary auth timeout is added for the user
+ assertNotNull(alarm);
+ // verify that the alarm is scheduled
+ verifyAlarm(nextAlarmTime, STRONG_AUTH_TIMEOUT_ALARM_TAG, alarm);
+ }
+
+ @Test
+ public void testReportSuccessfulStrongAuthUnlock_cancelAlarmsAndAllowNonStrongBio() {
+ setupAlarms(PRIMARY_USER_ID);
+ mStrongAuth.reportSuccessfulStrongAuthUnlock(PRIMARY_USER_ID);
+
+ waitForIdle();
+ // verify that unlocking with primary auth (PIN/pattern/password) cancels alarms
+ // for fallback and idle timeout and re-allow unlocking with non-strong biometric
+ verifyAlarmsCancelledAndNonStrongBiometricAllowed(PRIMARY_USER_ID);
+ }
+
+ @Test
+ public void testFallbackTimeout_convenienceBiometric_weakBiometric() {
+ // assume that unlock with convenience biometric
+ mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
+ // assume that unlock again with weak biometric
+ mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
+
+ waitForIdle();
+ // verify that the fallback alarm scheduled when unlocking with convenience biometric is
+ // not affected when unlocking again with weak biometric
+ verify(mAlarmManager, never()).cancel(any(NonStrongBiometricTimeoutAlarmListener.class));
+ assertNotNull(mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(PRIMARY_USER_ID));
+ }
+
+ private void verifyAlarm(long when, String tag, AlarmManager.OnAlarmListener alarm) {
+ verify(mAlarmManager).set(
+ eq(AlarmManager.ELAPSED_REALTIME),
+ eq(when),
+ eq(tag),
+ eq(alarm),
+ eq(mStrongAuth.mHandler));
+ }
+
+ private void verifyStrongAuthFlags(int reason, int userId) {
+ final int flags = mStrongAuth.mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
+ Log.d(TAG, "verifyStrongAuthFlags:"
+ + " reason=" + Integer.toHexString(reason)
+ + " userId=" + userId
+ + " flags=" + Integer.toHexString(flags));
+ assertTrue(containsFlag(flags, reason));
+ }
+
+ private void setupAlarms(int userId) {
+ // schedule (a) an alarm for non-strong biometric fallback timeout and (b) an alarm for
+ // non-strong biometric idle timeout, so later we can verify that unlocking with
+ // strong biometric or primary auth will cancel those alarms
+ mStrongAuth.reportSuccessfulBiometricUnlock(false /* isStrongBiometric */, PRIMARY_USER_ID);
+ mStrongAuth.scheduleNonStrongBiometricIdleTimeout(PRIMARY_USER_ID);
+ }
+
+ private void verifyAlarmsCancelledAndNonStrongBiometricAllowed(int userId) {
+ // verify that the current alarm for non-strong biometric fallback timeout is cancelled and
+ // removed
+ verify(mAlarmManager).cancel(any(NonStrongBiometricTimeoutAlarmListener.class));
+ assertNull(mStrongAuth.mNonStrongBiometricTimeoutAlarmListener.get(userId));
+
+ // verify that the current alarm for non-strong biometric idle timeout is cancelled
+ verify(mAlarmManager).cancel(any(NonStrongBiometricIdleTimeoutAlarmListener.class));
+
+ // verify that unlocking with non-strong biometrics is allowed
+ assertTrue(mStrongAuth.mIsNonStrongBiometricAllowedForUser
+ .get(userId, mDefaultIsNonStrongBiometricAllowed));
+ }
+
+ private static boolean containsFlag(int haystack, int needle) {
+ return (haystack & needle) != 0;
+ }
+
+ private static void waitForIdle() {
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 728e1492c0d5..e16f3145de0b 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -215,6 +215,8 @@ public final class DataManagerTest {
mDataManager = new DataManager(mContext, mInjector);
mDataManager.initialize();
+ when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any())).thenReturn(true);
verify(mShortcutServiceInternal).addShortcutChangeCallback(
mShortcutChangeCallbackCaptor.capture());
mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
index c4289efe1839..e7e8aca86364 100644
--- a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -158,17 +158,6 @@ public class AttentionDetectorTest extends AndroidTestCase {
}
@Test
- public void testOnUserActivity_disablesSettingIfNotSufficientPermissions() {
- when(mPackageManager.checkPermission(any(), any())).thenReturn(
- PackageManager.PERMISSION_DENIED);
-
- registerAttention();
- boolean enabled = Settings.Secure.getIntForUser(getContext().getContentResolver(),
- Settings.Secure.ADAPTIVE_SLEEP, 0, UserHandle.USER_CURRENT) == 1;
- assertFalse(enabled);
- }
-
- @Test
public void testOnUserActivity_doesntCrashIfNoAttentionService() {
mAttentionManagerInternal = null;
registerAttention();
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 965304f3c433..21af3563b869 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -26,6 +26,7 @@ import android.media.tv.ITvInputManager;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputService;
import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tunerresourcemanager.CasSessionRequest;
import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
import android.media.tv.tunerresourcemanager.ResourceClientProfile;
import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
@@ -34,7 +35,6 @@ import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
import android.media.tv.tunerresourcemanager.TunerLnbRequest;
import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
@@ -236,12 +236,8 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isFalse();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isFalse();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
}
@@ -264,12 +260,8 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isFalse();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isFalse();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
}
@@ -296,12 +288,8 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(0);
}
@@ -334,23 +322,15 @@ public class TunerResourceManagerServiceTest {
int[] frontendHandle = new int[1];
TunerFrontendRequest request =
new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(infos[0].getId());
request =
new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(infos[1].getId());
assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -394,32 +374,20 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
request =
new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isFalse();
- assertThat(listener.isRelaimed()).isFalse();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isFalse();
+ assertThat(listener.isRelaimed()).isFalse();
request =
new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isFalse();
- assertThat(listener.isRelaimed()).isFalse();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isFalse();
+ assertThat(listener.isRelaimed()).isFalse();
}
@Test
@@ -456,12 +424,8 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(infos[0].getId());
assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
@@ -470,12 +434,8 @@ public class TunerResourceManagerServiceTest {
request =
new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(infos[1].getId());
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
@@ -511,12 +471,8 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]);
assertThat(frontendId).isEqualTo(infos[0].getId());
assertThat(mTunerResourceManagerService
@@ -534,6 +490,99 @@ public class TunerResourceManagerServiceTest {
}
@Test
+ public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() {
+ // Register clients
+ ResourceClientProfile[] profiles = new ResourceClientProfile[2];
+ profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ profiles[1] = new ResourceClientProfile("1" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ int[] clientPriorities = {100, 500};
+ int[] clientId0 = new int[1];
+ int[] clientId1 = new int[1];
+ TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+ mTunerResourceManagerService.registerClientProfileInternal(
+ profiles[0], listener, clientId0);
+ assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ mTunerResourceManagerService.getClientProfile(clientId0[0])
+ .setPriority(clientPriorities[0]);
+ mTunerResourceManagerService.registerClientProfileInternal(
+ profiles[1], new TestResourcesReclaimListener(), clientId1);
+ assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+ mTunerResourceManagerService.getClientProfile(clientId1[0])
+ .setPriority(clientPriorities[1]);
+
+ // Init cas resources.
+ mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+ CasSessionRequest request = new CasSessionRequest(clientId0[0], 1 /*casSystemId*/);
+ int[] casSessionHandle = new int[1];
+ // Request for 2 cas sessions.
+ assertThat(mTunerResourceManagerService
+ .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+ assertThat(mTunerResourceManagerService
+ .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+ .getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCasResource(1)
+ .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+
+ request = new CasSessionRequest(clientId1[0], 1);
+ assertThat(mTunerResourceManagerService
+ .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
+ .getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+ .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService.getCasResource(1)
+ .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+ assertThat(listener.isRelaimed()).isTrue();
+ }
+
+ @Test
+ public void releaseCasTest() {
+ // Register clients
+ ResourceClientProfile[] profiles = new ResourceClientProfile[1];
+ profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+ TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+ int[] clientId = new int[1];
+ TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+ mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
+ assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+ // Init cas resources.
+ mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+ CasSessionRequest request = new CasSessionRequest(clientId[0], 1 /*casSystemId*/);
+ int[] casSessionHandle = new int[1];
+ // Request for 1 cas sessions.
+ assertThat(mTunerResourceManagerService
+ .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+ assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+ .isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
+ .getInUseCasSystemId()).isEqualTo(1);
+ assertThat(mTunerResourceManagerService.getCasResource(1)
+ .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+
+ // Release cas
+ mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService
+ .getCasResource(1), clientId[0]);
+ assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
+ .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+ assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+ assertThat(mTunerResourceManagerService.getCasResource(1)
+ .getOwnerClientIds()).isEmpty();
+ }
+
+ @Test
public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() {
// Register clients
ResourceClientProfile[] profiles = new ResourceClientProfile[2];
@@ -562,24 +611,16 @@ public class TunerResourceManagerServiceTest {
TunerLnbRequest request = new TunerLnbRequest(clientId0[0]);
int[] lnbHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestLnbInternal(request, lnbHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestLnbInternal(request, lnbHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
.isEqualTo(lnbIds[0]);
assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
.getInUseLnbIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(lnbIds[0])));
request = new TunerLnbRequest(clientId1[0]);
- try {
- assertThat(mTunerResourceManagerService
- .requestLnbInternal(request, lnbHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestLnbInternal(request, lnbHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
.isEqualTo(lnbIds[0]);
assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
@@ -608,12 +649,8 @@ public class TunerResourceManagerServiceTest {
TunerLnbRequest request = new TunerLnbRequest(clientId[0]);
int[] lnbHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestLnbInternal(request, lnbHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestLnbInternal(request, lnbHandle)).isTrue();
int lnbId = mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]);
assertThat(lnbId).isEqualTo(lnbIds[0]);
@@ -647,12 +684,8 @@ public class TunerResourceManagerServiceTest {
TunerFrontendRequest request =
new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
int[] frontendHandle = new int[1];
- try {
- assertThat(mTunerResourceManagerService
- .requestFrontendInternal(request, frontendHandle)).isTrue();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ assertThat(mTunerResourceManagerService
+ .requestFrontendInternal(request, frontendHandle)).isTrue();
assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
.isEqualTo(infos[0].getId());
assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
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 ecdd9e548e6a..7a5e2266e62f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6074,6 +6074,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(PKG);
+ when(info.getId()).thenReturn("someshortcutId");
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
when(info.isLongLived()).thenReturn(true);
when(info.isEnabled()).thenReturn(true);
shortcutInfos.add(info);
@@ -6137,6 +6140,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Pretend the shortcut exists
List<ShortcutInfo> shortcutInfos = new ArrayList<>();
ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(PKG);
+ when(info.getId()).thenReturn("someshortcutId");
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
when(info.isLongLived()).thenReturn(true);
when(info.isEnabled()).thenReturn(true);
shortcutInfos.add(info);
@@ -6483,6 +6489,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
ShortcutInfo si = mock(ShortcutInfo.class);
+ when(si.getPackage()).thenReturn(PKG_P);
+ when(si.getId()).thenReturn("convo");
+ when(si.getUserId()).thenReturn(USER_SYSTEM);
when(si.getShortLabel()).thenReturn("Hello");
when(si.isLongLived()).thenReturn(true);
when(si.isEnabled()).thenReturn(true);
@@ -6514,6 +6523,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
ShortcutInfo si = mock(ShortcutInfo.class);
+ when(si.getPackage()).thenReturn(PKG_P);
+ when(si.getId()).thenReturn("convo");
+ when(si.getUserId()).thenReturn(USER_SYSTEM);
when(si.getShortLabel()).thenReturn("Hello");
when(si.isLongLived()).thenReturn(false);
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index f7304bd0075b..3095c87ba2f6 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -170,6 +170,9 @@ public class ShortcutHelperTest extends UiServiceTestCase {
@Test
public void testGetValidShortcutInfo_notLongLived() {
ShortcutInfo si = mock(ShortcutInfo.class);
+ when(si.getPackage()).thenReturn(PKG);
+ when(si.getId()).thenReturn(SHORTCUT_ID);
+ when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(si.isLongLived()).thenReturn(false);
when(si.isEnabled()).thenReturn(true);
ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -184,6 +187,9 @@ public class ShortcutHelperTest extends UiServiceTestCase {
@Test
public void testGetValidShortcutInfo_notSharingShortcut() {
ShortcutInfo si = mock(ShortcutInfo.class);
+ when(si.getPackage()).thenReturn(PKG);
+ when(si.getId()).thenReturn(SHORTCUT_ID);
+ when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(si.isLongLived()).thenReturn(true);
when(si.isEnabled()).thenReturn(true);
ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -198,6 +204,9 @@ public class ShortcutHelperTest extends UiServiceTestCase {
@Test
public void testGetValidShortcutInfo_notEnabled() {
ShortcutInfo si = mock(ShortcutInfo.class);
+ when(si.getPackage()).thenReturn(PKG);
+ when(si.getId()).thenReturn(SHORTCUT_ID);
+ when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(si.isLongLived()).thenReturn(true);
when(si.isEnabled()).thenReturn(false);
ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -212,6 +221,9 @@ public class ShortcutHelperTest extends UiServiceTestCase {
@Test
public void testGetValidShortcutInfo_isValid() {
ShortcutInfo si = mock(ShortcutInfo.class);
+ when(si.getPackage()).thenReturn(PKG);
+ when(si.getId()).thenReturn(SHORTCUT_ID);
+ when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(si.isLongLived()).thenReturn(true);
when(si.isEnabled()).thenReturn(true);
ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 6ae8313e39dd..0700f9f2b29c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -329,6 +329,7 @@ class ActivityTestsBase extends SystemServiceTestsBase {
private boolean mCreateStack = true;
private ActivityStack mStack;
+ private TaskDisplayArea mTaskDisplayArea;
TaskBuilder(ActivityStackSupervisor supervisor) {
mSupervisor = supervisor;
@@ -378,9 +379,16 @@ class ActivityTestsBase extends SystemServiceTestsBase {
return this;
}
+ TaskBuilder setDisplay(DisplayContent display) {
+ mTaskDisplayArea = display.getDefaultTaskDisplayArea();
+ return this;
+ }
+
Task build() {
if (mStack == null && mCreateStack) {
- mStack = mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea().createStack(
+ TaskDisplayArea displayArea = mTaskDisplayArea != null ? mTaskDisplayArea
+ : mSupervisor.mRootWindowContainer.getDefaultTaskDisplayArea();
+ mStack = displayArea.createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
spyOn(mStack);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index daff14992e94..80fcf2e121f4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -41,6 +41,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -74,6 +75,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import android.annotation.SuppressLint;
+import android.app.ActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -1207,6 +1209,31 @@ public class DisplayContentTests extends WindowTestsBase {
assertNull(taskDisplayArea.getOrCreateRootHomeTask());
}
+ @Test
+ public void testFindScrollCaptureTargetWindow_behindWindow() {
+ DisplayContent display = createNewDisplay();
+ ActivityStack stack = createTaskStackOnDisplay(display);
+ Task task = createTaskInStack(stack, 0 /* userId */);
+ WindowState activityWindow = createAppWindow(task, TYPE_APPLICATION, "App Window");
+ WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
+
+ WindowState result = display.findScrollCaptureTargetWindow(behindWindow,
+ ActivityTaskManager.INVALID_TASK_ID);
+ assertEquals(activityWindow, result);
+ }
+
+ @Test
+ public void testFindScrollCaptureTargetWindow_taskId() {
+ DisplayContent display = createNewDisplay();
+ ActivityStack stack = createTaskStackOnDisplay(display);
+ Task task = createTaskInStack(stack, 0 /* userId */);
+ WindowState window = createAppWindow(task, TYPE_APPLICATION, "App Window");
+ WindowState behindWindow = createWindow(null, TYPE_SCREENSHOT, display, "Screenshot");
+
+ WindowState result = display.findScrollCaptureTargetWindow(null, task.mTaskId);
+ assertEquals(window, result);
+ }
+
private boolean isOptionsPanelAtRight(int displayId) {
return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index be2559719438..e887be0c48c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -33,6 +33,7 @@ import androidx.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
/**
* Build/Install/Run:
@@ -40,6 +41,7 @@ import org.junit.Test;
*/
@SmallTest
@Presubmit
+@RunWith(WindowTestRunner.class)
@FlakyTest
public class RefreshRatePolicyTest extends WindowTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 519ac780bd6b..dcc2ff1311a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -54,10 +54,7 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.TaskInfo;
@@ -75,12 +72,10 @@ import android.util.DisplayMetrics;
import android.util.Xml;
import android.view.DisplayInfo;
-import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.wm.Task.TaskFactory;
-import com.android.server.wm.utils.WmDisplayCutout;
import org.junit.Before;
import org.junit.Test;
@@ -368,25 +363,38 @@ public class TaskRecordTests extends ActivityTestsBase {
@Test
public void testComputeConfigResourceOverrides() {
- final Task task = new TaskBuilder(mSupervisor).build();
+ final Rect fullScreenBounds = new Rect(0, 0, 1080, 1920);
+ TestDisplayContent display = new TestDisplayContent.Builder(
+ mService, fullScreenBounds.width(), fullScreenBounds.height()).build();
+ final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
final Configuration inOutConfig = new Configuration();
final Configuration parentConfig = new Configuration();
final int longSide = 1200;
final int shortSide = 600;
+ final Rect parentBounds = new Rect(0, 0, 250, 500);
+ parentConfig.windowConfiguration.setBounds(parentBounds);
parentConfig.densityDpi = 400;
- parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px
- parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
+ parentConfig.screenHeightDp = (parentBounds.bottom * 160) / parentConfig.densityDpi; // 200
+ parentConfig.screenWidthDp = (parentBounds.right * 160) / parentConfig.densityDpi; // 100
parentConfig.windowConfiguration.setRotation(ROTATION_0);
- // Portrait bounds.
- inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide);
- // By default, the parent bounds should limit the existing input bounds.
+ // By default, the input bounds will fill parent.
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
assertEquals(Configuration.ORIENTATION_PORTRAIT, inOutConfig.orientation);
+ // If bounds are overridden, config properties should be made to match. Surface hierarchy
+ // will crop for policy.
+ inOutConfig.setToDefaults();
+ inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide);
+ // By default, the parent bounds should limit the existing input bounds.
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig);
+
+ assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160);
+ assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160);
+
inOutConfig.setToDefaults();
// Landscape bounds.
inOutConfig.windowConfiguration.getBounds().set(0, 0, longSide, shortSide);
@@ -394,21 +402,17 @@ public class TaskRecordTests extends ActivityTestsBase {
// Setup the display with a top stable inset. The later assertion will ensure the inset is
// excluded from screenHeightDp.
final int statusBarHeight = 100;
- final DisplayContent displayContent = task.mDisplayContent;
- final DisplayPolicy policy = mock(DisplayPolicy.class);
+ final DisplayPolicy policy = display.getDisplayPolicy();
doAnswer(invocationOnMock -> {
final Rect insets = invocationOnMock.<Rect>getArgument(0);
insets.top = statusBarHeight;
return null;
}).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0));
- doReturn(policy).when(displayContent).getDisplayPolicy();
- doReturn(mock(WmDisplayCutout.class)).when(displayContent)
- .calculateDisplayCutoutForRotation(anyInt());
// Without limiting to be inside the parent bounds, the out screen size should keep relative
// to the input bounds.
final ActivityRecord.CompatDisplayInsets compatIntsets =
- new ActivityRecord.CompatDisplayInsets(displayContent, task);
+ new ActivityRecord.CompatDisplayInsets(display, task);
task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi,
@@ -454,7 +458,6 @@ public class TaskRecordTests extends ActivityTestsBase {
parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px
parentConfig.windowConfiguration.setRotation(ROTATION_0);
- final float density = 2.5f; // densityDpi / DENSITY_DEFAULT_SCALE = 400 / 160.0f
final int longSideDp = 480; // longSide / density = 1200 / 400 * 160
final int shortSideDp = 240; // shortSide / density = 600 / 400 * 160
final int screenLayout = parentConfig.screenLayout
@@ -463,31 +466,38 @@ public class TaskRecordTests extends ActivityTestsBase {
Configuration.reduceScreenLayout(screenLayout, longSideDp, shortSideDp);
// Portrait bounds overlapping with navigation bar, without insets.
- inOutConfig.windowConfiguration.getBounds().set(0,
+ final Rect freeformBounds = new Rect(0,
displayHeight - 10 - longSide,
shortSide,
displayHeight - 10);
+ inOutConfig.windowConfiguration.setBounds(freeformBounds);
// Set to freeform mode to verify bug fix.
inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
- assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
- assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ // screenW/H should not be effected by parent since overridden and freeform
+ assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
inOutConfig.setToDefaults();
// Landscape bounds overlapping with navigtion bar, without insets.
- inOutConfig.windowConfiguration.getBounds().set(0,
+ freeformBounds.set(0,
displayHeight - 10 - shortSide,
longSide,
displayHeight - 10);
+ inOutConfig.windowConfiguration.setBounds(freeformBounds);
inOutConfig.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
- assertEquals(parentConfig.screenWidthDp, inOutConfig.screenWidthDp);
- assertEquals(parentConfig.screenHeightDp, inOutConfig.screenHeightDp);
+ assertEquals(freeformBounds.width() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenWidthDp);
+ assertEquals(freeformBounds.height() * 160 / parentConfig.densityDpi,
+ inOutConfig.screenHeightDp);
assertEquals(reducedScreenLayout, inOutConfig.screenLayout);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 7cb5e84e4e48..f354a04101f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -186,7 +186,7 @@ public class TaskStackTests extends WindowTestsBase {
final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
final int stackOutset = 10;
spyOn(stack);
- doReturn(stackOutset).when(stack).getStackOutset();
+ doReturn(stackOutset).when(stack).getTaskOutset();
doReturn(true).when(stack).inMultiWindowMode();
// Mock the resolved override windowing mode to non-fullscreen
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 91c3c2782d94..e39b4bcd2eb0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -24,6 +24,7 @@ import android.os.RemoteException;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.DragEvent;
+import android.view.IScrollCaptureController;
import android.view.IWindow;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -113,6 +114,10 @@ public class TestIWindow extends IWindow.Stub {
}
@Override
+ public void requestScrollCapture(IScrollCaptureController controller) throws RemoteException {
+ }
+
+ @Override
public void showInsets(int types, boolean fromIme) throws RemoteException {
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 53cc09bf08e8..7d2e88014f45 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -44,7 +44,6 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -82,7 +81,7 @@ import java.util.List;
* Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}.
*
* Build/Install/Run:
- * atest WmTests:TaskOrganizerTests
+ * atest WmTests:WindowOrganizerTests
*/
@SmallTest
@Presubmit
@@ -265,15 +264,22 @@ public class WindowOrganizerTests extends WindowTestsBase {
// newly entering the windowing mode.
final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- verify(organizer2).onTaskAppeared(any());
+ // One each for task and task2
+ verify(organizer2, times(2)).onTaskAppeared(any());
+ verify(organizer2, times(0)).onTaskVanished(any());
+ // One for task
+ verify(organizer).onTaskVanished(any());
assertTrue(stack2.isOrganized());
// Now we unregister the second one, the first one should automatically be reregistered
// so we verify that it's now seeing changes.
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2);
+ verify(organizer, times(3)).onTaskAppeared(any());
+ verify(organizer2, times(2)).onTaskVanished(any());
stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- verify(organizer, times(2)).onTaskAppeared(any());
+ verify(organizer, times(4)).onTaskAppeared(any());
+ verify(organizer2, times(2)).onTaskVanished(any());
assertTrue(stack3.isOrganized());
}
@@ -407,22 +413,20 @@ public class WindowOrganizerTests extends WindowTestsBase {
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
final Task task = stack.getTopMostTask();
WindowContainerTransaction t = new WindowContainerTransaction();
- t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
final int origScreenWDp = task.getConfiguration().screenHeightDp;
final int origScreenHDp = task.getConfiguration().screenHeightDp;
t = new WindowContainerTransaction();
// verify that setting config overrides on parent restricts children.
t.setScreenSizeDp(stack.mRemoteToken
- .toWindowContainerToken(), origScreenWDp, origScreenHDp);
- t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 150, 200));
+ .toWindowContainerToken(), origScreenWDp, origScreenHDp / 2);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
+ assertEquals(origScreenHDp / 2, task.getConfiguration().screenHeightDp);
t = new WindowContainerTransaction();
t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
SCREEN_HEIGHT_DP_UNDEFINED);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
- assertNotEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
+ assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
}
@Test
@@ -905,12 +909,13 @@ public class WindowOrganizerTests extends WindowTestsBase {
task.setHasBeenVisible(true);
verify(organizer, times(1)).onTaskAppeared(any());
- task.taskOrganizerUnregistered();
+ task.setTaskOrganizer(null);
+ verify(organizer, times(1)).onTaskVanished(any());
task.setTaskOrganizer(organizer);
verify(organizer, times(2)).onTaskAppeared(any());
task.removeImmediately();
- verify(organizer).onTaskVanished(any());
+ verify(organizer, times(2)).onTaskVanished(any());
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index e561c13a4e99..6a64d1c976c4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -247,7 +247,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
WindowState createAppWindow(Task task, int type, String name) {
synchronized (mWm.mGlobalLock) {
final ActivityRecord activity =
- WindowTestUtils.createTestActivityRecord(mDisplayContent);
+ WindowTestUtils.createTestActivityRecord(task.getDisplayContent());
task.addChild(activity, 0);
return createWindow(null, type, activity, name);
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 9dfa3ac7a5d8..fa9909547fc4 100755
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -734,6 +734,31 @@ public abstract class Connection extends Conferenceable {
"android.telecom.extra.ORIGINAL_CONNECTION_ID";
/**
+ * Extra key set on a {@link Connection} when it was created via a remote connection service.
+ * For example, if a connection manager requests a remote connection service to create a call
+ * using one of the remote connection service's phone account handle, this extra will be set so
+ * that Telecom knows that the wrapped remote connection originated in a remote connection
+ * service. We stash this in the extras since connection managers will typically copy the
+ * extras from a {@link RemoteConnection} to a {@link Connection} (there is ultimately not
+ * other way to relate a {@link RemoteConnection} to a {@link Connection}.
+ * @hide
+ */
+ public static final String EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE =
+ "android.telecom.extra.REMOTE_PHONE_ACCOUNT_HANDLE";
+
+ /**
+ * Extra key set from a {@link ConnectionService} when using the remote connection APIs
+ * (e.g. {@link RemoteConnectionService#createRemoteConnection(PhoneAccountHandle,
+ * ConnectionRequest, boolean)}) to create a remote connection. Provides the receiving
+ * {@link ConnectionService} with a means to know the package name of the requesting
+ * {@link ConnectionService} so that {@link #EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE} can be set for
+ * better visibility in Telecom of where a connection ultimately originated.
+ * @hide
+ */
+ public static final String EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME =
+ "android.telecom.extra.REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME";
+
+ /**
* Boolean connection extra key set on the extras passed to
* {@link Connection#sendConnectionEvent} which indicates that audio is present
* on the RTT call when the extra value is true.
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 1b60e4820ad0..a716b37f7efd 100755
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1859,9 +1859,25 @@ public abstract class ConnectionService extends Service {
new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONFERENCE"),
request.getAccountHandle());
}
- if (conference.getExtras() != null) {
- conference.getExtras().putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+
+ Bundle extras = request.getExtras();
+ Bundle newExtras = new Bundle();
+ newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+ if (extras != null) {
+ // If the request originated from a remote connection service, we will add some
+ // tracking information that Telecom can use to keep informed of which package
+ // made the remote request, and which remote connection service was used.
+ if (extras.containsKey(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) {
+ newExtras.putString(
+ Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
+ extras.getString(
+ Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME));
+ newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+ request.getAccountHandle());
+ }
}
+ conference.putExtras(newExtras);
+
mConferenceById.put(callId, conference);
mIdByConference.put(conference, callId);
conference.addListener(mConferenceListener);
@@ -1936,6 +1952,30 @@ public abstract class ConnectionService extends Service {
Log.i(this, "createConnection, implementation returned null connection.");
connection = Connection.createFailedConnection(
new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
+ } else {
+ try {
+ Bundle extras = request.getExtras();
+ if (extras != null) {
+ // If the request originated from a remote connection service, we will add some
+ // tracking information that Telecom can use to keep informed of which package
+ // made the remote request, and which remote connection service was used.
+ if (extras.containsKey(
+ Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME)) {
+ Bundle newExtras = new Bundle();
+ newExtras.putString(
+ Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
+ extras.getString(
+ Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME
+ ));
+ newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+ request.getAccountHandle());
+ connection.putExtras(newExtras);
+ }
+ }
+ } catch (UnsupportedOperationException ose) {
+ // Do nothing; if the ConnectionService reported a failure it will be an instance
+ // of an immutable Connection which we cannot edit, so we're out of luck.
+ }
}
boolean isSelfManaged =
diff --git a/telecomm/java/android/telecom/Logging/Session.java b/telecomm/java/android/telecom/Logging/Session.java
index d82e93fac76d..8d3f4e1df8bc 100644
--- a/telecomm/java/android/telecom/Logging/Session.java
+++ b/telecomm/java/android/telecom/Logging/Session.java
@@ -427,7 +427,7 @@ public class Session {
StringBuilder methodName = new StringBuilder();
methodName.append(getFullMethodPath(false /*truncatePath*/));
if (mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
- methodName.append("(InCall package: ");
+ methodName.append("(");
methodName.append(mOwnerInfo);
methodName.append(")");
}
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index cad5b707a146..a0833011715d 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -258,6 +258,9 @@ final class RemoteConnectionService {
// See comments on Connection.EXTRA_ORIGINAL_CONNECTION_ID for more information.
Bundle newExtras = new Bundle();
newExtras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
+ // Track the fact this request was relayed through the remote connection service.
+ newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+ parcel.getPhoneAccount());
conference.putExtras(newExtras);
conference.registerCallback(new RemoteConference.Callback() {
@@ -383,6 +386,11 @@ final class RemoteConnectionService {
RemoteConnection remoteConnection = new RemoteConnection(callId,
mOutgoingConnectionServiceRpc, connection, callingPackage,
callingTargetSdkVersion);
+ // Track that it is via a remote connection.
+ Bundle newExtras = new Bundle();
+ newExtras.putParcelable(Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE,
+ connection.getPhoneAccount());
+ remoteConnection.putExtras(newExtras);
mConnectionById.put(callId, remoteConnection);
remoteConnection.registerCallback(new RemoteConnection.Callback() {
@Override
@@ -535,10 +543,20 @@ final class RemoteConnectionService {
ConnectionRequest request,
boolean isIncoming) {
final String id = UUID.randomUUID().toString();
+ Bundle extras = new Bundle();
+ if (request.getExtras() != null) {
+ extras.putAll(request.getExtras());
+ }
+ // We will set the package name for the originator of the remote request; this lets the
+ // receiving ConnectionService know that the request originated from a remote connection
+ // service so that it can provide tracking information for Telecom.
+ extras.putString(Connection.EXTRA_REMOTE_CONNECTION_ORIGINATING_PACKAGE_NAME,
+ mOurConnectionServiceImpl.getApplicationContext().getOpPackageName());
+
final ConnectionRequest newRequest = new ConnectionRequest.Builder()
.setAccountHandle(request.getAccountHandle())
.setAddress(request.getAddress())
- .setExtras(request.getExtras())
+ .setExtras(extras)
.setVideoState(request.getVideoState())
.setRttPipeFromInCall(request.getRttPipeFromInCall())
.setRttPipeToInCall(request.getRttPipeToInCall())
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 151fb59b7550..ede67dd9fd61 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -49,7 +49,7 @@ import java.util.function.Consumer;
*
* Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager.
*/
-public class ImsRcsManager implements RegistrationManager {
+public class ImsRcsManager {
private static final String TAG = "ImsRcsManager";
/**
@@ -173,11 +173,11 @@ public class ImsRcsManager implements RegistrationManager {
/**
* @hide
*/
- @Override
+ // @Override add back to RegistrationManager interface once public.
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerImsRegistrationCallback(
@NonNull @CallbackExecutor Executor executor,
- @NonNull RegistrationCallback c)
+ @NonNull RegistrationManager.RegistrationCallback c)
throws ImsException {
if (c == null) {
throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
@@ -204,7 +204,7 @@ public class ImsRcsManager implements RegistrationManager {
/**
* @hide
*/
- @Override
+ // @Override add back to RegistrationManager interface once public.
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void unregisterImsRegistrationCallback(
@NonNull RegistrationManager.RegistrationCallback c) {
@@ -228,10 +228,10 @@ public class ImsRcsManager implements RegistrationManager {
/**
* @hide
*/
- @Override
+ // @Override add back to RegistrationManager interface once public.
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
- @NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
+ @NonNull @RegistrationManager.ImsRegistrationState Consumer<Integer> stateCallback) {
if (stateCallback == null) {
throw new IllegalArgumentException("Must include a non-null stateCallback.");
}
@@ -260,7 +260,6 @@ public class ImsRcsManager implements RegistrationManager {
/**
* @hide
*/
- @Override
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
@NonNull @AccessNetworkConstants.TransportType
@@ -347,8 +346,7 @@ public class ImsRcsManager implements RegistrationManager {
* inactive subscription, it will result in a no-op.
* @param c The RCS {@link AvailabilityCallback} to be removed.
* @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback)
- * @throws ImsException if the IMS service is not available when calling this method
- * {@link ImsRcsController#unregisterRcsAvailabilityCallback()}.
+ * @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
* @hide
*/
@@ -390,8 +388,7 @@ public class ImsRcsManager implements RegistrationManager {
* rather the subscription is capable of this service over IMS.
* @see #isAvailable(int)
* @see android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL
- * @throws ImsException if the IMS service is not available when calling this method
- * {@link ImsRcsController#isCapable(int, int)}.
+ * @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
* @hide
*/
@@ -424,9 +421,8 @@ public class ImsRcsManager implements RegistrationManager {
* @return true if the RCS capability is currently available for the associated subscription,
* false otherwise. If the capability is available, IMS is registered and the service is
* currently available over IMS.
- * @see #isCapable(int)
- * @throws ImsException if the IMS service is not available when calling this method
- * {@link ImsRcsController#isAvailable(int, int)}.
+ * @see #isCapable(int, int)
+ * @throws ImsException if the IMS service is not available when calling this method.
* See {@link ImsException#getCode()} for more information on the error codes.
* @hide
*/
diff --git a/telephony/java/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
index ab3fdf4ebb41..2f3897b9bac2 100644
--- a/telephony/java/com/android/internal/telephony/SmsHeader.java
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -23,6 +23,8 @@ import com.android.internal.util.HexDump;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
/**
* SMS user data header, as specified in TS 23.040 9.2.3.24.
@@ -71,6 +73,25 @@ public class SmsHeader {
public static final int PORT_WAP_PUSH = 2948;
public static final int PORT_WAP_WSP = 9200;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SmsHeader smsHeader = (SmsHeader) o;
+ return languageTable == smsHeader.languageTable
+ && languageShiftTable == smsHeader.languageShiftTable
+ && Objects.equals(portAddrs, smsHeader.portAddrs)
+ && Objects.equals(concatRef, smsHeader.concatRef)
+ && Objects.equals(specialSmsMsgList, smsHeader.specialSmsMsgList)
+ && Objects.equals(miscEltList, smsHeader.miscEltList);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(portAddrs, concatRef, specialSmsMsgList, miscEltList, languageTable,
+ languageShiftTable);
+ }
+
public static class PortAddrs {
@UnsupportedAppUsage
public PortAddrs() {
@@ -81,6 +102,21 @@ public class SmsHeader {
@UnsupportedAppUsage
public int origPort;
public boolean areEightBits;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PortAddrs portAddrs = (PortAddrs) o;
+ return destPort == portAddrs.destPort
+ && origPort == portAddrs.origPort
+ && areEightBits == portAddrs.areEightBits;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(destPort, origPort, areEightBits);
+ }
}
public static class ConcatRef {
@@ -95,11 +131,41 @@ public class SmsHeader {
@UnsupportedAppUsage
public int msgCount;
public boolean isEightBits;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ConcatRef concatRef = (ConcatRef) o;
+ return refNumber == concatRef.refNumber
+ && seqNumber == concatRef.seqNumber
+ && msgCount == concatRef.msgCount
+ && isEightBits == concatRef.isEightBits;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(refNumber, seqNumber, msgCount, isEightBits);
+ }
}
public static class SpecialSmsMsg {
public int msgIndType;
public int msgCount;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SpecialSmsMsg that = (SpecialSmsMsg) o;
+ return msgIndType == that.msgIndType
+ && msgCount == that.msgCount;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(msgIndType, msgCount);
+ }
}
/**
@@ -109,6 +175,22 @@ public class SmsHeader {
public static class MiscElt {
public int id;
public byte[] data;
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MiscElt miscElt = (MiscElt) o;
+ return id == miscElt.id
+ && Arrays.equals(data, miscElt.data);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(id);
+ result = 31 * result + Arrays.hashCode(data);
+ return result;
+ }
}
@UnsupportedAppUsage
diff --git a/tests/AutoVerify/app1/Android.bp b/tests/AutoVerify/app1/Android.bp
new file mode 100644
index 000000000000..548519fa653b
--- /dev/null
+++ b/tests/AutoVerify/app1/Android.bp
@@ -0,0 +1,11 @@
+android_app {
+ name: "AutoVerifyTest",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ platform_apis: true,
+ min_sdk_version: "26",
+ target_sdk_version: "26",
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/tests/AutoVerify/app1/AndroidManifest.xml b/tests/AutoVerify/app1/AndroidManifest.xml
new file mode 100644
index 000000000000..d9caad490d82
--- /dev/null
+++ b/tests/AutoVerify/app1/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.autoverify" >
+
+ <uses-sdk android:targetSdkVersion="26" />
+
+ <application
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="explicit.example.com" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/AutoVerify/app1/res/values/strings.xml b/tests/AutoVerify/app1/res/values/strings.xml
new file mode 100644
index 000000000000..e234355041c6
--- /dev/null
+++ b/tests/AutoVerify/app1/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+ <!-- app icon label, do not translate -->
+ <string name="app_name" translatable="false">AutoVerify Test</string>
+</resources>
diff --git a/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
new file mode 100644
index 000000000000..09ef47212622
--- /dev/null
+++ b/tests/AutoVerify/app1/src/com/android/test/autoverify/MainActivity.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/tests/AutoVerify/app2/Android.bp b/tests/AutoVerify/app2/Android.bp
new file mode 100644
index 000000000000..1c6c97bdf350
--- /dev/null
+++ b/tests/AutoVerify/app2/Android.bp
@@ -0,0 +1,11 @@
+android_app {
+ name: "AutoVerifyTest2",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ platform_apis: true,
+ min_sdk_version: "26",
+ target_sdk_version: "26",
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/tests/AutoVerify/app2/AndroidManifest.xml b/tests/AutoVerify/app2/AndroidManifest.xml
new file mode 100644
index 000000000000..a00807883cfc
--- /dev/null
+++ b/tests/AutoVerify/app2/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.autoverify" >
+
+ <uses-sdk android:targetSdkVersion="26" />
+
+ <application
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ <intent-filter android:autoVerify="true">
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="explicit.example.com" />
+ <data android:host="*.wildcard.tld" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/AutoVerify/app2/res/values/strings.xml b/tests/AutoVerify/app2/res/values/strings.xml
new file mode 100644
index 000000000000..e234355041c6
--- /dev/null
+++ b/tests/AutoVerify/app2/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+ <!-- app icon label, do not translate -->
+ <string name="app_name" translatable="false">AutoVerify Test</string>
+</resources>
diff --git a/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java
new file mode 100644
index 000000000000..09ef47212622
--- /dev/null
+++ b/tests/AutoVerify/app2/src/com/android/test/autoverify/MainActivity.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/tests/AutoVerify/app3/Android.bp b/tests/AutoVerify/app3/Android.bp
new file mode 100644
index 000000000000..70a2b77d1000
--- /dev/null
+++ b/tests/AutoVerify/app3/Android.bp
@@ -0,0 +1,11 @@
+android_app {
+ name: "AutoVerifyTest3",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ platform_apis: true,
+ min_sdk_version: "26",
+ target_sdk_version: "26",
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/tests/AutoVerify/app3/AndroidManifest.xml b/tests/AutoVerify/app3/AndroidManifest.xml
new file mode 100644
index 000000000000..efaabc9a38d3
--- /dev/null
+++ b/tests/AutoVerify/app3/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.autoverify" >
+
+ <uses-sdk android:targetSdkVersion="26" />
+
+ <application
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ <!-- does not request autoVerify -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="explicit.example.com" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/AutoVerify/app3/res/values/strings.xml b/tests/AutoVerify/app3/res/values/strings.xml
new file mode 100644
index 000000000000..e234355041c6
--- /dev/null
+++ b/tests/AutoVerify/app3/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+ <!-- app icon label, do not translate -->
+ <string name="app_name" translatable="false">AutoVerify Test</string>
+</resources>
diff --git a/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java
new file mode 100644
index 000000000000..09ef47212622
--- /dev/null
+++ b/tests/AutoVerify/app3/src/com/android/test/autoverify/MainActivity.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/tests/AutoVerify/app4/Android.bp b/tests/AutoVerify/app4/Android.bp
new file mode 100644
index 000000000000..fbdae1181a7a
--- /dev/null
+++ b/tests/AutoVerify/app4/Android.bp
@@ -0,0 +1,11 @@
+android_app {
+ name: "AutoVerifyTest4",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ platform_apis: true,
+ min_sdk_version: "26",
+ target_sdk_version: "26",
+ optimize: {
+ enabled: false,
+ },
+}
diff --git a/tests/AutoVerify/app4/AndroidManifest.xml b/tests/AutoVerify/app4/AndroidManifest.xml
new file mode 100644
index 000000000000..1c975f8336c9
--- /dev/null
+++ b/tests/AutoVerify/app4/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.autoverify" >
+
+ <uses-sdk android:targetSdkVersion="26" />
+
+ <application
+ android:label="@string/app_name" >
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+
+ <!-- intentionally does not autoVerify -->
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <data android:scheme="http" />
+ <data android:scheme="https" />
+ <data android:host="explicit.example.com" />
+ <data android:host="*.wildcard.tld" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/AutoVerify/app4/res/values/strings.xml b/tests/AutoVerify/app4/res/values/strings.xml
new file mode 100644
index 000000000000..e234355041c6
--- /dev/null
+++ b/tests/AutoVerify/app4/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2020 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<resources>
+ <!-- app icon label, do not translate -->
+ <string name="app_name" translatable="false">AutoVerify Test</string>
+</resources>
diff --git a/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java
new file mode 100644
index 000000000000..09ef47212622
--- /dev/null
+++ b/tests/AutoVerify/app4/src/com/android/test/autoverify/MainActivity.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 6c8dc00cb579..7d20d0d09dc2 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -758,6 +758,13 @@ public class WifiManager {
@SystemApi
public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+ /**
+ * Client disconnected for unspecified reason. This could for example be because the AP is being
+ * shut down.
+ * @hide
+ */
+ public static final int SAP_CLIENT_DISCONNECT_REASON_CODE_UNSPECIFIED = 2;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"IFACE_IP_MODE_"}, value = {