summaryrefslogtreecommitdiff
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/keyguard/GradientTextClock.java102
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java39
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java210
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java30
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java114
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java112
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java256
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java360
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java31
-rw-r--r--packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java178
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/BatteryMeterView.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/Utils.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java317
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java812
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java1713
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java824
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt178
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java630
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java301
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java519
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java154
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt178
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java358
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java2723
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java215
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt146
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt164
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java308
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java144
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java668
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java1147
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java1106
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt115
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java (renamed from packages/SystemUI/src/com/android/keyguard/dagger/RootView.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHost.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java177
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java162
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetService.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetail.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java (renamed from packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java)171
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java241
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java428
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java212
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java273
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java81
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java112
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java222
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java247
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/dagger/QSScope.java (renamed from packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt)27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java1144
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java689
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java197
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java597
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java346
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java231
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java160
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java212
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java170
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java197
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/toast/ToastUI.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerService.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ViewController.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java725
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java138
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java70
232 files changed, 8744 insertions, 18173 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/GradientTextClock.java b/packages/SystemUI/src/com/android/keyguard/GradientTextClock.java
new file mode 100644
index 000000000000..7cf1bd0b3e79
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/GradientTextClock.java
@@ -0,0 +1,102 @@
+/*
+ * 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.keyguard;
+
+import android.content.Context;
+import android.graphics.LinearGradient;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.widget.TextClock;
+
+/**
+ * Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
+ * The time's text color is a gradient that changes its colors based on its controller.
+ */
+public class GradientTextClock extends TextClock {
+ private int[] mGradientColors;
+ private float[] mPositions;
+
+ public GradientTextClock(Context context) {
+ this(context, null, 0, 0);
+ }
+
+ public GradientTextClock(Context context, AttributeSet attrs) {
+ this(context, attrs, 0, 0);
+ }
+
+ public GradientTextClock(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public GradientTextClock(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ addOnLayoutChangeListener(mOnLayoutChangeListener);
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ removeOnLayoutChangeListener(mOnLayoutChangeListener);
+ }
+
+ @Override
+ public void refreshTime() {
+ super.refreshTime();
+ }
+
+ @Override
+ public void setFormat12Hour(CharSequence format) {
+ super.setFormat12Hour(FORMAT_12);
+ }
+
+ @Override
+ public void setFormat24Hour(CharSequence format) {
+ super.setFormat24Hour(FORMAT_24);
+ }
+
+ public void setGradientColors(int[] colors) {
+ mGradientColors = colors;
+ updatePaint();
+ }
+
+ public void setColorPositions(float[] positions) {
+ mPositions = positions;
+ }
+
+ private void updatePaint() {
+ getPaint().setShader(
+ new LinearGradient(
+ getX(), getY(), getX(), getMeasuredHeight() + getY(),
+ mGradientColors, mPositions, Shader.TileMode.REPEAT));
+ }
+
+ private final OnLayoutChangeListener mOnLayoutChangeListener =
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ if (bottom != oldBottom || top != oldTop) {
+ updatePaint();
+ }
+ };
+
+ public static final CharSequence FORMAT_12 = "hh\nmm";
+ public static final CharSequence FORMAT_24 = "HH\nmm";
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 4ffd22c73116..5e452666bece 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -81,8 +81,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
abstract void resetState();
@Override
- public void init() {
- super.init();
+ public void onInit() {
mMessageAreaController.init();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 272954df6dd6..c6ee15fcf4e3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -15,6 +15,7 @@ import android.transition.TransitionValues;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -72,6 +73,16 @@ public class KeyguardClockSwitch extends RelativeLayout {
private TextClock mClockViewBold;
/**
+ * Gradient clock for usage when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
+ */
+ private TimeBasedColorsClockController mNewLockscreenClockViewController;
+
+ /**
+ * Frame for clock when mode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL.
+ */
+ private FrameLayout mNewLockscreenClockFrame;
+
+ /**
* Frame for default and custom clock.
*/
private FrameLayout mSmallClockFrame;
@@ -137,23 +148,28 @@ public class KeyguardClockSwitch extends RelativeLayout {
mLockScreenMode = mode;
RelativeLayout.LayoutParams statusAreaLP = (RelativeLayout.LayoutParams)
mKeyguardStatusArea.getLayoutParams();
- RelativeLayout.LayoutParams clockLP = (RelativeLayout.LayoutParams)
- mSmallClockFrame.getLayoutParams();
if (mode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ final int startEndPadding = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 12,
+ getResources().getDisplayMetrics());
+ setPaddingRelative(startEndPadding, 0, startEndPadding, 0);
+ mSmallClockFrame.setVisibility(GONE);
+ mNewLockscreenClockFrame.setVisibility(VISIBLE);
+ mNewLockscreenClockViewController.init();
+
statusAreaLP.removeRule(RelativeLayout.BELOW);
- statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.clock_view);
+ statusAreaLP.addRule(RelativeLayout.LEFT_OF, R.id.new_lockscreen_clock_view);
statusAreaLP.addRule(RelativeLayout.ALIGN_PARENT_START);
-
- clockLP.addRule(RelativeLayout.ALIGN_PARENT_END);
- clockLP.width = ViewGroup.LayoutParams.WRAP_CONTENT;
} else {
+ setPaddingRelative(0, 0, 0, 0);
+ mSmallClockFrame.setVisibility(VISIBLE);
+ mNewLockscreenClockFrame.setVisibility(GONE);
+
statusAreaLP.removeRule(RelativeLayout.LEFT_OF);
statusAreaLP.removeRule(RelativeLayout.ALIGN_PARENT_START);
statusAreaLP.addRule(RelativeLayout.BELOW, R.id.clock_view);
-
- clockLP.removeRule(RelativeLayout.ALIGN_PARENT_END);
- clockLP.width = ViewGroup.LayoutParams.MATCH_PARENT;
}
requestLayout();
@@ -164,6 +180,9 @@ public class KeyguardClockSwitch extends RelativeLayout {
super.onFinishInflate();
mClockView = findViewById(R.id.default_clock_view);
mClockViewBold = findViewById(R.id.default_clock_view_bold);
+ mNewLockscreenClockFrame = findViewById(R.id.new_lockscreen_clock_view);
+ mNewLockscreenClockViewController =
+ new TimeBasedColorsClockController(findViewById(R.id.gradient_clock_view));
mSmallClockFrame = findViewById(R.id.clock_view);
mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
}
@@ -286,6 +305,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
if (mClockPlugin != null) {
mClockPlugin.setDarkAmount(darkAmount);
}
+ mNewLockscreenClockViewController.setDarkAmount(darkAmount);
updateBigClockAlpha();
}
@@ -336,6 +356,7 @@ public class KeyguardClockSwitch extends RelativeLayout {
* Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
*/
public void refresh() {
+ mNewLockscreenClockViewController.refreshTime(System.currentTimeMillis());
mClockView.refreshTime();
mClockViewBold.refreshTime();
if (mClockPlugin != null) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index fe5fcc6fd632..5b89f7f46772 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -17,28 +17,47 @@
package com.android.keyguard;
import android.app.WallpaperManager;
+import android.content.res.Resources;
+import android.text.format.DateFormat;
+import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
+import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.util.ViewController;
+
+import java.util.Locale;
+import java.util.TimeZone;
import javax.inject.Inject;
/**
* Injectable controller for {@link KeyguardClockSwitch}.
*/
-public class KeyguardClockSwitchController {
+public class KeyguardClockSwitchController extends ViewController<KeyguardClockSwitch> {
private static final boolean CUSTOM_CLOCKS_ENABLED = true;
- private final KeyguardClockSwitch mView;
+ private final Resources mResources;
private final StatusBarStateController mStatusBarStateController;
private final SysuiColorExtractor mColorExtractor;
private final ClockManager mClockManager;
private final KeyguardSliceViewController mKeyguardSliceViewController;
+ private final NotificationIconAreaController mNotificationIconAreaController;
+ private FrameLayout mNewLockscreenClockFrame;
+
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@@ -65,51 +84,60 @@ public class KeyguardClockSwitchController {
private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
- private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
- new View.OnAttachStateChangeListener() {
- @Override
- public void onViewAttachedToWindow(View v) {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.addOnClockChangedListener(mClockChangedListener);
- }
- mStatusBarStateController.addCallback(mStateListener);
- mColorExtractor.addOnColorsChangedListener(mColorsListener);
- mView.updateColors(getGradientColors());
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
- if (CUSTOM_CLOCKS_ENABLED) {
- mClockManager.removeOnClockChangedListener(mClockChangedListener);
- }
- mStatusBarStateController.removeCallback(mStateListener);
- mColorExtractor.removeOnColorsChangedListener(mColorsListener);
- mView.setClockPlugin(null, mStatusBarStateController.getState());
- }
- };
-
@Inject
- public KeyguardClockSwitchController(KeyguardClockSwitch keyguardClockSwitch,
+ public KeyguardClockSwitchController(
+ KeyguardClockSwitch keyguardClockSwitch,
+ @Main Resources resources,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor, ClockManager clockManager,
- KeyguardSliceViewController keyguardSliceViewController) {
- mView = keyguardClockSwitch;
+ KeyguardSliceViewController keyguardSliceViewController,
+ NotificationIconAreaController notificationIconAreaController) {
+ super(keyguardClockSwitch);
+ mResources = resources;
mStatusBarStateController = statusBarStateController;
mColorExtractor = colorExtractor;
mClockManager = clockManager;
mKeyguardSliceViewController = keyguardSliceViewController;
+ mNotificationIconAreaController = notificationIconAreaController;
}
/**
* Attach the controller to the view it relates to.
*/
- public void init() {
- if (mView.isAttachedToWindow()) {
- mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+ @Override
+ public void onInit() {
+ mKeyguardSliceViewController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ if (CUSTOM_CLOCKS_ENABLED) {
+ mClockManager.addOnClockChangedListener(mClockChangedListener);
}
- mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ refreshFormat();
+ mStatusBarStateController.addCallback(mStateListener);
+ mColorExtractor.addOnColorsChangedListener(mColorsListener);
+ mView.updateColors(getGradientColors());
+ updateAodIcons();
+ mNewLockscreenClockFrame = mView.findViewById(R.id.new_lockscreen_clock_view);
+ }
- mKeyguardSliceViewController.init();
+ @Override
+ protected void onViewDetached() {
+ if (CUSTOM_CLOCKS_ENABLED) {
+ mClockManager.removeOnClockChangedListener(mClockChangedListener);
+ }
+ mStatusBarStateController.removeCallback(mStateListener);
+ mColorExtractor.removeOnColorsChangedListener(mColorsListener);
+ mView.setClockPlugin(null, mStatusBarStateController.getState());
+ }
+
+ /**
+ * Updates clock's text
+ */
+ public void onDensityOrFontScaleChanged() {
+ mView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ mResources.getDimensionPixelSize(R.dimen.widget_big_font_size));
}
/**
@@ -119,6 +147,91 @@ public class KeyguardClockSwitchController {
mView.setBigClockContainer(bigClockContainer, mStatusBarStateController.getState());
}
+ /**
+ * Set whether or not the lock screen is showing notifications.
+ */
+ public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
+ mView.setHasVisibleNotifications(hasVisibleNotifications);
+ }
+
+ /**
+ * If we're presenting a custom clock of just the default one.
+ */
+ public boolean hasCustomClock() {
+ return mView.hasCustomClock();
+ }
+
+ /**
+ * Get the clock text size.
+ */
+ public float getClockTextSize() {
+ return mView.getTextSize();
+ }
+
+ /**
+ * Returns the preferred Y position of the clock.
+ *
+ * @param totalHeight The height available to position the clock.
+ * @return Y position of clock.
+ */
+ public int getClockPreferredY(int totalHeight) {
+ return mView.getPreferredY(totalHeight);
+ }
+
+ /**
+ * Refresh clock. Called in response to TIME_TICK broadcasts.
+ */
+ void refresh() {
+ mView.refresh();
+ }
+
+ /**
+ * Update position of the view, with optional animation. Move the slice view and the clock
+ * slightly towards the center in order to prevent burn-in. Y positioning occurs at the
+ * view parent level.
+ */
+ void updatePosition(int x, AnimationProperties props, boolean animate) {
+ x = Math.abs(x);
+ if (mNewLockscreenClockFrame != null) {
+ PropertyAnimator.setProperty(mNewLockscreenClockFrame, AnimatableProperty.TRANSLATION_X,
+ -x, props, animate);
+ }
+ mKeyguardSliceViewController.updatePosition(x, props, animate);
+ mNotificationIconAreaController.updatePosition(x, props, animate);
+ }
+
+ /**
+ * Update lockscreen mode that may change clock display.
+ */
+ void updateLockScreenMode(int mode) {
+ mLockScreenMode = mode;
+ mView.updateLockScreenMode(mLockScreenMode);
+ updateAodIcons();
+ }
+
+ void updateTimeZone(TimeZone timeZone) {
+ mView.onTimeZoneChanged(timeZone);
+ }
+
+ void refreshFormat() {
+ Patterns.update(mResources);
+ mView.setFormat12Hour(Patterns.sClockView12);
+ mView.setFormat24Hour(Patterns.sClockView24);
+ }
+
+ private void updateAodIcons() {
+ NotificationIconContainer nic = (NotificationIconContainer)
+ mView.findViewById(
+ com.android.systemui.R.id.left_aligned_notification_icon_container);
+
+ if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ // alt icon area is set in KeyguardClockSwitchController
+ mNotificationIconAreaController.setupAodIcons(nic, mLockScreenMode);
+ } else {
+ nic.setVisibility(View.GONE);
+ }
+ }
+
private void setClockPlugin(ClockPlugin plugin) {
mView.setClockPlugin(plugin, mStatusBarStateController.getState());
}
@@ -126,4 +239,35 @@ public class KeyguardClockSwitchController {
private ColorExtractor.GradientColors getGradientColors() {
return mColorExtractor.getColors(WallpaperManager.FLAG_LOCK);
}
+
+ // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
+ // This is an optimization to ensure we only recompute the patterns when the inputs change.
+ private static final class Patterns {
+ static String sClockView12;
+ static String sClockView24;
+ static String sCacheKey;
+
+ static void update(Resources res) {
+ final Locale locale = Locale.getDefault();
+ final String clockView12Skel = res.getString(R.string.clock_12hr_format);
+ final String clockView24Skel = res.getString(R.string.clock_24hr_format);
+ final String key = locale.toString() + clockView12Skel + clockView24Skel;
+ if (key.equals(sCacheKey)) return;
+
+ sClockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
+ // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
+ // format. The following code removes the AM/PM indicator if we didn't want it.
+ if (!clockView12Skel.contains("a")) {
+ sClockView12 = sClockView12.replaceAll("a", "").trim();
+ }
+
+ sClockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
+
+ // Use fancy colon.
+ sClockView24 = sClockView24.replace(':', '\uee01');
+ sClockView12 = sClockView12.replace(':', '\uee01');
+
+ sCacheKey = key;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
index 36d5543f1c01..901a7360f311 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardDisplayManager.java
@@ -20,7 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import android.app.Presentation;
import android.content.Context;
import android.graphics.Color;
-import android.graphics.Point;
+import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteInfo;
@@ -127,7 +127,7 @@ public class KeyguardDisplayManager {
Presentation presentation = mPresentations.get(displayId);
if (presentation == null) {
final Presentation newPresentation = new KeyguardPresentation(mContext, display,
- mKeyguardStatusViewComponentFactory, LayoutInflater.from(mContext));
+ mKeyguardStatusViewComponentFactory);
newPresentation.setOnDismissListener(dialog -> {
if (newPresentation.equals(mPresentations.get(displayId))) {
mPresentations.remove(displayId);
@@ -245,7 +245,6 @@ public class KeyguardDisplayManager {
private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
- private final LayoutInflater mLayoutInflater;
private KeyguardClockSwitchController mKeyguardClockSwitchController;
private View mClock;
private int mUsableWidth;
@@ -264,18 +263,16 @@ public class KeyguardDisplayManager {
};
KeyguardPresentation(Context context, Display display,
- KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
- LayoutInflater layoutInflater) {
- super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
+ KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
+ super(context, display, R.style.Theme_SystemUI_KeyguardPresentation,
+ WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
- mLayoutInflater = layoutInflater;
- getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
setCancelable(false);
}
@Override
public void cancel() {
- // Do not allow anything to cancel KeyguardPresetation except KeyguardDisplayManager.
+ // Do not allow anything to cancel KeyguardPresentation except KeyguardDisplayManager.
}
@Override
@@ -287,14 +284,15 @@ public class KeyguardDisplayManager {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- Point p = new Point();
- getDisplay().getSize(p);
- mUsableWidth = VIDEO_SAFE_REGION * p.x/100;
- mUsableHeight = VIDEO_SAFE_REGION * p.y/100;
- mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
- mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
+ final Rect bounds = getWindow().getWindowManager().getMaximumWindowMetrics()
+ .getBounds();
+ mUsableWidth = VIDEO_SAFE_REGION * bounds.width() / 100;
+ mUsableHeight = VIDEO_SAFE_REGION * bounds.height() / 100;
+ mMarginLeft = (100 - VIDEO_SAFE_REGION) * bounds.width() / 200;
+ mMarginTop = (100 - VIDEO_SAFE_REGION) * bounds.height() / 200;
- setContentView(mLayoutInflater.inflate(R.layout.keyguard_presentation, null));
+ setContentView(LayoutInflater.from(getContext())
+ .inflate(R.layout.keyguard_presentation, null));
// Logic to make the lock screen fullscreen
getWindow().getDecorView().setSystemUiVisibility(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 351369c51364..3fafa5c606bd 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -178,8 +178,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
}
/** Initialize the Controller. */
- public void init() {
- super.init();
+ public void onInit() {
mKeyguardSecurityContainerController.init();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index fbc0a3163817..db3d616fbcda 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -191,8 +191,8 @@ public class KeyguardPatternViewController
}
@Override
- public void init() {
- super.init();
+ public void onInit() {
+ super.onInit();
mMessageAreaController.init();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java
index 5c125fcc95cb..4e375c2d1227 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardRootViewController.java
@@ -19,7 +19,7 @@ package com.android.keyguard;
import android.view.ViewGroup;
import com.android.keyguard.dagger.KeyguardBouncerScope;
-import com.android.keyguard.dagger.RootView;
+import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.util.ViewController;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 1c23605a8516..9a511502b475 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -169,8 +169,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
}
@Override
- public void init() {
- super.init();
+ public void onInit() {
mSecurityViewFlipperController.init();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index a479bca56c2a..a9c06edf46cc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -33,9 +33,11 @@ import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.util.AttributeSet;
import android.util.TypedValue;
+import android.view.Gravity;
import android.view.View;
import android.view.animation.Animation;
import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.slice.SliceItem;
@@ -55,8 +57,10 @@ import com.android.systemui.util.wakelock.KeepAwakeAnimationListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* View visible under the clock on the lock screen and AoD.
@@ -86,6 +90,8 @@ public class KeyguardSliceView extends LinearLayout {
private float mRowWithHeaderTextSize;
private View.OnClickListener mOnClickListener;
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+
public KeyguardSliceView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -142,6 +148,40 @@ public class KeyguardSliceView extends LinearLayout {
}
}
+ /**
+ * Updates the lockscreen mode which may change the layout of the keyguard slice view.
+ */
+ public void updateLockScreenMode(int mode) {
+ mLockScreenMode = mode;
+ if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ // add top padding to better align with top of clock
+ final int topPadding = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 20,
+ getResources().getDisplayMetrics());
+ mTitle.setPaddingRelative(0, topPadding, 0, 0);
+ mTitle.setGravity(Gravity.START);
+ setGravity(Gravity.START);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
+ lp.removeRule(RelativeLayout.CENTER_HORIZONTAL);
+ setLayoutParams(lp);
+ } else {
+ final int horizontalPaddingDpValue = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ 44,
+ getResources().getDisplayMetrics()
+ );
+ mTitle.setPaddingRelative(horizontalPaddingDpValue, 0, horizontalPaddingDpValue, 0);
+ mTitle.setGravity(Gravity.CENTER_HORIZONTAL);
+ setGravity(Gravity.CENTER_HORIZONTAL);
+ RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) getLayoutParams();
+ lp.addRule(RelativeLayout.CENTER_HORIZONTAL);
+ setLayoutParams(lp);
+ }
+ mRow.setLockscreenMode(mode);
+ requestLayout();
+ }
+
Map<View, PendingIntent> showSlice(RowContent header, List<SliceContent> subItems) {
Trace.beginSection("KeyguardSliceView#showSlice");
mHasHeader = header != null;
@@ -166,6 +206,8 @@ public class KeyguardSliceView extends LinearLayout {
final int startIndex = mHasHeader ? 1 : 0; // First item is header; skip it
mRow.setVisibility(subItemsCount > 0 ? VISIBLE : GONE);
LinearLayout.LayoutParams layoutParams = (LayoutParams) mRow.getLayoutParams();
+ layoutParams.gravity = mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL
+ ? Gravity.START : Gravity.CENTER;
layoutParams.topMargin = mHasHeader ? mRowWithHeaderPadding : mRowPadding;
mRow.setLayoutParams(layoutParams);
@@ -282,6 +324,7 @@ public class KeyguardSliceView extends LinearLayout {
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mHasHeader: " + mHasHeader);
+ pw.println(" mLockScreenMode: " + mLockScreenMode);
}
@Override
@@ -291,6 +334,8 @@ public class KeyguardSliceView extends LinearLayout {
}
public static class Row extends LinearLayout {
+ private Set<KeyguardSliceTextView> mKeyguardSliceTextViewSet = new HashSet();
+ private int mLockScreenModeRow = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
/**
* This view is visible in AOD, which means that the device will sleep if we
@@ -361,12 +406,18 @@ public class KeyguardSliceView extends LinearLayout {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int childCount = getChildCount();
+
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child instanceof KeyguardSliceTextView) {
- ((KeyguardSliceTextView) child).setMaxWidth(width / 3);
+ if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ ((KeyguardSliceTextView) child).setMaxWidth(Integer.MAX_VALUE);
+ } else {
+ ((KeyguardSliceTextView) child).setMaxWidth(width / 3);
+ }
}
}
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@@ -384,6 +435,42 @@ public class KeyguardSliceView extends LinearLayout {
public boolean hasOverlappingRendering() {
return false;
}
+
+ @Override
+ public void addView(View view, int index) {
+ super.addView(view, index);
+
+ if (view instanceof KeyguardSliceTextView) {
+ ((KeyguardSliceTextView) view).setLockScreenMode(mLockScreenModeRow);
+ mKeyguardSliceTextViewSet.add((KeyguardSliceTextView) view);
+ }
+ }
+
+ @Override
+ public void removeView(View view) {
+ super.removeView(view);
+ if (view instanceof KeyguardSliceTextView) {
+ mKeyguardSliceTextViewSet.remove((KeyguardSliceTextView) view);
+ }
+ }
+
+ /**
+ * Updates the lockscreen mode which may change the layout of this view.
+ */
+ public void setLockscreenMode(int mode) {
+ mLockScreenModeRow = mode;
+ if (mLockScreenModeRow == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ setOrientation(LinearLayout.VERTICAL);
+ setGravity(Gravity.START);
+ } else {
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.CENTER);
+ }
+
+ for (KeyguardSliceTextView textView : mKeyguardSliceTextViewSet) {
+ textView.setLockScreenMode(mLockScreenModeRow);
+ }
+ }
}
/**
@@ -392,6 +479,7 @@ public class KeyguardSliceView extends LinearLayout {
@VisibleForTesting
static class KeyguardSliceTextView extends TextView implements
ConfigurationController.ConfigurationListener {
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
@StyleRes
private static int sStyleId = R.style.TextAppearance_Keyguard_Secondary;
@@ -432,9 +520,16 @@ public class KeyguardSliceView extends LinearLayout {
private void updatePadding() {
boolean hasText = !TextUtils.isEmpty(getText());
- int horizontalPadding = (int) getContext().getResources()
+ int padding = (int) getContext().getResources()
.getDimension(R.dimen.widget_horizontal_padding) / 2;
- setPadding(horizontalPadding, 0, horizontalPadding * (hasText ? 1 : -1), 0);
+ if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ // orientation is vertical, so add padding to top & bottom
+ setPadding(0, padding, 0, padding * (hasText ? 1 : -1));
+ } else {
+ // oreintation is horizontal, so add padding to left & right
+ setPadding(padding, 0, padding * (hasText ? 1 : -1), 0);
+ }
+
setCompoundDrawablePadding((int) mContext.getResources()
.getDimension(R.dimen.widget_icon_padding));
}
@@ -461,5 +556,18 @@ public class KeyguardSliceView extends LinearLayout {
}
}
}
+
+ /**
+ * Updates the lockscreen mode which may change the layout of this view.
+ */
+ public void setLockScreenMode(int mode) {
+ mLockScreenMode = mode;
+ if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ setGravity(Gravity.START);
+ } else {
+ setGravity(Gravity.CENTER);
+ }
+ updatePadding();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
index 2470b958a85f..02b18b28a5ea 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceViewController.java
@@ -42,8 +42,12 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.ViewController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -55,11 +59,10 @@ import javax.inject.Inject;
/** Controller for a {@link KeyguardSliceView}. */
@KeyguardStatusViewScope
-public class KeyguardSliceViewController implements Dumpable {
+public class KeyguardSliceViewController extends ViewController<KeyguardSliceView> implements
+ Dumpable {
private static final String TAG = "KeyguardSliceViewCtrl";
- private final KeyguardSliceView mView;
- private final KeyguardStatusView mKeyguardStatusView;
private final ActivityStarter mActivityStarter;
private final ConfigurationController mConfigurationController;
private final TunerService mTunerService;
@@ -69,41 +72,7 @@ public class KeyguardSliceViewController implements Dumpable {
private Uri mKeyguardSliceUri;
private Slice mSlice;
private Map<View, PendingIntent> mClickActions;
-
- private final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
- new View.OnAttachStateChangeListener() {
-
- @Override
- public void onViewAttachedToWindow(View v) {
-
- Display display = mView.getDisplay();
- if (display != null) {
- mDisplayId = display.getDisplayId();
- }
- mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI);
- // Make sure we always have the most current slice
- if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) {
- mLiveData.observeForever(mObserver);
- }
- mConfigurationController.addCallback(mConfigurationListener);
- mDumpManager.registerDumpable(
- TAG + "@" + Integer.toHexString(
- KeyguardSliceViewController.this.hashCode()),
- KeyguardSliceViewController.this);
- }
-
- @Override
- public void onViewDetachedFromWindow(View v) {
-
- // TODO(b/117344873) Remove below work around after this issue be fixed.
- if (mDisplayId == DEFAULT_DISPLAY) {
- mLiveData.removeObserver(mObserver);
- }
- mTunerService.removeTunable(mTunable);
- mConfigurationController.removeCallback(mConfigurationListener);
- mDumpManager.unregisterDumpable(TAG);
- }
- };
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
TunerService.Tunable mTunable = (key, newValue) -> setupUri(newValue);
@@ -134,27 +103,57 @@ public class KeyguardSliceViewController implements Dumpable {
};
@Inject
- public KeyguardSliceViewController(KeyguardSliceView keyguardSliceView,
- KeyguardStatusView keyguardStatusView, ActivityStarter activityStarter,
- ConfigurationController configurationController, TunerService tunerService,
+ public KeyguardSliceViewController(
+ KeyguardSliceView keyguardSliceView,
+ ActivityStarter activityStarter,
+ ConfigurationController configurationController,
+ TunerService tunerService,
DumpManager dumpManager) {
- mView = keyguardSliceView;
- mKeyguardStatusView = keyguardStatusView;
+ super(keyguardSliceView);
mActivityStarter = activityStarter;
mConfigurationController = configurationController;
mTunerService = tunerService;
mDumpManager = dumpManager;
}
- /** Initialize the controller. */
- public void init() {
- if (mView.isAttachedToWindow()) {
- mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
+ @Override
+ protected void onViewAttached() {
+ Display display = mView.getDisplay();
+ if (display != null) {
+ mDisplayId = display.getDisplayId();
+ }
+ mTunerService.addTunable(mTunable, Settings.Secure.KEYGUARD_SLICE_URI);
+ // Make sure we always have the most current slice
+ if (mDisplayId == DEFAULT_DISPLAY && mLiveData != null) {
+ mLiveData.observeForever(mObserver);
+ }
+ mConfigurationController.addCallback(mConfigurationListener);
+ mDumpManager.registerDumpable(
+ TAG + "@" + Integer.toHexString(
+ KeyguardSliceViewController.this.hashCode()),
+ KeyguardSliceViewController.this);
+ mView.updateLockScreenMode(mLockScreenMode);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ // TODO(b/117344873) Remove below work around after this issue be fixed.
+ if (mDisplayId == DEFAULT_DISPLAY) {
+ mLiveData.removeObserver(mObserver);
}
- mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- mView.setOnClickListener(mOnClickListener);
- // TODO: remove the line below.
- mKeyguardStatusView.setKeyguardSliceViewController(this);
+ mTunerService.removeTunable(mTunable);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mDumpManager.unregisterDumpable(
+ TAG + "@" + Integer.toHexString(
+ KeyguardSliceViewController.this.hashCode()));
+ }
+
+ /**
+ * Updates the lockscreen mode which may change the layout of the keyguard slice view.
+ */
+ public void updateLockScreenMode(int mode) {
+ mLockScreenMode = mode;
+ mView.updateLockScreenMode(mLockScreenMode);
}
/**
@@ -203,6 +202,13 @@ public class KeyguardSliceViewController implements Dumpable {
Trace.endSection();
}
+ /**
+ * Update position of the view, with optional animation
+ */
+ void updatePosition(int x, AnimationProperties props, boolean animate) {
+ PropertyAnimator.setProperty(mView, AnimatableProperty.TRANSLATION_X, x, props, animate);
+ }
+
void showSlice(Slice slice) {
Trace.beginSection("KeyguardSliceViewController#showSlice");
if (slice == null) {
@@ -228,12 +234,10 @@ public class KeyguardSliceViewController implements Dumpable {
Trace.endSection();
}
-
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
pw.println(" mSlice: " + mSlice);
pw.println(" mClickActions: " + mClickActions);
-
- mKeyguardStatusView.dump(fd, pw, args);
+ pw.println(" mLockScreenMode: " + mLockScreenMode);
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 9ef2def04ec1..2036b3321bda 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,16 +19,13 @@ package com.android.keyguard;
import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
-import android.content.res.Resources;
import android.graphics.Color;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
-import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Slog;
import android.util.TypedValue;
import android.view.View;
import android.widget.GridLayout;
@@ -39,15 +36,18 @@ import androidx.core.graphics.ColorUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.Locale;
-import java.util.TimeZone;
-public class KeyguardStatusView extends GridLayout implements
- ConfigurationController.ConfigurationListener {
+/**
+ * View consisting of:
+ * - keyguard clock
+ * - logout button (on certain managed devices)
+ * - owner information (if set)
+ * - notification icons (shown on AOD)
+ */
+public class KeyguardStatusView extends GridLayout {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardStatusView";
private static final int MARQUEE_DELAY_MS = 2000;
@@ -62,9 +62,7 @@ public class KeyguardStatusView extends GridLayout implements
private View mNotificationIcons;
private Runnable mPendingMarqueeStart;
private Handler mHandler;
- private KeyguardSliceViewController mKeyguardSliceViewController;
- private boolean mPulsing;
private float mDarkAmount = 0;
private int mTextColor;
@@ -76,56 +74,6 @@ public class KeyguardStatusView extends GridLayout implements
private int mIconTopMarginWithHeader;
private boolean mShowingHeader;
- private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
-
- @Override
- public void onLockScreenModeChanged(int mode) {
- updateLockScreenMode(mode);
- }
-
- @Override
- public void onTimeChanged() {
- refreshTime();
- }
-
- @Override
- public void onTimeZoneChanged(TimeZone timeZone) {
- updateTimeZone(timeZone);
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (showing) {
- if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
- refreshTime();
- updateOwnerInfo();
- updateLogoutView();
- }
- }
-
- @Override
- public void onStartedWakingUp() {
- setEnableMarquee(true);
- }
-
- @Override
- public void onFinishedGoingToSleep(int why) {
- setEnableMarquee(false);
- }
-
- @Override
- public void onUserSwitchComplete(int userId) {
- refreshFormat();
- updateOwnerInfo();
- updateLogoutView();
- }
-
- @Override
- public void onLogoutEnabledChanged() {
- updateLogoutView();
- }
- };
-
public KeyguardStatusView(Context context) {
this(context, null, 0);
}
@@ -142,21 +90,7 @@ public class KeyguardStatusView extends GridLayout implements
onDensityOrFontScaleChanged();
}
- /**
- * If we're presenting a custom clock of just the default one.
- */
- public boolean hasCustomClock() {
- return mClockView.hasCustomClock();
- }
-
- /**
- * Set whether or not the lock screen is showing notifications.
- */
- public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
- mClockView.setHasVisibleNotifications(hasVisibleNotifications);
- }
-
- private void setEnableMarquee(boolean enabled) {
+ void setEnableMarquee(boolean enabled) {
if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable"));
if (enabled) {
if (mPendingMarqueeStart == null) {
@@ -203,7 +137,6 @@ public class KeyguardStatusView extends GridLayout implements
boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
- refreshFormat();
updateOwnerInfo();
updateLogoutView();
updateDark();
@@ -238,64 +171,14 @@ public class KeyguardStatusView extends GridLayout implements
layoutOwnerInfo();
}
- @Override
- public void onDensityOrFontScaleChanged() {
- if (mClockView != null) {
- mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.widget_big_font_size));
- }
- if (mOwnerInfo != null) {
- mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
- }
- loadBottomMargin();
- }
-
- public void dozeTimeTick() {
- refreshTime();
- mKeyguardSliceViewController.refresh();
- }
-
- private void refreshTime() {
- mClockView.refresh();
- }
-
- private void updateLockScreenMode(int mode) {
- mClockView.updateLockScreenMode(mode);
- }
-
- private void updateTimeZone(TimeZone timeZone) {
- mClockView.onTimeZoneChanged(timeZone);
- }
-
- private void refreshFormat() {
- Patterns.update(mContext);
- mClockView.setFormat12Hour(Patterns.clockView12);
- mClockView.setFormat24Hour(Patterns.clockView24);
- }
-
- public int getLogoutButtonHeight() {
+ int getLogoutButtonHeight() {
if (mLogoutView == null) {
return 0;
}
return mLogoutView.getVisibility() == VISIBLE ? mLogoutView.getHeight() : 0;
}
- public float getClockTextSize() {
- return mClockView.getTextSize();
- }
-
- /**
- * Returns the preferred Y position of the clock.
- *
- * @param totalHeight The height available to position the clock.
- * @return Y position of clock.
- */
- public int getClockPreferredY(int totalHeight) {
- return mClockView.getPreferredY(totalHeight);
- }
-
- private void updateLogoutView() {
+ void updateLogoutView() {
if (mLogoutView == null) {
return;
}
@@ -305,7 +188,16 @@ public class KeyguardStatusView extends GridLayout implements
com.android.internal.R.string.global_action_logout));
}
- private void updateOwnerInfo() {
+ void onDensityOrFontScaleChanged() {
+ if (mOwnerInfo != null) {
+ mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.widget_label_font_size));
+ loadBottomMargin();
+ }
+ }
+
+ void updateOwnerInfo() {
if (mOwnerInfo == null) return;
String info = mLockPatternUtils.getDeviceOwnerInfo();
if (info == null) {
@@ -320,30 +212,36 @@ public class KeyguardStatusView extends GridLayout implements
updateDark();
}
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
- Dependency.get(ConfigurationController.class).addCallback(this);
+ void setDarkAmount(float darkAmount) {
+ if (mDarkAmount == darkAmount) {
+ return;
+ }
+ mDarkAmount = darkAmount;
+ mClockView.setDarkAmount(darkAmount);
+ updateDark();
}
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
- Dependency.get(ConfigurationController.class).removeCallback(this);
- }
+ void updateDark() {
+ boolean dark = mDarkAmount == 1;
+ if (mLogoutView != null) {
+ mLogoutView.setAlpha(dark ? 0 : 1);
+ }
- @Override
- public void onLocaleListChanged() {
- refreshFormat();
+ if (mOwnerInfo != null) {
+ boolean hasText = !TextUtils.isEmpty(mOwnerInfo.getText());
+ mOwnerInfo.setVisibility(hasText ? VISIBLE : GONE);
+ layoutOwnerInfo();
+ }
+
+ final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
+ mKeyguardSlice.setDarkAmount(mDarkAmount);
+ mClockView.setTextColor(blendedTextColor);
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("KeyguardStatusView:");
pw.println(" mOwnerInfo: " + (mOwnerInfo == null
? "null" : mOwnerInfo.getVisibility() == VISIBLE));
- pw.println(" mPulsing: " + mPulsing);
pw.println(" mDarkAmount: " + mDarkAmount);
pw.println(" mTextColor: " + Integer.toHexString(mTextColor));
if (mLogoutView != null) {
@@ -363,64 +261,6 @@ public class KeyguardStatusView extends GridLayout implements
R.dimen.widget_vertical_padding_with_header);
}
- // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
- // This is an optimization to ensure we only recompute the patterns when the inputs change.
- private static final class Patterns {
- static String clockView12;
- static String clockView24;
- static String cacheKey;
-
- static void update(Context context) {
- final Locale locale = Locale.getDefault();
- final Resources res = context.getResources();
- final String clockView12Skel = res.getString(R.string.clock_12hr_format);
- final String clockView24Skel = res.getString(R.string.clock_24hr_format);
- final String key = locale.toString() + clockView12Skel + clockView24Skel;
- if (key.equals(cacheKey)) return;
-
- clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
- // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
- // format. The following code removes the AM/PM indicator if we didn't want it.
- if (!clockView12Skel.contains("a")) {
- clockView12 = clockView12.replaceAll("a", "").trim();
- }
-
- clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
-
- // Use fancy colon.
- clockView24 = clockView24.replace(':', '\uee01');
- clockView12 = clockView12.replace(':', '\uee01');
-
- cacheKey = key;
- }
- }
-
- public void setDarkAmount(float darkAmount) {
- if (mDarkAmount == darkAmount) {
- return;
- }
- mDarkAmount = darkAmount;
- mClockView.setDarkAmount(darkAmount);
- updateDark();
- }
-
- private void updateDark() {
- boolean dark = mDarkAmount == 1;
- if (mLogoutView != null) {
- mLogoutView.setAlpha(dark ? 0 : 1);
- }
-
- if (mOwnerInfo != null) {
- boolean hasText = !TextUtils.isEmpty(mOwnerInfo.getText());
- mOwnerInfo.setVisibility(hasText ? VISIBLE : GONE);
- layoutOwnerInfo();
- }
-
- final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
- mKeyguardSlice.setDarkAmount(mDarkAmount);
- mClockView.setTextColor(blendedTextColor);
- }
-
private void layoutOwnerInfo() {
if (mOwnerInfo != null && mOwnerInfo.getVisibility() != GONE) {
// Animate owner info during wake-up transition
@@ -442,13 +282,6 @@ public class KeyguardStatusView extends GridLayout implements
}
}
- public void setPulsing(boolean pulsing) {
- if (mPulsing == pulsing) {
- return;
- }
- mPulsing = pulsing;
- }
-
private boolean shouldShowLogout() {
return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
&& KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
@@ -463,9 +296,4 @@ public class KeyguardStatusView extends GridLayout implements
Log.e(TAG, "Failed to logout user", re);
}
}
-
- // TODO: remove this method when a controller is available.
- void setKeyguardSliceViewController(KeyguardSliceViewController keyguardSliceViewController) {
- mKeyguardSliceViewController = keyguardSliceViewController;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
new file mode 100644
index 000000000000..cc7b8322d190
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -0,0 +1,360 @@
+/*
+ * 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.keyguard;
+
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+
+import android.util.Slog;
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
+
+import java.util.TimeZone;
+
+import javax.inject.Inject;
+
+/**
+ * Injectable controller for {@link KeyguardStatusView}.
+ */
+public class KeyguardStatusViewController extends ViewController<KeyguardStatusView> {
+ private static final boolean DEBUG = KeyguardConstants.DEBUG;
+ private static final String TAG = "KeyguardStatusViewController";
+
+ private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
+ private final KeyguardSliceViewController mKeyguardSliceViewController;
+ private final KeyguardClockSwitchController mKeyguardClockSwitchController;
+ private final KeyguardStateController mKeyguardStateController;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final ConfigurationController mConfigurationController;
+ private final NotificationIconAreaController mNotificationIconAreaController;
+
+ private boolean mKeyguardStatusViewAnimating;
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
+
+ @Inject
+ public KeyguardStatusViewController(
+ KeyguardStatusView keyguardStatusView,
+ KeyguardSliceViewController keyguardSliceViewController,
+ KeyguardClockSwitchController keyguardClockSwitchController,
+ KeyguardStateController keyguardStateController,
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ ConfigurationController configurationController,
+ NotificationIconAreaController notificationIconAreaController) {
+ super(keyguardStatusView);
+ mKeyguardSliceViewController = keyguardSliceViewController;
+ mKeyguardClockSwitchController = keyguardClockSwitchController;
+ mKeyguardStateController = keyguardStateController;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mConfigurationController = configurationController;
+ mNotificationIconAreaController = notificationIconAreaController;
+ }
+
+ @Override
+ public void onInit() {
+ mKeyguardClockSwitchController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
+ mConfigurationController.addCallback(mConfigurationListener);
+ updateAodIcons();
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ }
+
+ /**
+ * Updates views on doze time tick.
+ */
+ public void dozeTimeTick() {
+ refreshTime();
+ mKeyguardSliceViewController.refresh();
+ }
+
+ /**
+ * The amount we're in doze.
+ */
+ public void setDarkAmount(float darkAmount) {
+ mView.setDarkAmount(darkAmount);
+ }
+
+ /**
+ * Set whether or not the lock screen is showing notifications.
+ */
+ public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
+ mKeyguardClockSwitchController.setHasVisibleNotifications(hasVisibleNotifications);
+ }
+
+ /**
+ * If we're presenting a custom clock of just the default one.
+ */
+ public boolean hasCustomClock() {
+ return mKeyguardClockSwitchController.hasCustomClock();
+ }
+
+ /**
+ * Get the height of the logout button.
+ */
+ public int getLogoutButtonHeight() {
+ return mView.getLogoutButtonHeight();
+ }
+
+ /**
+ * Set keyguard status view alpha.
+ */
+ public void setAlpha(float alpha) {
+ if (!mKeyguardStatusViewAnimating) {
+ mView.setAlpha(alpha);
+ }
+ }
+
+ /**
+ * Set pivot x.
+ */
+ public void setPivotX(float pivot) {
+ mView.setPivotX(pivot);
+ }
+
+ /**
+ * Set pivot y.
+ */
+ public void setPivotY(float pivot) {
+ mView.setPivotY(pivot);
+ }
+
+ /**
+ * Get the clock text size.
+ */
+ public float getClockTextSize() {
+ return mKeyguardClockSwitchController.getClockTextSize();
+ }
+
+ /**
+ * Returns the preferred Y position of the clock.
+ *
+ * @param totalHeight The height available to position the clock.
+ * @return Y position of clock.
+ */
+ public int getClockPreferredY(int totalHeight) {
+ return mKeyguardClockSwitchController.getClockPreferredY(totalHeight);
+ }
+
+ /**
+ * Get the height of the keyguard status view.
+ */
+ public int getHeight() {
+ return mView.getHeight();
+ }
+
+ /**
+ * Set whether the view accessibility importance mode.
+ */
+ public void setStatusAccessibilityImportance(int mode) {
+ mView.setImportantForAccessibility(mode);
+ }
+
+ /**
+ * Update position of the view with an optional animation
+ */
+ public void updatePosition(int x, int y, boolean animate) {
+ PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES,
+ animate);
+
+ if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_LAYOUT_1) {
+ // reset any prior movement
+ PropertyAnimator.setProperty(mView, AnimatableProperty.X, 0,
+ CLOCK_ANIMATION_PROPERTIES, animate);
+
+ mKeyguardClockSwitchController.updatePosition(x, CLOCK_ANIMATION_PROPERTIES, animate);
+ } else {
+ // reset any prior movement
+ mKeyguardClockSwitchController.updatePosition(0, CLOCK_ANIMATION_PROPERTIES, animate);
+
+ PropertyAnimator.setProperty(mView, AnimatableProperty.X, x,
+ CLOCK_ANIMATION_PROPERTIES, animate);
+ }
+ }
+
+ /**
+ * Set the visibility of the keyguard status view based on some new state.
+ */
+ public void setKeyguardStatusViewVisibility(
+ int statusBarState,
+ boolean keyguardFadingAway,
+ boolean goingToFullShade,
+ int oldStatusBarState) {
+ mView.animate().cancel();
+ mKeyguardStatusViewAnimating = false;
+ if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD
+ && statusBarState != KEYGUARD) || goingToFullShade) {
+ mKeyguardStatusViewAnimating = true;
+ mView.animate()
+ .alpha(0f)
+ .setStartDelay(0)
+ .setDuration(160)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .withEndAction(
+ mAnimateKeyguardStatusViewGoneEndRunnable);
+ if (keyguardFadingAway) {
+ mView.animate()
+ .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
+ .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
+ .start();
+ }
+ } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) {
+ mView.setVisibility(View.VISIBLE);
+ mKeyguardStatusViewAnimating = true;
+ mView.setAlpha(0f);
+ mView.animate()
+ .alpha(1f)
+ .setStartDelay(0)
+ .setDuration(320)
+ .setInterpolator(Interpolators.ALPHA_IN)
+ .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
+ } else if (statusBarState == KEYGUARD) {
+ if (keyguardFadingAway) {
+ mKeyguardStatusViewAnimating = true;
+ mView.animate()
+ .alpha(0)
+ .translationYBy(-getHeight() * 0.05f)
+ .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
+ .setDuration(125)
+ .setStartDelay(0)
+ .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
+ .start();
+ } else {
+ mView.setVisibility(View.VISIBLE);
+ mView.setAlpha(1f);
+ }
+ } else {
+ mView.setVisibility(View.GONE);
+ mView.setAlpha(1f);
+ }
+ }
+
+ private void refreshTime() {
+ mKeyguardClockSwitchController.refresh();
+ }
+
+ private void updateAodIcons() {
+ NotificationIconContainer nic = (NotificationIconContainer)
+ mView.findViewById(com.android.systemui.R.id.clock_notification_icon_container);
+ if (mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ // alternate icon area is set in KeyguardClockSwitchController
+ mNotificationIconAreaController.setupAodIcons(nic, mLockScreenMode);
+ } else {
+ nic.setVisibility(View.GONE);
+ }
+ }
+
+ private final ConfigurationController.ConfigurationListener mConfigurationListener =
+ new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onLocaleListChanged() {
+ refreshTime();
+ }
+
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ mKeyguardClockSwitchController.onDensityOrFontScaleChanged();
+ mView.onDensityOrFontScaleChanged();
+ }
+ };
+
+ private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onLockScreenModeChanged(int mode) {
+ mLockScreenMode = mode;
+ mKeyguardClockSwitchController.updateLockScreenMode(mode);
+ mKeyguardSliceViewController.updateLockScreenMode(mode);
+ updateAodIcons();
+ }
+
+ @Override
+ public void onTimeChanged() {
+ refreshTime();
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mKeyguardClockSwitchController.updateTimeZone(timeZone);
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean showing) {
+ if (showing) {
+ if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
+ refreshTime();
+ mView.updateOwnerInfo();
+ mView.updateLogoutView();
+ }
+ }
+
+ @Override
+ public void onStartedWakingUp() {
+ mView.setEnableMarquee(true);
+ }
+
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ mView.setEnableMarquee(false);
+ }
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ mKeyguardClockSwitchController.refreshFormat();
+ mView.updateOwnerInfo();
+ mView.updateLogoutView();
+ }
+
+ @Override
+ public void onLogoutEnabledChanged() {
+ mView.updateLogoutView();
+ }
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> {
+ mKeyguardStatusViewAnimating = false;
+ mView.setVisibility(View.INVISIBLE);
+ };
+
+
+ private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> {
+ mKeyguardStatusViewAnimating = false;
+ mView.setVisibility(View.GONE);
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> {
+ mKeyguardStatusViewAnimating = false;
+ };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index bb8a99bb8cd8..fa1762e1e5db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -94,6 +94,7 @@ import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -236,6 +237,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final Context mContext;
private final boolean mIsPrimaryUser;
private final boolean mIsAutomotive;
+ private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
HashMap<Integer, SimData> mSimDatas = new HashMap<>();
HashMap<Integer, ServiceState> mServiceStates = new HashMap<Integer, ServiceState>();
@@ -288,6 +290,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final DevicePolicyManager mDevicePolicyManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private boolean mLogoutEnabled;
+ // cached value to avoid IPCs
+ private boolean mIsUdfpsEnrolled;
// If the user long pressed the lock icon, disabling face auth for the current session.
private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -1582,7 +1586,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
RingerModeTracker ringerModeTracker,
@Background Executor backgroundExecutor,
StatusBarStateController statusBarStateController,
- LockPatternUtils lockPatternUtils) {
+ LockPatternUtils lockPatternUtils,
+ AuthController authController) {
mContext = context;
mSubscriptionManager = SubscriptionManager.from(context);
mDeviceProvisioned = isDeviceProvisionedInSettingsDb();
@@ -1592,6 +1597,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mRingerModeTracker = ringerModeTracker;
mStatusBarStateController = statusBarStateController;
mLockPatternUtils = lockPatternUtils;
+ mAuthController = authController;
dumpManager.registerDumpable(getClass().getName(), this);
mHandler = new Handler(mainLooper) {
@@ -1718,7 +1724,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
// Take a guess at initial SIM state, battery status and PLMN until we get an update
- mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0);
+ mBatteryStatus = new BatteryStatus(BATTERY_STATUS_UNKNOWN, 100, 0, 0, 0, true);
// Watch for interesting updates
final IntentFilter filter = new IntentFilter();
@@ -1854,7 +1860,15 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private void updateLockScreenMode() {
mLockScreenMode = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.SHOW_NEW_LOCKSCREEN, 0);
+ Settings.Global.SHOW_NEW_LOCKSCREEN,
+ isUdfpsEnrolled() ? 1 : 0);
+ }
+
+ private void updateUdfpsEnrolled(int userId) {
+ mIsUdfpsEnrolled = mAuthController.isUdfpsEnrolled(userId);
+ }
+ public boolean isUdfpsEnrolled() {
+ return mIsUdfpsEnrolled;
}
private final UserSwitchObserver mUserSwitchObserver = new UserSwitchObserver() {
@@ -2095,6 +2109,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
if (DEBUG) Log.v(TAG, "startListeningForFingerprint()");
int userId = getCurrentUser();
+ updateUdfpsEnrolled(userId);
if (isUnlockWithFingerprintPossible(userId)) {
if (mFingerprintCancelSignal != null) {
mFingerprintCancelSignal.cancel();
@@ -2103,7 +2118,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (isEncryptedOrLockdown(userId)) {
mFpm.detectFingerprint(mFingerprintCancelSignal, mFingerprintDetectionCallback,
- userId, null /* surface */);
+ userId);
} else {
mFpm.authenticate(null /* crypto */, mFingerprintCancelSignal,
mFingerprintAuthenticationCallback, null /* handler */, userId);
@@ -2632,6 +2647,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final boolean wasPluggedIn = old.isPluggedIn();
final boolean stateChangedWhilePluggedIn = wasPluggedIn && nowPluggedIn
&& (old.status != current.status);
+ final boolean nowPresent = current.present;
+ final boolean wasPresent = old.present;
// change in plug state is always interesting
if (wasPluggedIn != nowPluggedIn || stateChangedWhilePluggedIn) {
@@ -2648,6 +2665,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return true;
}
+ // Battery either showed up or disappeared
+ if (wasPresent != nowPresent) {
+ return true;
+ }
+
return false;
}
@@ -3122,6 +3144,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
+ " expected=" + (shouldListenForFingerprint() ? 1 : 0));
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println(" trustManaged=" + getUserTrustIsManaged(userId));
+ pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
}
if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
final int userId = ActivityManager.getCurrentUser();
diff --git a/packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java b/packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java
new file mode 100644
index 000000000000..3cbae0a18937
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/TimeBasedColorsClockController.java
@@ -0,0 +1,178 @@
+/*
+ * 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.keyguard;
+
+import android.util.MathUtils;
+
+import com.android.internal.graphics.ColorUtils;
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.util.ViewController;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+
+/**
+ * Changes the color of the text clock based on the time of day.
+ */
+public class TimeBasedColorsClockController extends ViewController<GradientTextClock> {
+ private final int[] mGradientColors = new int[3];
+ private final float[] mPositions = new float[3];
+
+ /**
+ * 0 = fully awake
+ * between 0 and 1 = transitioning between awake and doze
+ * 1 = fully in doze
+ */
+ private float mDarkAmount = 0f;
+
+ public TimeBasedColorsClockController(GradientTextClock view) {
+ super(view);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ refreshTime(System.currentTimeMillis());
+ }
+
+ @Override
+ protected void onViewDetached() {
+
+ }
+
+ /**
+ * Updates the time for this view. Also updates any color changes.
+ */
+ public void refreshTime(long timeInMillis) {
+ updateColors(timeInMillis);
+ updatePositions(timeInMillis);
+ mView.refreshTime();
+ }
+
+ /**
+ * Set the amount (ratio) that the device has transitioned to doze.
+ *
+ * @param darkAmount Amount of transition to doze: 1f for doze and 0f for awake.
+ */
+ public void setDarkAmount(float darkAmount) {
+ mDarkAmount = darkAmount;
+
+ // TODO: (b/170228350) currently this relayouts throughout the animation;
+ // eventually this should use new Text APIs to animate the variable font weight
+ refreshTime(System.currentTimeMillis());
+
+ int weight = (int) MathUtils.lerp(200, 400, 1f - darkAmount);
+ mView.setFontVariationSettings("'wght' " + weight);
+ }
+
+ private int getTimeIndex(long timeInMillis) {
+ Calendar now = getCalendar(timeInMillis);
+ int hour = now.get(Calendar.HOUR_OF_DAY); // 0 - 23
+ if (hour < mTimes[0]) {
+ return mTimes.length - 1;
+ }
+
+ for (int i = 1; i < mTimes.length; i++) {
+ if (hour < mTimes[i]) {
+ return i - 1;
+ }
+ }
+
+ return mTimes.length - 1;
+ }
+
+ private void updateColors(long timeInMillis) {
+ final int index = getTimeIndex(timeInMillis);
+ final int wallpaperTextColor =
+ Utils.getColorAttrDefaultColor(mView.getContext(), R.attr.wallpaperTextColor);
+ for (int i = 0; i < mGradientColors.length; i++) {
+ // wallpaperTextColor on LS when mDarkAmount = 0f
+ // full color on AOD when mDarkAmount = 1f
+ mGradientColors[i] =
+ ColorUtils.blendARGB(wallpaperTextColor, COLORS[index][i], mDarkAmount);
+ }
+ mView.setGradientColors(mGradientColors);
+ }
+
+ private void updatePositions(long timeInMillis) {
+ Calendar now = getCalendar(timeInMillis);
+ final int index = getTimeIndex(timeInMillis);
+
+ final Calendar startTime = new GregorianCalendar();
+ startTime.setTimeInMillis(now.getTimeInMillis());
+ startTime.set(Calendar.HOUR_OF_DAY, mTimes[index]);
+ if (startTime.getTimeInMillis() > now.getTimeInMillis()) {
+ // start should be earlier than 'now'
+ startTime.add(Calendar.DATE, -1);
+ }
+
+ final Calendar endTime = new GregorianCalendar();
+ endTime.setTimeInMillis(now.getTimeInMillis());
+ if (index == mTimes.length - 1) {
+ endTime.set(Calendar.HOUR_OF_DAY, mTimes[0]);
+ endTime.add(Calendar.DATE, 1); // end time is tomorrow
+ } else {
+ endTime.set(Calendar.HOUR_OF_DAY, mTimes[index + 1]);
+ }
+
+ long totalTimeInThisColorGradient = endTime.getTimeInMillis() - startTime.getTimeInMillis();
+ long timeIntoThisColorGradient = now.getTimeInMillis() - startTime.getTimeInMillis();
+ float percentageWithinGradient =
+ (float) timeIntoThisColorGradient / (float) totalTimeInThisColorGradient;
+
+ for (int i = 0; i < mPositions.length; i++) {
+ // currently hard-coded .3 movement of gradient
+ mPositions[i] = POSITIONS[index][i] - (.3f * percentageWithinGradient);
+ }
+ mView.setColorPositions(mPositions);
+ }
+
+ private Calendar getCalendar(long timeInMillis) {
+ Calendar now = new GregorianCalendar();
+ now.setTimeInMillis(timeInMillis);
+ return now;
+ }
+
+ private static final int[] SUNRISE = new int[] {0xFF6F75AA, 0xFFAFF0FF, 0xFFFFDEBF};
+ private static final int[] DAY = new int[] {0xFF9BD8FB, 0xFFD7F5FF, 0xFFFFF278};
+ private static final int[] NIGHT = new int[] {0xFF333D5E, 0xFFC5A1D6, 0xFF907359};
+
+ private static final float[] SUNRISE_START_POSITIONS = new float[] {.3f, .5f, .8f};
+ private static final float[] DAY_START_POSITIONS = new float[] {.4f, .8f, 1f};
+ private static final float[] NIGHT_START_POSITIONS = new float[] {.25f, .5f, .8f};
+
+ // TODO (b/170228350): use TwilightManager to set sunrise/sunset times
+ private final int mSunriseTime = 6; // 6am
+ private final int mDaytime = 9; // 9 am
+ private final int mNightTime = 19; // 7pm
+
+ private int[] mTimes = new int[] {
+ mSunriseTime,
+ mDaytime,
+ mNightTime
+ };
+ private static final int[][] COLORS = new int[][] {
+ SUNRISE,
+ DAY,
+ NIGHT
+ };
+ private static final float[][] POSITIONS = new float[][] {
+ SUNRISE_START_POSITIONS,
+ DAY_START_POSITIONS,
+ NIGHT_START_POSITIONS
+ };
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index 881108858b51..4fad9a916d0d 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -24,6 +24,7 @@ import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardSecurityContainer;
import com.android.keyguard.KeyguardSecurityViewFlipper;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import dagger.Module;
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
index 21ccff707d34..1b6476ce74df 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusViewComponent.java
@@ -18,6 +18,7 @@ package com.android.keyguard.dagger;
import com.android.keyguard.KeyguardClockSwitchController;
import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardStatusViewController;
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -36,4 +37,7 @@ public interface KeyguardStatusViewComponent {
/** Builds a {@link com.android.keyguard.KeyguardClockSwitchController}. */
KeyguardClockSwitchController getKeyguardClockSwitchController();
+
+ /** Builds a {@link com.android.keyguard.KeyguardStatusViewController}. */
+ KeyguardStatusViewController getKeyguardStatusViewController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 521bb8d58c2b..caaee5fd3f37 100644
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -31,6 +31,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
@@ -91,12 +92,15 @@ public class BatteryMeterView extends LinearLayout implements
private int mTextColor;
private int mLevel;
private int mShowPercentMode = MODE_DEFAULT;
- private boolean mForceShowPercent;
private boolean mShowPercentAvailable;
// Some places may need to show the battery conditionally, and not obey the tuner
private boolean mIgnoreTunerUpdates;
private boolean mIsSubscribedForTunerUpdates;
private boolean mCharging;
+ // Error state where we know nothing about the current battery state
+ private boolean mBatteryStateUnknown;
+ // Lazily-loaded since this is expected to be a rare-if-ever state
+ private Drawable mUnknownStateDrawable;
private DualToneHandler mDualToneHandler;
private int mUser;
@@ -341,6 +345,11 @@ public class BatteryMeterView extends LinearLayout implements
}
private void updatePercentText() {
+ if (mBatteryStateUnknown) {
+ setContentDescription(getContext().getString(R.string.accessibility_battery_unknown));
+ return;
+ }
+
if (mBatteryController == null) {
return;
}
@@ -381,9 +390,13 @@ public class BatteryMeterView extends LinearLayout implements
final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System
.getIntForUser(getContext().getContentResolver(),
SHOW_BATTERY_PERCENT, 0, mUser));
+ boolean shouldShow =
+ (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
+ || mShowPercentMode == MODE_ON
+ || mShowPercentMode == MODE_ESTIMATE;
+ shouldShow = shouldShow && !mBatteryStateUnknown;
- if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
- || mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
+ if (shouldShow) {
if (!showing) {
mBatteryPercentView = loadPercentView();
if (mPercentageStyleId != 0) { // Only set if specified as attribute
@@ -409,6 +422,32 @@ public class BatteryMeterView extends LinearLayout implements
scaleBatteryMeterViews();
}
+ private Drawable getUnknownStateDrawable() {
+ if (mUnknownStateDrawable == null) {
+ mUnknownStateDrawable = mContext.getDrawable(R.drawable.ic_battery_unknown);
+ mUnknownStateDrawable.setTint(mTextColor);
+ }
+
+ return mUnknownStateDrawable;
+ }
+
+ @Override
+ public void onBatteryUnknownStateChanged(boolean isUnknown) {
+ if (mBatteryStateUnknown == isUnknown) {
+ return;
+ }
+
+ mBatteryStateUnknown = isUnknown;
+
+ if (mBatteryStateUnknown) {
+ mBatteryIconView.setImageDrawable(getUnknownStateDrawable());
+ } else {
+ mBatteryIconView.setImageDrawable(mDrawable);
+ }
+
+ updateShowPercent();
+ }
+
/**
* Looks up the scale factor for status bar icons and scales the battery view by that amount.
*/
@@ -449,6 +488,10 @@ public class BatteryMeterView extends LinearLayout implements
if (mBatteryPercentView != null) {
mBatteryPercentView.setTextColor(singleToneColor);
}
+
+ if (mUnknownStateDrawable != null) {
+ mUnknownStateDrawable.setTint(singleToneColor);
+ }
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
@@ -458,8 +501,8 @@ public class BatteryMeterView extends LinearLayout implements
pw.println(" mDrawable.getPowerSave: " + powerSave);
pw.println(" mBatteryPercentView.getText(): " + percent);
pw.println(" mTextColor: #" + Integer.toHexString(mTextColor));
+ pw.println(" mBatteryStateUnknown: " + mBatteryStateUnknown);
pw.println(" mLevel: " + mLevel);
- pw.println(" mForceShowPercent: " + mForceShowPercent);
}
private final class SettingObserver extends ContentObserver {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 9f28e0936d7f..cf576dd6b964 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -37,7 +37,6 @@ import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -119,13 +118,11 @@ import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tuner.TunablePadding.TunablePaddingService;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.leak.GarbageMonitor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.util.sensors.AsyncSensorManager;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.SystemWindows;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -310,7 +307,6 @@ public class Dependency {
@Inject Lazy<KeyguardDismissUtil> mKeyguardDismissUtil;
@Inject Lazy<SmartReplyController> mSmartReplyController;
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
- @Inject Lazy<Bubbles> mBubbles;
@Inject Lazy<NotificationEntryManager> mNotificationEntryManager;
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@@ -340,12 +336,10 @@ public class Dependency {
@Inject Lazy<CommandQueue> mCommandQueue;
@Inject Lazy<Recents> mRecents;
@Inject Lazy<StatusBar> mStatusBar;
- @Inject Lazy<DisplayController> mDisplayController;
- @Inject Lazy<SystemWindows> mSystemWindows;
- @Inject Lazy<DisplayImeController> mDisplayImeController;
@Inject Lazy<RecordingController> mRecordingController;
@Inject Lazy<ProtoTracer> mProtoTracer;
@Inject Lazy<MediaOutputDialogFactory> mMediaOutputDialogFactory;
+ @Inject Lazy<DeviceConfigProxy> mDeviceConfigProxy;
@Inject
public Dependency() {
@@ -510,7 +504,6 @@ public class Dependency {
mProviders.put(SmartReplyController.class, mSmartReplyController::get);
mProviders.put(RemoteInputQuickSettingsDisabler.class,
mRemoteInputQuickSettingsDisabler::get);
- mProviders.put(Bubbles.class, mBubbles::get);
mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
@@ -530,10 +523,8 @@ public class Dependency {
mProviders.put(CommandQueue.class, mCommandQueue::get);
mProviders.put(Recents.class, mRecents::get);
mProviders.put(StatusBar.class, mStatusBar::get);
- mProviders.put(DisplayController.class, mDisplayController::get);
- mProviders.put(SystemWindows.class, mSystemWindows::get);
- mProviders.put(DisplayImeController.class, mDisplayImeController::get);
mProviders.put(ProtoTracer.class, mProtoTracer::get);
+ mProviders.put(DeviceConfigProxy.class, mDeviceConfigProxy::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index a4648ee75485..6d057387430c 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -89,6 +89,7 @@ import com.android.systemui.qs.SecureSetting;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.List;
@@ -124,6 +125,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
private final BroadcastDispatcher mBroadcastDispatcher;
private final Handler mMainHandler;
private final TunerService mTunerService;
+ private final SecureSettings mSecureSettings;
private DisplayManager.DisplayListener mDisplayListener;
private CameraAvailabilityListener mCameraListener;
private final UserTracker mUserTracker;
@@ -203,11 +205,13 @@ public class ScreenDecorations extends SystemUI implements Tunable {
@Inject
public ScreenDecorations(Context context,
@Main Handler handler,
+ SecureSettings secureSettings,
BroadcastDispatcher broadcastDispatcher,
TunerService tunerService,
UserTracker userTracker) {
super(context);
mMainHandler = handler;
+ mSecureSettings = secureSettings;
mBroadcastDispatcher = broadcastDispatcher;
mTunerService = tunerService;
mUserTracker = userTracker;
@@ -313,7 +317,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
// Watch color inversion and invert the overlay as needed.
if (mColorInversionSetting == null) {
- mColorInversionSetting = new SecureSetting(mContext, mHandler,
+ mColorInversionSetting = new SecureSetting(mSecureSettings, mHandler,
Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
mUserTracker.getUserId()) {
@Override
@@ -321,10 +325,9 @@ public class ScreenDecorations extends SystemUI implements Tunable {
updateColorInversion(value);
}
};
-
- mColorInversionSetting.setListening(true);
- mColorInversionSetting.onChange(false);
}
+ mColorInversionSetting.setListening(true);
+ mColorInversionSetting.onChange(false);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
@@ -586,6 +589,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
+ if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
+ Log.i(TAG, "ScreenDecorations is disabled");
+ return;
+ }
mHandler.post(() -> {
int oldRotation = mRotation;
mPendingRotationChange = false;
@@ -643,8 +650,8 @@ public class ScreenDecorations extends SystemUI implements Tunable {
com.android.internal.R.dimen.rounded_corner_radius_bottom);
final boolean changed = mRoundedDefault.x != newRoundedDefault
- || mRoundedDefaultTop.x != newRoundedDefault
- || mRoundedDefaultBottom.x != newRoundedDefault;
+ || mRoundedDefaultTop.x != newRoundedDefaultTop
+ || mRoundedDefaultBottom.x != newRoundedDefaultBottom;
if (changed) {
// If config_roundedCornerMultipleRadius set as true, ScreenDecorations respect the
@@ -777,6 +784,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
@Override
public void onTuningChanged(String key, String newValue) {
+ if (DEBUG_DISABLE_SCREEN_DECORATIONS) {
+ Log.i(TAG, "ScreenDecorations is disabled");
+ return;
+ }
mHandler.post(() -> {
if (mOverlays == null) return;
if (SIZE.equals(key)) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 7dcec3d75367..bf42a60ac033 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -19,22 +19,28 @@ package com.android.systemui;
import android.app.ActivityThread;
import android.app.Application;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Process;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Log;
import android.util.TimingsTraceLog;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.systemui.dagger.ContextComponentHelper;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.people.PeopleSpaceActivity;
+import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.util.NotificationChannels;
import java.lang.reflect.Constructor;
@@ -64,6 +70,8 @@ public class SystemUIApplication extends Application implements
public SystemUIApplication() {
super();
Log.v(TAG, "SystemUIApplication constructed.");
+ // SysUI may be building without protolog preprocessing in some cases
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false;
}
@Override
@@ -104,6 +112,35 @@ public class SystemUIApplication extends Application implements
mServices[i].onBootCompleted();
}
}
+ // If flag SHOW_PEOPLE_SPACE is true, enable People Space launcher icon.
+ // TODO(b/170396074): Remove this when we don't need an icon anymore.
+ try {
+ int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_PEOPLE_SPACE, 0);
+ context.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, PeopleSpaceActivity.class),
+ showPeopleSpace == 1
+ ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ } catch (Exception e) {
+ Log.w(TAG, "Error enabling People Space launch icon:", e);
+ }
+
+ // If SHOW_PEOPLE_SPACE is true, enable People Space widget provider.
+ // TODO(b/170396074): Remove this when we don't need a widget anymore.
+ try {
+ int showPeopleSpace = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.SHOW_PEOPLE_SPACE, 0);
+ context.getPackageManager().setComponentEnabledSetting(
+ new ComponentName(context, PeopleSpaceWidgetProvider.class),
+ showPeopleSpace == 1
+ ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+ : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP);
+ } catch (Exception e) {
+ Log.w(TAG, "Error enabling People Space widget:", e);
+ }
}
}, bootCompletedFilter);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 80253b424335..17bb40e69e27 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -16,6 +16,7 @@
package com.android.systemui;
+import android.app.ActivityThread;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
@@ -30,6 +31,7 @@ import com.android.systemui.dagger.WMComponent;
import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
+import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -49,6 +51,11 @@ public class SystemUIFactory {
}
public static void createFromConfig(Context context) {
+ createFromConfig(context, false);
+ }
+
+ @VisibleForTesting
+ public static void createFromConfig(Context context, boolean fromTest) {
if (mFactory != null) {
return;
}
@@ -62,7 +69,7 @@ public class SystemUIFactory {
Class<?> cls = null;
cls = context.getClassLoader().loadClass(clsName);
mFactory = (SystemUIFactory) cls.newInstance();
- mFactory.init(context);
+ mFactory.init(context, fromTest);
} catch (Throwable t) {
Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
throw new RuntimeException(t);
@@ -76,16 +83,48 @@ public class SystemUIFactory {
public SystemUIFactory() {}
- private void init(Context context) throws ExecutionException, InterruptedException {
+ @VisibleForTesting
+ public void init(Context context, boolean fromTest)
+ throws ExecutionException, InterruptedException {
+ // Only initialize components for the main system ui process running as the primary user
+ final boolean initializeComponents = !fromTest
+ && android.os.Process.myUserHandle().isSystem()
+ && ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName());
mRootComponent = buildGlobalRootComponent(context);
// Stand up WMComponent
mWMComponent = mRootComponent.getWMComponentBuilder().build();
+ if (initializeComponents) {
+ // Only initialize when not starting from tests since this currently initializes some
+ // components that shouldn't be run in the test environment
+ mWMComponent.init();
+ }
// And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
- // TODO: StubAPIClass is just a placeholder.
- mSysUIComponent = mRootComponent.getSysUIComponent()
- .setStubAPIClass(mWMComponent.createStubAPIClass())
+ SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
+ if (initializeComponents) {
+ // Only initialize when not starting from tests since this currently initializes some
+ // components that shouldn't be run in the test environment
+ builder = prepareSysUIComponentBuilder(builder, mWMComponent)
+ .setPip(mWMComponent.getPip())
+ .setSplitScreen(mWMComponent.getSplitScreen())
+ .setOneHanded(mWMComponent.getOneHanded())
+ .setBubbles(mWMComponent.getBubbles())
+ .setShellDump(mWMComponent.getShellDump());
+ } else {
+ // TODO: Call on prepareSysUIComponentBuilder but not with real components.
+ builder = builder.setPip(Optional.ofNullable(null))
+ .setSplitScreen(Optional.ofNullable(null))
+ .setOneHanded(Optional.ofNullable(null))
+ .setBubbles(Optional.ofNullable(null))
+ .setShellDump(Optional.ofNullable(null));
+ }
+ mSysUIComponent = builder
+ .setInputConsumerController(mWMComponent.getInputConsumerController())
+ .setShellTaskOrganizer(mWMComponent.getShellTaskOrganizer())
.build();
+ if (initializeComponents) {
+ mSysUIComponent.init();
+ }
// Every other part of our codebase currently relies on Dependency, so we
// really need to ensure the Dependency gets initialized early on.
@@ -93,6 +132,16 @@ public class SystemUIFactory {
dependency.start();
}
+ /**
+ * Prepares the SysUIComponent builder before it is built.
+ * @param sysUIBuilder the builder provided by the root component's getSysUIComponent() method
+ * @param wm the built WMComponent from the root component's getWMComponent() method
+ */
+ protected SysUIComponent.Builder prepareSysUIComponentBuilder(
+ SysUIComponent.Builder sysUIBuilder, WMComponent wm) {
+ return sysUIBuilder;
+ }
+
protected GlobalRootComponent buildGlobalRootComponent(Context context) {
return DaggerGlobalRootComponent.builder()
.context(context)
@@ -141,7 +190,7 @@ public class SystemUIFactory {
* This method is overridden in vendor specific implementation of Sys UI.
*/
public BackGestureTfClassifierProvider createBackGestureTfClassifierProvider(
- AssetManager am) {
+ AssetManager am, String modelName) {
return new BackGestureTfClassifierProvider();
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 708002d5b946..1f41038c260f 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
import com.android.systemui.dump.LogBufferFreezer;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
+import com.android.systemui.statusbar.policy.BatteryStateNotifier;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -44,18 +45,21 @@ public class SystemUIService extends Service {
private final DumpHandler mDumpHandler;
private final BroadcastDispatcher mBroadcastDispatcher;
private final LogBufferFreezer mLogBufferFreezer;
+ private final BatteryStateNotifier mBatteryStateNotifier;
@Inject
public SystemUIService(
@Main Handler mainHandler,
DumpHandler dumpHandler,
BroadcastDispatcher broadcastDispatcher,
- LogBufferFreezer logBufferFreezer) {
+ LogBufferFreezer logBufferFreezer,
+ BatteryStateNotifier batteryStateNotifier) {
super();
mMainHandler = mainHandler;
mDumpHandler = dumpHandler;
mBroadcastDispatcher = broadcastDispatcher;
mLogBufferFreezer = logBufferFreezer;
+ mBatteryStateNotifier = batteryStateNotifier;
}
@Override
@@ -68,6 +72,11 @@ public class SystemUIService extends Service {
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
+ // If configured, set up a battery notification
+ if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
+ mBatteryStateNotifier.startListening();
+ }
+
// For debugging RescueParty
if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
throw new RuntimeException();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 69a0d65a6963..e40185c279a8 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -24,6 +24,7 @@ import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
import android.util.MathUtils;
import android.view.Gravity;
@@ -31,6 +32,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.ImageView;
@@ -48,15 +51,16 @@ class MagnificationModeSwitch {
@VisibleForTesting
static final long FADING_ANIMATION_DURATION_MS = 300;
- private static final int DEFAULT_FADE_OUT_ANIMATION_DELAY_MS = 3000;
- // The button visible duration starting from the last showButton() called.
- private int mVisibleDuration = DEFAULT_FADE_OUT_ANIMATION_DELAY_MS;
+ @VisibleForTesting
+ static final int DEFAULT_FADE_OUT_ANIMATION_DELAY_MS = 3000;
+ private int mUiTimeout;
private final Runnable mFadeInAnimationTask;
private final Runnable mFadeOutAnimationTask;
@VisibleForTesting
boolean mIsFadeOutAnimating = false;
private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
private final ImageView mImageView;
private final PointF mLastDown = new PointF();
@@ -64,7 +68,7 @@ class MagnificationModeSwitch {
private final int mTapTimeout = ViewConfiguration.getTapTimeout();
private final int mTouchSlop;
private int mMagnificationMode = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
- private final WindowManager.LayoutParams mParams;
+ private final LayoutParams mParams;
private boolean mIsVisible = false;
MagnificationModeSwitch(Context context) {
@@ -74,9 +78,10 @@ class MagnificationModeSwitch {
@VisibleForTesting
MagnificationModeSwitch(Context context, @NonNull ImageView imageView) {
mContext = context;
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mWindowManager = (WindowManager) mContext.getSystemService(
Context.WINDOW_SERVICE);
- mParams = createLayoutParams();
+ mParams = createLayoutParams(context);
mImageView = imageView;
mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
applyResourcesValues();
@@ -202,6 +207,10 @@ class MagnificationModeSwitch {
mWindowManager.addView(mImageView, mParams);
mIsVisible = true;
mImageView.postOnAnimation(mFadeInAnimationTask);
+ mUiTimeout = mAccessibilityManager.getRecommendedTimeoutMillis(
+ DEFAULT_FADE_OUT_ANIMATION_DELAY_MS,
+ AccessibilityManager.FLAG_CONTENT_ICONS
+ | AccessibilityManager.FLAG_CONTENT_CONTROLS);
}
if (mIsFadeOutAnimating) {
mImageView.animate().cancel();
@@ -209,15 +218,26 @@ class MagnificationModeSwitch {
}
// Refresh the time slot of the fade-out task whenever this method is called.
mImageView.removeCallbacks(mFadeOutAnimationTask);
- mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mVisibleDuration);
+ mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mUiTimeout);
}
void onConfigurationChanged(int configDiff) {
- if ((configDiff & ActivityInfo.CONFIG_DENSITY) == 0) {
+ if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
+ applyResourcesValues();
+ mImageView.setImageResource(getIconResId(mMagnificationMode));
+ return;
+ }
+ if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ updateAccessibilityWindowTitle();
return;
}
- applyResourcesValues();
- mImageView.setImageResource(getIconResId(mMagnificationMode));
+ }
+
+ private void updateAccessibilityWindowTitle() {
+ mParams.accessibilityTitle = getAccessibilityWindowTitle(mContext);
+ if (mIsVisible) {
+ mWindowManager.updateViewLayout(mImageView, mParams);
+ }
}
private void toggleMagnificationMode() {
@@ -225,8 +245,11 @@ class MagnificationModeSwitch {
mMagnificationMode ^ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL;
mMagnificationMode = newMode;
mImageView.setImageResource(getIconResId(newMode));
- Settings.Secure.putInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, newMode);
+ Settings.Secure.putIntForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE,
+ newMode,
+ UserHandle.USER_CURRENT);
}
private void handleSingleTap() {
@@ -250,14 +273,19 @@ class MagnificationModeSwitch {
: R.drawable.ic_open_in_new_fullscreen;
}
- private static WindowManager.LayoutParams createLayoutParams() {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ private static LayoutParams createLayoutParams(Context context) {
+ final LayoutParams params = new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT);
params.gravity = Gravity.BOTTOM | Gravity.RIGHT;
+ params.accessibilityTitle = getAccessibilityWindowTitle(context);
return params;
}
+
+ private static String getAccessibilityWindowTitle(Context context) {
+ return context.getString(com.android.internal.R.string.android_system_label);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
index a705ec784c9a..98424beab14e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java
@@ -53,7 +53,8 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
CommandQueue.Callbacks {
private static final String TAG = "WindowMagnification";
private static final int CONFIG_MASK =
- ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION;
+ ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_ORIENTATION
+ | ActivityInfo.CONFIG_LOCALE;
@VisibleForTesting
protected WindowMagnificationAnimationController mWindowMagnificationAnimationController;
@@ -72,8 +73,7 @@ public class WindowMagnification extends SystemUI implements WindowMagnifierCall
super(context);
mHandler = mainHandler;
mLastConfiguration = new Configuration(context.getResources().getConfiguration());
- mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
+ mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mCommandQueue = commandQueue;
mModeSwitchesController = modeSwitchesController;
final WindowMagnificationController controller = new WindowMagnificationController(mContext,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index c3474bb7ca57..fd89baa61657 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -16,7 +16,7 @@
package com.android.systemui.accessibility;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.WindowManager.LayoutParams;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
@@ -249,14 +249,23 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) {
updateDimensions();
if (isWindowVisible()) {
- mWm.removeView(mMirrorView);
- createMirrorWindow();
+ deleteWindowMagnification();
+ enableWindowMagnification(Float.NaN, Float.NaN, Float.NaN);
}
} else if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
onRotate();
+ } else if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) {
+ updateAccessibilityWindowTitleIfNeeded();
}
}
+ private void updateAccessibilityWindowTitleIfNeeded() {
+ if (!isWindowVisible()) return;
+ LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams();
+ params.accessibilityTitle = getAccessibilityWindowTitle();
+ mWm.updateViewLayout(mMirrorView, params);
+ }
+
/** Handles MirrorWindow position when the navigation bar mode changed. */
public void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
@@ -290,8 +299,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return;
}
// The rect of MirrorView is going to be transformed.
- WindowManager.LayoutParams params =
- (WindowManager.LayoutParams) mMirrorView.getLayoutParams();
+ LayoutParams params =
+ (LayoutParams) mMirrorView.getLayoutParams();
mTmpRect.set(params.x, params.y, params.x + params.width, params.y + params.height);
final RectF transformedRect = new RectF(mTmpRect);
matrix.mapRect(transformedRect);
@@ -313,17 +322,18 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin;
int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin;
- WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ LayoutParams params = new LayoutParams(
windowWidth, windowHeight,
- WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY,
+ LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSPARENT);
params.gravity = Gravity.TOP | Gravity.LEFT;
params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
- params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
params.setTitle(mContext.getString(R.string.magnification_window_title));
+ params.accessibilityTitle = getAccessibilityWindowTitle();
mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null);
mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view);
@@ -369,6 +379,10 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
return regionInsideDragBorder;
}
+ private String getAccessibilityWindowTitle() {
+ return mResources.getString(com.android.internal.R.string.android_system_label);
+ }
+
private void showControls() {
if (mMirrorWindowControl != null) {
mMirrorWindowControl.showControl();
@@ -432,8 +446,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
final int maxMirrorViewX = mDisplaySize.x - mMirrorView.getWidth();
final int maxMirrorViewY = mDisplaySize.y - mMirrorView.getHeight() - mNavGestureHeight;
- WindowManager.LayoutParams params =
- (WindowManager.LayoutParams) mMirrorView.getLayoutParams();
+ LayoutParams params =
+ (LayoutParams) mMirrorView.getLayoutParams();
params.x = mMagnificationFrame.left - mMirrorSurfaceMargin;
params.y = mMagnificationFrame.top - mMirrorSurfaceMargin;
// If nav bar mode supports swipe-up gesture, the Y position of mirror view should not
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab4025f7ef9d..24ab6355c2bd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -24,6 +24,9 @@ import android.graphics.PixelFormat;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -51,6 +54,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
/**
* Top level container/controller for the BiometricPrompt UI.
@@ -76,6 +80,8 @@ public class AuthContainerView extends LinearLayout
final Config mConfig;
final int mEffectiveUserId;
+ @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
+ @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
private final Handler mHandler;
private final Injector mInjector;
private final IBinder mWindowToken = new Binder();
@@ -111,7 +117,8 @@ public class AuthContainerView extends LinearLayout
boolean mRequireConfirmation;
int mUserId;
String mOpPackageName;
- @BiometricAuthenticator.Modality int mModalityMask;
+ int[] mSensorIds;
+ boolean mCredentialAllowed;
boolean mSkipIntro;
long mOperationId;
}
@@ -159,9 +166,12 @@ public class AuthContainerView extends LinearLayout
return this;
}
- public AuthContainerView build(@BiometricAuthenticator.Modality int modalityMask) {
- mConfig.mModalityMask = modalityMask;
- return new AuthContainerView(mConfig, new Injector());
+ public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
+ @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
+ @Nullable List<FaceSensorPropertiesInternal> faceProps) {
+ mConfig.mSensorIds = sensorIds;
+ mConfig.mCredentialAllowed = credentialAllowed;
+ return new AuthContainerView(mConfig, new Injector(), fpProps, faceProps);
}
}
@@ -242,11 +252,15 @@ public class AuthContainerView extends LinearLayout
}
@VisibleForTesting
- AuthContainerView(Config config, Injector injector) {
+ AuthContainerView(Config config, Injector injector,
+ @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
+ @Nullable List<FaceSensorPropertiesInternal> faceProps) {
super(config.mContext);
mConfig = config;
mInjector = injector;
+ mFpProps = fpProps;
+ mFaceProps = faceProps;
mEffectiveUserId = mInjector.getUserManager(mContext)
.getCredentialOwnerProfile(mConfig.mUserId);
@@ -269,24 +283,29 @@ public class AuthContainerView extends LinearLayout
// Inflate biometric view only if necessary.
if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
- final @BiometricAuthenticator.Modality int biometricModality =
- config.mModalityMask & ~BiometricAuthenticator.TYPE_CREDENTIAL;
-
- switch (biometricModality) {
- case BiometricAuthenticator.TYPE_FINGERPRINT:
+ if (config.mSensorIds.length == 1) {
+ final int singleSensorAuthId = config.mSensorIds[0];
+ if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
mBiometricView = (AuthBiometricFingerprintView)
factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
- break;
- case BiometricAuthenticator.TYPE_FACE:
+ } else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) {
mBiometricView = (AuthBiometricFaceView)
factory.inflate(R.layout.auth_biometric_face_view, null, false);
- break;
- default:
- Log.e(TAG, "Unsupported biometric modality: " + biometricModality);
+ } else {
+ // Unknown sensorId
+ Log.e(TAG, "Unknown sensorId: " + singleSensorAuthId);
mBiometricView = null;
mBackgroundView = null;
mBiometricScrollView = null;
return;
+ }
+ } else {
+ // The UI currently only supports authentication with a single sensor.
+ Log.e(TAG, "Unsupported sensor array, length: " + config.mSensorIds.length);
+ mBiometricView = null;
+ mBackgroundView = null;
+ mBiometricScrollView = null;
+ return;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index b1ae56a584d2..a6b1b90317f9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -29,12 +29,13 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
-import android.hardware.biometrics.BiometricAuthenticator;
+import android.graphics.RectF;
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricPrompt;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorPropertiesInternal;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.os.Bundle;
@@ -42,6 +43,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.WindowManager;
import com.android.internal.R;
@@ -54,6 +56,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@@ -74,8 +77,13 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
private final StatusBarStateController mStatusBarStateController;
private final IActivityTaskManager mActivityTaskManager;
@Nullable private final FingerprintManager mFingerprintManager;
+ @Nullable private final FaceManager mFaceManager;
private final Provider<UdfpsController> mUdfpsControllerFactory;
+ @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
+ @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
+ @Nullable private final List<FingerprintSensorPropertiesInternal> mUdfpsProps;
+
// TODO: These should just be saved from onSaveState
private SomeArgs mCurrentDialogArgs;
@VisibleForTesting
@@ -239,16 +247,25 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
/**
+ * @return where the UDFPS exists on the screen in pixels.
+ */
+ public RectF getUdfpsRegion() {
+ return mUdfpsController == null ? null : mUdfpsController.getSensorLocation();
+ }
+
+ /**
* Requests fingerprint scan.
*
* @param screenX X position of long press
* @param screenY Y position of long press
+ * @param major length of the major axis. See {@link MotionEvent#AXIS_TOOL_MAJOR}.
+ * @param minor length of the minor axis. See {@link MotionEvent#AXIS_TOOL_MINOR}.
*/
- public void onAodInterrupt(int screenX, int screenY) {
+ public void onAodInterrupt(int screenX, int screenY, float major, float minor) {
if (mUdfpsController == null) {
return;
}
- mUdfpsController.onAodInterrupt(screenX, screenY);
+ mUdfpsController.onAodInterrupt(screenX, screenY, major, minor);
}
/**
@@ -285,14 +302,30 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
StatusBarStateController statusBarStateController,
IActivityTaskManager activityTaskManager,
@Nullable FingerprintManager fingerprintManager,
+ @Nullable FaceManager faceManager,
Provider<UdfpsController> udfpsControllerFactory) {
super(context);
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
mActivityTaskManager = activityTaskManager;
mFingerprintManager = fingerprintManager;
+ mFaceManager = faceManager;
mUdfpsControllerFactory = udfpsControllerFactory;
+ mFpProps = mFingerprintManager != null ? mFingerprintManager.getSensorPropertiesInternal()
+ : null;
+ mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
+
+ List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>();
+ if (mFpProps != null) {
+ for (FingerprintSensorPropertiesInternal props : mFpProps) {
+ if (props.isAnyUdfpsType()) {
+ udfpsProps.add(props);
+ }
+ }
+ }
+ mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null;
+
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
@@ -305,15 +338,9 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
mCommandQueue.addCallback(this);
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
- final List<FingerprintSensorPropertiesInternal> fingerprintSensorProperties =
- mFingerprintManager.getSensorPropertiesInternal();
- for (FingerprintSensorPropertiesInternal props : fingerprintSensorProperties) {
- if (props.isAnyUdfpsType()) {
- mUdfpsController = mUdfpsControllerFactory.get();
- break;
- }
- }
+ if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()
+ && mUdfpsProps != null) {
+ mUdfpsController = mUdfpsControllerFactory.get();
}
try {
@@ -326,24 +353,30 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
- @BiometricAuthenticator.Modality int biometricModality, boolean requireConfirmation,
+ int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
int userId, String opPackageName, long operationId) {
@Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
if (DEBUG) {
+ StringBuilder ids = new StringBuilder();
+ for (int sensorId : sensorIds) {
+ ids.append(sensorId).append(" ");
+ }
Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators
- + ", biometricModality: " + biometricModality
+ + ", sensorIds: " + ids.toString()
+ + ", credentialAllowed: " + credentialAllowed
+ ", requireConfirmation: " + requireConfirmation
+ ", operationId: " + operationId);
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = promptInfo;
args.arg2 = receiver;
- args.argi1 = biometricModality;
- args.arg3 = requireConfirmation;
- args.argi2 = userId;
- args.arg4 = opPackageName;
- args.arg5 = operationId;
+ args.arg3 = sensorIds;
+ args.arg4 = credentialAllowed;
+ args.arg5 = requireConfirmation;
+ args.argi1 = userId;
+ args.arg6 = opPackageName;
+ args.arg7 = operationId;
boolean skipAnimation = false;
if (mCurrentDialog != null) {
@@ -456,27 +489,41 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
}
+ /**
+ * Whether the passed userId has enrolled UDFPS.
+ */
+ public boolean isUdfpsEnrolled(int userId) {
+ if (mUdfpsController == null) {
+ return false;
+ }
+
+ return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
+ }
+
private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
mCurrentDialogArgs = args;
- final @BiometricAuthenticator.Modality int type = args.argi1;
+
final PromptInfo promptInfo = (PromptInfo) args.arg1;
- final boolean requireConfirmation = (boolean) args.arg3;
- final int userId = args.argi2;
- final String opPackageName = (String) args.arg4;
- final long operationId = (long) args.arg5;
+ final int[] sensorIds = (int[]) args.arg3;
+ final boolean credentialAllowed = (boolean) args.arg4;
+ final boolean requireConfirmation = (boolean) args.arg5;
+ final int userId = args.argi1;
+ final String opPackageName = (String) args.arg6;
+ final long operationId = (long) args.arg7;
// Create a new dialog but do not replace the current one yet.
final AuthDialog newDialog = buildDialog(
promptInfo,
requireConfirmation,
userId,
- type,
+ sensorIds,
+ credentialAllowed,
opPackageName,
skipAnimation,
operationId);
if (newDialog == null) {
- Log.e(TAG, "Unsupported type: " + type);
+ Log.e(TAG, "Unsupported type configuration");
return;
}
@@ -484,8 +531,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
Log.d(TAG, "userId: " + userId
+ " savedState: " + savedState
+ " mCurrentDialog: " + mCurrentDialog
- + " newDialog: " + newDialog
- + " type: " + type);
+ + " newDialog: " + newDialog);
}
if (mCurrentDialog != null) {
@@ -541,7 +587,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
}
protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
- int userId, @BiometricAuthenticator.Modality int type, String opPackageName,
+ int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
boolean skipIntro, long operationId) {
return new AuthContainerView.Builder(mContext)
.setCallback(this)
@@ -551,6 +597,6 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
.setOpPackageName(opPackageName)
.setSkipIntro(skipIntro)
.setOperationId(operationId)
- .build(type);
+ .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e3b00495f3dc..a4b407d2785d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -25,6 +25,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PixelFormat;
import android.graphics.Point;
+import android.graphics.RectF;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -78,7 +79,7 @@ class UdfpsController implements DozeReceiver {
// Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
// sensors, this, in addition to a lot of the code here, will be updated.
@VisibleForTesting
- final int mUdfpsSensorId;
+ final FingerprintSensorPropertiesInternal mSensorProps;
private final WindowManager mWindowManager;
private final SystemSettings mSystemSettings;
private final DelayableExecutor mFgExecutor;
@@ -180,19 +181,12 @@ class UdfpsController implements DozeReceiver {
mFgExecutor = fgExecutor;
mLayoutParams = createLayoutParams(context);
- int udfpsSensorId = -1;
- for (FingerprintSensorPropertiesInternal props :
- mFingerprintManager.getSensorPropertiesInternal()) {
- if (props.isAnyUdfpsType()) {
- udfpsSensorId = props.sensorId;
- break;
- }
- }
+ mSensorProps = findFirstUdfps();
// At least one UDFPS sensor exists
- checkArgument(udfpsSensorId != -1);
- mUdfpsSensorId = udfpsSensorId;
+ checkArgument(mSensorProps != null);
mView = (UdfpsView) inflater.inflate(R.layout.udfps_view, null, false);
+ mView.setSensorProperties(mSensorProps);
mHbmPath = resources.getString(R.string.udfps_hbm_sysfs_path);
mHbmEnableCommand = resources.getString(R.string.udfps_hbm_enable_command);
@@ -235,11 +229,29 @@ class UdfpsController implements DozeReceiver {
mIsOverlayShowing = false;
}
+ @Nullable
+ private FingerprintSensorPropertiesInternal findFirstUdfps() {
+ for (FingerprintSensorPropertiesInternal props :
+ mFingerprintManager.getSensorPropertiesInternal()) {
+ if (props.isAnyUdfpsType()) {
+ return props;
+ }
+ }
+ return null;
+ }
+
@Override
public void dozeTimeTick() {
mView.dozeTimeTick();
}
+ /**
+ * @return where the UDFPS exists on the screen in pixels.
+ */
+ public RectF getSensorLocation() {
+ return mView.getSensorRect();
+ }
+
private void setShowOverlay(boolean show) {
if (show == mIsOverlayRequested) {
return;
@@ -333,7 +345,7 @@ class UdfpsController implements DozeReceiver {
* This is intented to be called in response to a sensor that triggers an AOD interrupt for the
* fingerprint sensor.
*/
- void onAodInterrupt(int screenX, int screenY) {
+ void onAodInterrupt(int screenX, int screenY, float major, float minor) {
if (mIsAodInterruptActive) {
return;
}
@@ -344,7 +356,7 @@ class UdfpsController implements DozeReceiver {
mCancelAodTimeoutAction = mFgExecutor.executeDelayed(this::onCancelAodInterrupt,
AOD_INTERRUPT_TIMEOUT_MILLIS);
// using a hard-coded value for major and minor until it is available from the sensor
- onFingerDown(screenX, screenY, 13.0f, 13.0f);
+ onFingerDown(screenX, screenY, minor, major);
}
/**
@@ -374,7 +386,7 @@ class UdfpsController implements DozeReceiver {
fw.write(mHbmEnableCommand);
fw.close();
}
- mFingerprintManager.onPointerDown(mUdfpsSensorId, x, y, minor, major);
+ mFingerprintManager.onPointerDown(mSensorProps.sensorId, x, y, minor, major);
} catch (IOException e) {
mView.hideScrimAndDot();
Log.e(TAG, "onFingerDown | failed to enable HBM: " + e.getMessage());
@@ -382,7 +394,7 @@ class UdfpsController implements DozeReceiver {
}
private void onFingerUp() {
- mFingerprintManager.onPointerUp(mUdfpsSensorId);
+ mFingerprintManager.onPointerUp(mSensorProps.sensorId);
// Hiding the scrim before disabling HBM results in less noticeable flicker.
mView.hideScrimAndDot();
if (mHbmSupported) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
index d7e91384f049..7edcf66196e4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
+import android.annotation.NonNull;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -25,6 +26,7 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -55,8 +57,6 @@ public class UdfpsView extends View implements DozeReceiver,
private final RectF mSensorRect;
private final Paint mSensorPaint;
- private final float mSensorRadius;
- private final float mSensorCenterY;
private final float mSensorTouchAreaCoefficient;
private final int mMaxBurnInOffsetX;
private final int mMaxBurnInOffsetY;
@@ -64,9 +64,7 @@ public class UdfpsView extends View implements DozeReceiver,
private final Rect mTouchableRegion;
private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener;
- // This is calculated from the screen's dimensions at runtime, as opposed to mSensorCenterY,
- // which is defined in layout.xml
- private float mSensorCenterX;
+ @NonNull private FingerprintSensorPropertiesInternal mProps;
// AOD anti-burn-in offsets
private float mInterpolatedDarkAmount;
@@ -83,18 +81,10 @@ public class UdfpsView extends View implements DozeReceiver,
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.UdfpsView, 0,
0);
try {
- if (!a.hasValue(R.styleable.UdfpsView_sensorRadius)) {
- throw new IllegalArgumentException("UdfpsView must contain sensorRadius");
- }
- if (!a.hasValue(R.styleable.UdfpsView_sensorCenterY)) {
- throw new IllegalArgumentException("UdfpsView must contain sensorMarginBottom");
- }
if (!a.hasValue(R.styleable.UdfpsView_sensorTouchAreaCoefficient)) {
throw new IllegalArgumentException(
"UdfpsView must contain sensorTouchAreaCoefficient");
}
- mSensorRadius = a.getDimension(R.styleable.UdfpsView_sensorRadius, 0f);
- mSensorCenterY = a.getDimension(R.styleable.UdfpsView_sensorCenterY, 0f);
mSensorTouchAreaCoefficient = a.getFloat(
R.styleable.UdfpsView_sensorTouchAreaCoefficient, 0f);
} finally {
@@ -134,6 +124,10 @@ public class UdfpsView extends View implements DozeReceiver,
mIsScrimShowing = false;
}
+ void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal properties) {
+ mProps = properties;
+ }
+
@Override
public void dozeTimeTick() {
updateAodPosition();
@@ -165,9 +159,10 @@ public class UdfpsView extends View implements DozeReceiver,
final int h = getLayoutParams().height;
final int w = getLayoutParams().width;
mScrimRect.set(0 /* left */, 0 /* top */, w, h);
- mSensorCenterX = w / 2f;
- mSensorRect.set(mSensorCenterX - mSensorRadius, mSensorCenterY - mSensorRadius,
- mSensorCenterX + mSensorRadius, mSensorCenterY + mSensorRadius);
+ mSensorRect.set(mProps.sensorLocationX - mProps.sensorRadius,
+ mProps.sensorLocationY - mProps.sensorRadius,
+ mProps.sensorLocationX + mProps.sensorRadius,
+ mProps.sensorLocationY + mProps.sensorRadius);
// Sets mTouchableRegion with rounded up values from mSensorRect.
mSensorRect.roundOut(mTouchableRegion);
@@ -201,6 +196,10 @@ public class UdfpsView extends View implements DozeReceiver,
canvas.restore();
}
+ RectF getSensorRect() {
+ return new RectF(mSensorRect);
+ }
+
void setHbmSupported(boolean hbmSupported) {
mHbmSupported = hbmSupported;
}
@@ -211,10 +210,10 @@ public class UdfpsView extends View implements DozeReceiver,
}
boolean isValidTouch(float x, float y, float pressure) {
- return x > (mSensorCenterX - mSensorRadius * mSensorTouchAreaCoefficient)
- && x < (mSensorCenterX + mSensorRadius * mSensorTouchAreaCoefficient)
- && y > (mSensorCenterY - mSensorRadius * mSensorTouchAreaCoefficient)
- && y < (mSensorCenterY + mSensorRadius * mSensorTouchAreaCoefficient);
+ return x > (mProps.sensorLocationX - mProps.sensorRadius * mSensorTouchAreaCoefficient)
+ && x < (mProps.sensorLocationX + mProps.sensorRadius * mSensorTouchAreaCoefficient)
+ && y > (mProps.sensorLocationY - mProps.sensorRadius * mSensorTouchAreaCoefficient)
+ && y < (mProps.sensorLocationY + mProps.sensorRadius * mSensorTouchAreaCoefficient);
}
void setScrimAlpha(int alpha) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index 2e9afa58987a..fd5e85a953ad 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -20,9 +20,11 @@ import static android.hardware.biometrics.BiometricManager.Authenticators;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.UserManager;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
@@ -33,6 +35,7 @@ import com.android.internal.widget.LockPatternUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
public class Utils {
@@ -98,4 +101,19 @@ public class Utils {
final UserManager userManager = context.getSystemService(UserManager.class);
return userManager.isManagedProfile(userId);
}
+
+ static boolean containsSensorId(@Nullable List<? extends SensorPropertiesInternal> properties,
+ int sensorId) {
+ if (properties == null) {
+ return false;
+ }
+
+ for (SensorPropertiesInternal prop : properties) {
+ if (prop.sensorId == sensorId) {
+ return true;
+ }
+ }
+
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 83de3243602b..eea168ad16b3 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -112,7 +112,7 @@ open class BroadcastDispatcher constructor (
* @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
* executor in the main thread (default).
* @param user A user handle to determine which broadcast should be dispatched to this receiver.
- * By default, it is the user of the context (system user in SystemUI).
+ * Pass `null` to use the user of the context (system user in SystemUI).
* @throws IllegalArgumentException if the filter has other constraints that are not actions or
* categories or the filter has no actions.
*/
@@ -120,13 +120,17 @@ open class BroadcastDispatcher constructor (
open fun registerReceiver(
receiver: BroadcastReceiver,
filter: IntentFilter,
- executor: Executor? = context.mainExecutor,
- user: UserHandle = context.user
+ executor: Executor? = null,
+ user: UserHandle? = null
) {
checkFilter(filter)
this.handler
- .obtainMessage(MSG_ADD_RECEIVER,
- ReceiverData(receiver, filter, executor ?: context.mainExecutor, user))
+ .obtainMessage(MSG_ADD_RECEIVER, ReceiverData(
+ receiver,
+ filter,
+ executor ?: context.mainExecutor,
+ user ?: context.user
+ ))
.sendToTarget()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
deleted file mode 100644
index 9f7358bf94ff..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ /dev/null
@@ -1,317 +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.bubbles;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.PathParser;
-import android.widget.ImageView;
-
-import com.android.launcher3.icons.DotRenderer;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-
-import java.util.EnumSet;
-
-import static android.graphics.Paint.DITHER_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-/**
- * View that displays an adaptive icon with an app-badge and a dot.
- *
- * Dot = a small colored circle that indicates whether this bubble has an unread update.
- * Badge = the icon associated with the app that created this bubble, this will show work profile
- * badge if appropriate.
- */
-public class BadgedImageView extends ImageView {
-
- /** Same value as Launcher3 dot code */
- public static final float WHITE_SCRIM_ALPHA = 0.54f;
- /** Same as value in Launcher3 IconShape */
- public static final int DEFAULT_PATH_SIZE = 100;
- /** Same as value in Launcher3 BaseIconFactory */
- private static final float ICON_BADGE_SCALE = 0.444f;
-
- /**
- * Flags that suppress the visibility of the 'new' dot, for one reason or another. If any of
- * these flags are set, the dot will not be shown even if {@link Bubble#showDot()} returns true.
- */
- enum SuppressionFlag {
- // Suppressed because the flyout is visible - it will morph into the dot via animation.
- FLYOUT_VISIBLE,
- // Suppressed because this bubble is behind others in the collapsed stack.
- BEHIND_STACK,
- }
-
- /**
- * Start by suppressing the dot because the flyout is visible - most bubbles are added with a
- * flyout, so this is a reasonable default.
- */
- private final EnumSet<SuppressionFlag> mDotSuppressionFlags =
- EnumSet.of(SuppressionFlag.FLYOUT_VISIBLE);
-
- private float mDotScale = 0f;
- private float mAnimatingToDotScale = 0f;
- private boolean mDotIsAnimating = false;
-
- private BubbleViewProvider mBubble;
-
- private int mBubbleBitmapSize;
- private int mBubbleSize;
- private DotRenderer mDotRenderer;
- private DotRenderer.DrawParams mDrawParams;
- private boolean mOnLeft;
-
- private int mDotColor;
-
- private Rect mTempBounds = new Rect();
-
- public BadgedImageView(Context context) {
- this(context, null);
- }
-
- public BadgedImageView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- mBubbleBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
- mDrawParams = new DotRenderer.DrawParams();
-
- Path iconPath = PathParser.createPathFromPathData(
- getResources().getString(com.android.internal.R.string.config_icon_mask));
- mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE);
-
- setFocusable(true);
- setClickable(true);
- }
-
- /**
- * Updates the view with provided info.
- */
- public void setRenderedBubble(BubbleViewProvider bubble) {
- mBubble = bubble;
- showBadge();
- mDotColor = bubble.getDotColor();
- drawDot(bubble.getDotPath());
- }
-
- @Override
- public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (!shouldDrawDot()) {
- return;
- }
-
- getDrawingRect(mTempBounds);
-
- mDrawParams.color = mDotColor;
- mDrawParams.iconBounds = mTempBounds;
- mDrawParams.leftAlign = mOnLeft;
- mDrawParams.scale = mDotScale;
-
- mDotRenderer.draw(canvas, mDrawParams);
- }
-
- /** Adds a dot suppression flag, updating dot visibility if needed. */
- void addDotSuppressionFlag(SuppressionFlag flag) {
- if (mDotSuppressionFlags.add(flag)) {
- // Update dot visibility, and animate out if we're now behind the stack.
- updateDotVisibility(flag == SuppressionFlag.BEHIND_STACK /* animate */);
- }
- }
-
- /** Removes a dot suppression flag, updating dot visibility if needed. */
- void removeDotSuppressionFlag(SuppressionFlag flag) {
- if (mDotSuppressionFlags.remove(flag)) {
- // Update dot visibility, animating if we're no longer behind the stack.
- updateDotVisibility(flag == SuppressionFlag.BEHIND_STACK);
- }
- }
-
- /** Updates the visibility of the dot, animating if requested. */
- void updateDotVisibility(boolean animate) {
- final float targetScale = shouldDrawDot() ? 1f : 0f;
-
- if (animate) {
- animateDotScale(targetScale, null /* after */);
- } else {
- mDotScale = targetScale;
- mAnimatingToDotScale = targetScale;
- invalidate();
- }
- }
-
- /**
- * @param iconPath The new icon path to use when calculating dot position.
- */
- void drawDot(Path iconPath) {
- mDotRenderer = new DotRenderer(mBubbleBitmapSize, iconPath, DEFAULT_PATH_SIZE);
- invalidate();
- }
-
- /**
- * How big the dot should be, fraction from 0 to 1.
- */
- void setDotScale(float fraction) {
- mDotScale = fraction;
- invalidate();
- }
-
- /**
- * Whether decorations (badges or dots) are on the left.
- */
- boolean getDotOnLeft() {
- return mOnLeft;
- }
-
- /**
- * Return dot position relative to bubble view container bounds.
- */
- float[] getDotCenter() {
- float[] dotPosition;
- if (mOnLeft) {
- dotPosition = mDotRenderer.getLeftDotPosition();
- } else {
- dotPosition = mDotRenderer.getRightDotPosition();
- }
- getDrawingRect(mTempBounds);
- float dotCenterX = mTempBounds.width() * dotPosition[0];
- float dotCenterY = mTempBounds.height() * dotPosition[1];
- return new float[]{dotCenterX, dotCenterY};
- }
-
- /**
- * The key for the {@link Bubble} associated with this view, if one exists.
- */
- @Nullable
- public String getKey() {
- return (mBubble != null) ? mBubble.getKey() : null;
- }
-
- int getDotColor() {
- return mDotColor;
- }
-
- /** Sets the position of the dot and badge, animating them out and back in if requested. */
- void animateDotBadgePositions(boolean onLeft) {
- mOnLeft = onLeft;
-
- if (onLeft != getDotOnLeft() && shouldDrawDot()) {
- animateDotScale(0f /* showDot */, () -> {
- invalidate();
- animateDotScale(1.0f, null /* after */);
- });
- }
- // TODO animate badge
- showBadge();
-
- }
-
- /** Sets the position of the dot and badge. */
- void setDotBadgeOnLeft(boolean onLeft) {
- mOnLeft = onLeft;
- invalidate();
- showBadge();
- }
-
-
- /** Whether to draw the dot in onDraw(). */
- private boolean shouldDrawDot() {
- // Always render the dot if it's animating, since it could be animating out. Otherwise, show
- // it if the bubble wants to show it, and we aren't suppressing it.
- return mDotIsAnimating || (mBubble.showDot() && mDotSuppressionFlags.isEmpty());
- }
-
- /**
- * Animates the dot to the given scale, running the optional callback when the animation ends.
- */
- private void animateDotScale(float toScale, @Nullable Runnable after) {
- mDotIsAnimating = true;
-
- // Don't restart the animation if we're already animating to the given value.
- if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
- mDotIsAnimating = false;
- return;
- }
-
- mAnimatingToDotScale = toScale;
-
- final boolean showDot = toScale > 0f;
-
- // Do NOT wait until after animation ends to setShowDot
- // to avoid overriding more recent showDot states.
- clearAnimation();
- animate()
- .setDuration(200)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setUpdateListener((valueAnimator) -> {
- float fraction = valueAnimator.getAnimatedFraction();
- fraction = showDot ? fraction : 1f - fraction;
- setDotScale(fraction);
- }).withEndAction(() -> {
- setDotScale(showDot ? 1f : 0f);
- mDotIsAnimating = false;
- if (after != null) {
- after.run();
- }
- }).start();
- }
-
- void showBadge() {
- Drawable badge = mBubble.getAppBadge();
- if (badge == null) {
- setImageBitmap(mBubble.getBubbleIcon());
- return;
- }
- Canvas bubbleCanvas = new Canvas();
- Bitmap noBadgeBubble = mBubble.getBubbleIcon();
- Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true);
-
- bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
- bubbleCanvas.setBitmap(bubble);
-
- final int badgeSize = (int) (ICON_BADGE_SCALE * mBubbleSize);
- if (mOnLeft) {
- badge.setBounds(0, mBubbleSize - badgeSize, badgeSize, mBubbleSize);
- } else {
- badge.setBounds(mBubbleSize - badgeSize, mBubbleSize - badgeSize,
- mBubbleSize, mBubbleSize);
- }
- badge.draw(bubbleCanvas);
- bubbleCanvas.setBitmap(null);
- setImageBitmap(bubble);
- }
-
- void hideBadge() {
- setImageBitmap(mBubble.getBubbleIcon());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
deleted file mode 100644
index 57d8dc70cd88..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ /dev/null
@@ -1,812 +0,0 @@
-/*
- * 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.bubbles;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.os.AsyncTask.Status.FINISHED;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-
-import android.annotation.DimenRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Notification;
-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.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Parcelable;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Encapsulates the data and UI elements of a bubble.
- */
-class Bubble implements BubbleViewProvider {
- private static final String TAG = "Bubble";
-
- private final String mKey;
-
- private long mLastUpdated;
- private long mLastAccessed;
-
- @Nullable
- private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
-
- /** Whether the bubble should show a dot for the notification indicating updated content. */
- private boolean mShowBubbleUpdateDot = true;
-
- /** Whether flyout text should be suppressed, regardless of any other flags or state. */
- private boolean mSuppressFlyout;
-
- // Items that are typically loaded later
- private String mAppName;
- private ShortcutInfo mShortcutInfo;
- private String mMetadataShortcutId;
- private BadgedImageView mIconView;
- private BubbleExpandedView mExpandedView;
-
- private BubbleViewInfoTask mInflationTask;
- private boolean mInflateSynchronously;
- private boolean mPendingIntentCanceled;
- private boolean mIsImportantConversation;
-
- /**
- * Presentational info about the flyout.
- */
- public static class FlyoutMessage {
- @Nullable public Icon senderIcon;
- @Nullable public Drawable senderAvatar;
- @Nullable public CharSequence senderName;
- @Nullable public CharSequence message;
- @Nullable public boolean isGroupChat;
- }
-
- private FlyoutMessage mFlyoutMessage;
- private Drawable mBadgeDrawable;
- // Bitmap with no badge, no dot
- private Bitmap mBubbleBitmap;
- private int mDotColor;
- private Path mDotPath;
- private int mFlags;
-
- @NonNull
- private UserHandle mUser;
- @NonNull
- private String mPackageName;
- @Nullable
- private String mTitle;
- @Nullable
- private Icon mIcon;
- private boolean mIsBubble;
- private boolean mIsVisuallyInterruptive;
- private boolean mIsClearable;
- private boolean mShouldSuppressNotificationDot;
- private boolean mShouldSuppressNotificationList;
- private boolean mShouldSuppressPeek;
- private int mDesiredHeight;
- @DimenRes
- private int mDesiredHeightResId;
-
- /** for logging **/
- @Nullable
- private InstanceId mInstanceId;
- @Nullable
- private String mChannelId;
- private int mNotificationId;
- private int mAppUid = -1;
-
- /**
- * A bubble is created and can be updated. This intent is updated until the user first
- * expands the bubble. Once the user has expanded the contents, we ignore the intent updates
- * to prevent restarting the intent & possibly altering UI state in the activity in front of
- * the user.
- *
- * Once the bubble is overflowed, the activity is finished and updates to the
- * notification are respected. Typically an update to an overflowed bubble would result in
- * that bubble being added back to the stack anyways.
- */
- @Nullable
- private PendingIntent mIntent;
- private boolean mIntentActive;
- @Nullable
- private PendingIntent.CancelListener mIntentCancelListener;
-
- /**
- * Sent when the bubble & notification are no longer visible to the user (i.e. no
- * notification in the shade, no bubble in the stack or overflow).
- */
- @Nullable
- private PendingIntent mDeleteIntent;
-
- /**
- * Create a bubble with limited information based on given {@link ShortcutInfo}.
- * Note: Currently this is only being used when the bubble is persisted to disk.
- */
- Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
- final int desiredHeight, final int desiredHeightResId, @Nullable final String title) {
- Objects.requireNonNull(key);
- Objects.requireNonNull(shortcutInfo);
- mMetadataShortcutId = shortcutInfo.getId();
- mShortcutInfo = shortcutInfo;
- mKey = key;
- mFlags = 0;
- mUser = shortcutInfo.getUserHandle();
- mPackageName = shortcutInfo.getPackage();
- mIcon = shortcutInfo.getIcon();
- mDesiredHeight = desiredHeight;
- mDesiredHeightResId = desiredHeightResId;
- mTitle = title;
- mShowBubbleUpdateDot = false;
- }
-
- @VisibleForTesting(visibility = PRIVATE)
- Bubble(@NonNull final BubbleEntry entry,
- @Nullable final BubbleController.NotificationSuppressionChangedListener listener,
- final BubbleController.PendingIntentCanceledListener intentCancelListener) {
- mKey = entry.getKey();
- mSuppressionListener = listener;
- mIntentCancelListener = intent -> {
- if (mIntent != null) {
- mIntent.unregisterCancelListener(mIntentCancelListener);
- }
- intentCancelListener.onPendingIntentCanceled(this);
- };
- setEntry(entry);
- }
-
- @Override
- public String getKey() {
- return mKey;
- }
-
- public UserHandle getUser() {
- return mUser;
- }
-
- @NonNull
- public String getPackageName() {
- return mPackageName;
- }
-
- @Override
- public Bitmap getBubbleIcon() {
- return mBubbleBitmap;
- }
-
- @Override
- public Drawable getAppBadge() {
- return mBadgeDrawable;
- }
-
- @Override
- public int getDotColor() {
- return mDotColor;
- }
-
- @Override
- public Path getDotPath() {
- return mDotPath;
- }
-
- @Nullable
- public String getAppName() {
- return mAppName;
- }
-
- @Nullable
- public ShortcutInfo getShortcutInfo() {
- return mShortcutInfo;
- }
-
- @Nullable
- @Override
- public BadgedImageView getIconView() {
- return mIconView;
- }
-
- @Override
- @Nullable
- public BubbleExpandedView getExpandedView() {
- return mExpandedView;
- }
-
- @Nullable
- public String getTitle() {
- return mTitle;
- }
-
- String getMetadataShortcutId() {
- return mMetadataShortcutId;
- }
-
- boolean hasMetadataShortcutId() {
- return (mMetadataShortcutId != null && !mMetadataShortcutId.isEmpty());
- }
-
- /**
- * Call this to clean up the task for the bubble. Ensure this is always called when done with
- * the bubble.
- */
- void cleanupExpandedView() {
- if (mExpandedView != null) {
- mExpandedView.cleanUpExpandedState();
- mExpandedView = null;
- }
- if (mIntent != null) {
- mIntent.unregisterCancelListener(mIntentCancelListener);
- }
- mIntentActive = false;
- }
-
- /**
- * Call when all the views should be removed/cleaned up.
- */
- void cleanupViews() {
- cleanupExpandedView();
- mIconView = null;
- }
-
- void setPendingIntentCanceled() {
- mPendingIntentCanceled = true;
- }
-
- boolean getPendingIntentCanceled() {
- return mPendingIntentCanceled;
- }
-
- /**
- * Sets whether to perform inflation on the same thread as the caller. This method should only
- * be used in tests, not in production.
- */
- @VisibleForTesting
- void setInflateSynchronously(boolean inflateSynchronously) {
- mInflateSynchronously = inflateSynchronously;
- }
-
- /**
- * Sets whether this bubble is considered visually interruptive. This method is purely for
- * testing.
- */
- @VisibleForTesting
- void setVisuallyInterruptiveForTest(boolean visuallyInterruptive) {
- mIsVisuallyInterruptive = visuallyInterruptive;
- }
-
- /**
- * Starts a task to inflate & load any necessary information to display a bubble.
- *
- * @param callback the callback to notify one the bubble is ready to be displayed.
- * @param context the context for the bubble.
- * @param stackView the stackView the bubble is eventually added to.
- * @param iconFactory the iconfactory use to create badged images for the bubble.
- */
- void inflate(BubbleViewInfoTask.Callback callback,
- Context context,
- BubbleStackView stackView,
- BubbleIconFactory iconFactory,
- boolean skipInflation) {
- if (isBubbleLoading()) {
- mInflationTask.cancel(true /* mayInterruptIfRunning */);
- }
- mInflationTask = new BubbleViewInfoTask(this,
- context,
- stackView,
- iconFactory,
- skipInflation,
- callback);
- if (mInflateSynchronously) {
- mInflationTask.onPostExecute(mInflationTask.doInBackground());
- } else {
- mInflationTask.execute();
- }
- }
-
- private boolean isBubbleLoading() {
- return mInflationTask != null && mInflationTask.getStatus() != FINISHED;
- }
-
- boolean isInflated() {
- return mIconView != null && mExpandedView != null;
- }
-
- void stopInflation() {
- if (mInflationTask == null) {
- return;
- }
- mInflationTask.cancel(true /* mayInterruptIfRunning */);
- }
-
- void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) {
- if (!isInflated()) {
- mIconView = info.imageView;
- mExpandedView = info.expandedView;
- }
-
- mShortcutInfo = info.shortcutInfo;
- mAppName = info.appName;
- mFlyoutMessage = info.flyoutMessage;
-
- mBadgeDrawable = info.badgeDrawable;
- mBubbleBitmap = info.bubbleBitmap;
-
- mDotColor = info.dotColor;
- mDotPath = info.dotPath;
-
- if (mExpandedView != null) {
- mExpandedView.update(this /* bubble */);
- }
- if (mIconView != null) {
- mIconView.setRenderedBubble(this /* bubble */);
- }
- }
-
- /**
- * Set visibility of bubble in the expanded state.
- *
- * @param visibility {@code true} if the expanded bubble should be visible on the screen.
- *
- * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
- * and setting {@code false} actually means rendering the expanded view in transparent.
- */
- @Override
- public void setContentVisibility(boolean visibility) {
- if (mExpandedView != null) {
- mExpandedView.setContentVisibility(visibility);
- }
- }
-
- /**
- * Sets the entry associated with this bubble.
- */
- void setEntry(@NonNull final BubbleEntry entry) {
- Objects.requireNonNull(entry);
- mLastUpdated = entry.getStatusBarNotification().getPostTime();
- mIsBubble = entry.getStatusBarNotification().getNotification().isBubbleNotification();
- mPackageName = entry.getStatusBarNotification().getPackageName();
- mUser = entry.getStatusBarNotification().getUser();
- mTitle = getTitle(entry);
- mChannelId = entry.getStatusBarNotification().getNotification().getChannelId();
- mNotificationId = entry.getStatusBarNotification().getId();
- mAppUid = entry.getStatusBarNotification().getUid();
- mInstanceId = entry.getStatusBarNotification().getInstanceId();
- mFlyoutMessage = extractFlyoutMessage(entry);
- if (entry.getRanking() != null) {
- mShortcutInfo = entry.getRanking().getShortcutInfo();
- mIsVisuallyInterruptive = entry.getRanking().visuallyInterruptive();
- if (entry.getRanking().getChannel() != null) {
- mIsImportantConversation =
- entry.getRanking().getChannel().isImportantConversation();
- }
- }
- if (entry.getBubbleMetadata() != null) {
- mMetadataShortcutId = entry.getBubbleMetadata().getShortcutId();
- mFlags = entry.getBubbleMetadata().getFlags();
- mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight();
- mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId();
- mIcon = entry.getBubbleMetadata().getIcon();
-
- if (!mIntentActive || mIntent == null) {
- if (mIntent != null) {
- mIntent.unregisterCancelListener(mIntentCancelListener);
- }
- mIntent = entry.getBubbleMetadata().getIntent();
- if (mIntent != null) {
- mIntent.registerCancelListener(mIntentCancelListener);
- }
- } else if (mIntent != null && entry.getBubbleMetadata().getIntent() == null) {
- // Was an intent bubble now it's a shortcut bubble... still unregister the listener
- mIntent.unregisterCancelListener(mIntentCancelListener);
- mIntentActive = false;
- mIntent = null;
- }
- mDeleteIntent = entry.getBubbleMetadata().getDeleteIntent();
- }
-
- mIsClearable = entry.isClearable();
- mShouldSuppressNotificationDot = entry.shouldSuppressNotificationDot();
- mShouldSuppressNotificationList = entry.shouldSuppressNotificationList();
- mShouldSuppressPeek = entry.shouldSuppressPeek();
- }
-
- @Nullable
- Icon getIcon() {
- return mIcon;
- }
-
- boolean isVisuallyInterruptive() {
- return mIsVisuallyInterruptive;
- }
-
- /**
- * @return the last time this bubble was updated or accessed, whichever is most recent.
- */
- long getLastActivity() {
- return Math.max(mLastUpdated, mLastAccessed);
- }
-
- /**
- * Sets if the intent used for this bubble is currently active (i.e. populating an
- * expanded view, expanded or not).
- */
- void setIntentActive() {
- mIntentActive = true;
- }
-
- boolean isIntentActive() {
- return mIntentActive;
- }
-
- public InstanceId getInstanceId() {
- return mInstanceId;
- }
-
- @Nullable
- public String getChannelId() {
- return mChannelId;
- }
-
- public int getNotificationId() {
- return mNotificationId;
- }
-
- /**
- * @return the task id of the task in which bubble contents is drawn.
- */
- @Override
- public int getTaskId() {
- return mExpandedView != null ? mExpandedView.getTaskId() : INVALID_TASK_ID;
- }
-
- /**
- * Should be invoked whenever a Bubble is accessed (selected while expanded).
- */
- void markAsAccessedAt(long lastAccessedMillis) {
- mLastAccessed = lastAccessedMillis;
- setSuppressNotification(true);
- setShowDot(false /* show */);
- }
-
- /**
- * Should be invoked whenever a Bubble is promoted from overflow.
- */
- void markUpdatedAt(long lastAccessedMillis) {
- mLastUpdated = lastAccessedMillis;
- }
-
- /**
- * Whether this notification should be shown in the shade.
- */
- boolean showInShade() {
- return !shouldSuppressNotification() || !mIsClearable;
- }
-
- /**
- * Whether this notification conversation is important.
- */
- boolean isImportantConversation() {
- return mIsImportantConversation;
- }
-
- /**
- * Sets whether this notification should be suppressed in the shade.
- */
- void setSuppressNotification(boolean suppressNotification) {
- boolean prevShowInShade = showInShade();
- if (suppressNotification) {
- mFlags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- } else {
- mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- }
-
- if (showInShade() != prevShowInShade && mSuppressionListener != null) {
- mSuppressionListener.onBubbleNotificationSuppressionChange(this);
- }
- }
-
- /**
- * Sets whether the bubble for this notification should show a dot indicating updated content.
- */
- void setShowDot(boolean showDot) {
- mShowBubbleUpdateDot = showDot;
-
- if (mIconView != null) {
- mIconView.updateDotVisibility(true /* animate */);
- }
- }
-
- /**
- * Whether the bubble for this notification should show a dot indicating updated content.
- */
- @Override
- public boolean showDot() {
- return mShowBubbleUpdateDot
- && !mShouldSuppressNotificationDot
- && !shouldSuppressNotification();
- }
-
- /**
- * Whether the flyout for the bubble should be shown.
- */
- boolean showFlyout() {
- return !mSuppressFlyout && !mShouldSuppressPeek
- && !shouldSuppressNotification()
- && !mShouldSuppressNotificationList;
- }
-
- /**
- * Set whether the flyout text for the bubble should be shown when an update is received.
- *
- * @param suppressFlyout whether the flyout text is shown
- */
- void setSuppressFlyout(boolean suppressFlyout) {
- mSuppressFlyout = suppressFlyout;
- }
-
- FlyoutMessage getFlyoutMessage() {
- return mFlyoutMessage;
- }
-
- int getRawDesiredHeight() {
- return mDesiredHeight;
- }
-
- int getRawDesiredHeightResId() {
- return mDesiredHeightResId;
- }
-
- float getDesiredHeight(Context context) {
- boolean useRes = mDesiredHeightResId != 0;
- if (useRes) {
- return getDimenForPackageUser(context, mDesiredHeightResId, mPackageName,
- mUser.getIdentifier());
- } else {
- return mDesiredHeight * context.getResources().getDisplayMetrics().density;
- }
- }
-
- String getDesiredHeightString() {
- boolean useRes = mDesiredHeightResId != 0;
- if (useRes) {
- return String.valueOf(mDesiredHeightResId);
- } else {
- return String.valueOf(mDesiredHeight);
- }
- }
-
- @Nullable
- PendingIntent getBubbleIntent() {
- return mIntent;
- }
-
- @Nullable
- PendingIntent getDeleteIntent() {
- return mDeleteIntent;
- }
-
- Intent getSettingsIntent(final Context context) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
- final int uid = getUid(context);
- if (uid != -1) {
- intent.putExtra(Settings.EXTRA_APP_UID, uid);
- }
- intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
- return intent;
- }
-
- public int getAppUid() {
- return mAppUid;
- }
-
- private int getUid(final Context context) {
- if (mAppUid != -1) return mAppUid;
- final PackageManager pm = context.getPackageManager();
- if (pm == null) return -1;
- try {
- final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0);
- return info.uid;
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "cannot find uid", e);
- }
- return -1;
- }
-
- private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
- PackageManager pm = context.getPackageManager();
- Resources r;
- if (pkg != null) {
- try {
- if (userId == UserHandle.USER_ALL) {
- userId = UserHandle.USER_SYSTEM;
- }
- r = pm.getResourcesForApplicationAsUser(pkg, userId);
- return r.getDimensionPixelSize(resId);
- } catch (PackageManager.NameNotFoundException ex) {
- // Uninstalled, don't care
- } catch (Resources.NotFoundException e) {
- // Invalid res id, return 0 and user our default
- Log.e(TAG, "Couldn't find desired height res id", e);
- }
- }
- return 0;
- }
-
- private boolean shouldSuppressNotification() {
- return isEnabled(Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
- }
-
- public boolean shouldAutoExpand() {
- return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- }
-
- void setShouldAutoExpand(boolean shouldAutoExpand) {
- if (shouldAutoExpand) {
- enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- } else {
- disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
- }
- }
-
- public void setIsBubble(final boolean isBubble) {
- mIsBubble = isBubble;
- }
-
- public boolean isBubble() {
- return mIsBubble;
- }
-
- public void enable(int option) {
- mFlags |= option;
- }
-
- public void disable(int option) {
- mFlags &= ~option;
- }
-
- public boolean isEnabled(int option) {
- return (mFlags & option) != 0;
- }
-
- @Override
- public String toString() {
- return "Bubble{" + mKey + '}';
- }
-
- /**
- * Description of current bubble state.
- */
- public void dump(
- @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
- pw.print("key: "); pw.println(mKey);
- pw.print(" showInShade: "); pw.println(showInShade());
- pw.print(" showDot: "); pw.println(showDot());
- pw.print(" showFlyout: "); pw.println(showFlyout());
- pw.print(" lastActivity: "); pw.println(getLastActivity());
- pw.print(" desiredHeight: "); pw.println(getDesiredHeightString());
- pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification());
- pw.print(" autoExpand: "); pw.println(shouldAutoExpand());
- if (mExpandedView != null) {
- mExpandedView.dump(fd, pw, args);
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof Bubble)) return false;
- Bubble bubble = (Bubble) o;
- return Objects.equals(mKey, bubble.mKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mKey);
- }
-
- @Nullable
- private static String getTitle(@NonNull final BubbleEntry e) {
- final CharSequence titleCharSeq = e.getStatusBarNotification()
- .getNotification().extras.getCharSequence(Notification.EXTRA_TITLE);
- return titleCharSeq == null ? null : titleCharSeq.toString();
- }
-
- /**
- * Returns our best guess for the most relevant text summary of the latest update to this
- * notification, based on its type. Returns null if there should not be an update message.
- */
- @NonNull
- static Bubble.FlyoutMessage extractFlyoutMessage(BubbleEntry entry) {
- Objects.requireNonNull(entry);
- final Notification underlyingNotif = entry.getStatusBarNotification().getNotification();
- final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
-
- Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
- bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
- Notification.EXTRA_IS_GROUP_CONVERSATION);
- try {
- if (Notification.BigTextStyle.class.equals(style)) {
- // Return the big text, it is big so probably important. If it's not there use the
- // normal text.
- CharSequence bigText =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
- bubbleMessage.message = !TextUtils.isEmpty(bigText)
- ? bigText
- : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- return bubbleMessage;
- } else if (Notification.MessagingStyle.class.equals(style)) {
- final List<Notification.MessagingStyle.Message> messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- (Parcelable[]) underlyingNotif.extras.get(
- Notification.EXTRA_MESSAGES));
-
- final Notification.MessagingStyle.Message latestMessage =
- Notification.MessagingStyle.findLatestIncomingMessage(messages);
- if (latestMessage != null) {
- bubbleMessage.message = latestMessage.getText();
- Person sender = latestMessage.getSenderPerson();
- bubbleMessage.senderName = sender != null ? sender.getName() : null;
- bubbleMessage.senderAvatar = null;
- bubbleMessage.senderIcon = sender != null ? sender.getIcon() : null;
- return bubbleMessage;
- }
- } else if (Notification.InboxStyle.class.equals(style)) {
- CharSequence[] lines =
- underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
-
- // Return the last line since it should be the most recent.
- if (lines != null && lines.length > 0) {
- bubbleMessage.message = lines[lines.length - 1];
- return bubbleMessage;
- }
- } else if (Notification.MediaStyle.class.equals(style)) {
- // Return nothing, media updates aren't typically useful as a text update.
- return bubbleMessage;
- } else {
- // Default to text extra.
- bubbleMessage.message =
- underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
- return bubbleMessage;
- }
- } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
- // No use crashing, we'll just return null and the caller will assume there's no update
- // message.
- e.printStackTrace();
- }
-
- return bubbleMessage;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
deleted file mode 100644
index 3f94b00d3c60..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ /dev/null
@@ -1,1713 +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.bubbles;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.Notification.FLAG_BUBBLE;
-import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
-import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CLICK;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
-import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static android.view.View.INVISIBLE;
-import static android.view.View.VISIBLE;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
-
-import static java.lang.annotation.ElementType.FIELD;
-import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
-import static java.lang.annotation.ElementType.PARAMETER;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.NonNull;
-import android.annotation.UserIdInt;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationListenerService.RankingMap;
-import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseSetArray;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.dagger.BubbleModule;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.TaskStackChangeListener;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.ScrimView;
-import com.android.systemui.statusbar.notification.NotificationChannelHelper;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.BubbleCoordinator;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.pip.PinnedStackListenerForwarder;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Bubbles are a special type of content that can "float" on top of other apps or System UI.
- * Bubbles can be expanded to show more content.
- *
- * The controller manages addition, removal, and visible state of bubbles on screen.
- */
-public class BubbleController implements Bubbles, ConfigurationController.ConfigurationListener,
- Dumpable {
-
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
-
- @Retention(SOURCE)
- @IntDef({DISMISS_USER_GESTURE, DISMISS_AGED, DISMISS_TASK_FINISHED, DISMISS_BLOCKED,
- DISMISS_NOTIF_CANCEL, DISMISS_ACCESSIBILITY_ACTION, DISMISS_NO_LONGER_BUBBLE,
- DISMISS_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
- DISMISS_OVERFLOW_MAX_REACHED, DISMISS_SHORTCUT_REMOVED, DISMISS_PACKAGE_REMOVED,
- DISMISS_NO_BUBBLE_UP})
- @Target({FIELD, LOCAL_VARIABLE, PARAMETER})
- @interface DismissReason {}
-
- static final int DISMISS_USER_GESTURE = 1;
- static final int DISMISS_AGED = 2;
- static final int DISMISS_TASK_FINISHED = 3;
- static final int DISMISS_BLOCKED = 4;
- static final int DISMISS_NOTIF_CANCEL = 5;
- static final int DISMISS_ACCESSIBILITY_ACTION = 6;
- static final int DISMISS_NO_LONGER_BUBBLE = 7;
- static final int DISMISS_USER_CHANGED = 8;
- static final int DISMISS_GROUP_CANCELLED = 9;
- static final int DISMISS_INVALID_INTENT = 10;
- static final int DISMISS_OVERFLOW_MAX_REACHED = 11;
- static final int DISMISS_SHORTCUT_REMOVED = 12;
- static final int DISMISS_PACKAGE_REMOVED = 13;
- static final int DISMISS_NO_BUBBLE_UP = 14;
-
- private final Context mContext;
- private final NotificationEntryManager mNotificationEntryManager;
- private final NotifPipeline mNotifPipeline;
- private final BubbleTaskStackListener mTaskStackListener;
- private BubbleExpandListener mExpandListener;
- @Nullable private BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer;
- private final NotificationGroupManagerLegacy mNotificationGroupManager;
- private final ShadeController mShadeController;
- private final FloatingContentCoordinator mFloatingContentCoordinator;
- private final BubbleDataRepository mDataRepository;
- private BubbleLogger mLogger;
- private final Handler mMainHandler;
- private BubbleData mBubbleData;
- private ScrimView mBubbleScrim;
- @Nullable private BubbleStackView mStackView;
- private BubbleIconFactory mBubbleIconFactory;
-
- /**
- * The relative position of the stack when we removed it and nulled it out. If the stack is
- * re-created, it will re-appear at this position.
- */
- @Nullable private BubbleStackView.RelativeStackPosition mPositionFromRemovedStack;
-
- // Tracks the id of the current (foreground) user.
- private int mCurrentUserId;
- // Saves notification keys of active bubbles when users are switched.
- private final SparseSetArray<String> mSavedBubbleKeysPerUser;
-
- // Used when ranking updates occur and we check if things should bubble / unbubble
- private NotificationListenerService.Ranking mTmpRanking;
-
- // Bubbles get added to the status bar view
- private final NotificationShadeWindowController mNotificationShadeWindowController;
- private final ZenModeController mZenModeController;
- private StatusBarStateListener mStatusBarStateListener;
- private INotificationManager mINotificationManager;
-
- // Callback that updates BubbleOverflowActivity on data change.
- @Nullable private BubbleData.Listener mOverflowListener = null;
-
- // Only load overflow data from disk once
- private boolean mOverflowDataLoaded = false;
-
- /**
- * When the shade status changes to SHADE (from anything but SHADE, like LOCKED) we'll select
- * this bubble and expand the stack.
- */
- @Nullable private NotificationEntry mNotifEntryToExpandOnShadeUnlock;
-
- private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
- private IStatusBarService mBarService;
- private WindowManager mWindowManager;
- private SysUiState mSysUiState;
-
- // Used to post to main UI thread
- private Handler mHandler = new Handler();
-
- /** LayoutParams used to add the BubbleStackView to the window manager. */
- private WindowManager.LayoutParams mWmLayoutParams;
- /** Whether or not the BubbleStackView has been added to the WindowManager. */
- private boolean mAddedToWindowManager = false;
-
- // Listens to user switch so bubbles can be saved and restored.
- private final NotificationLockscreenUserManager mNotifUserManager;
-
- /** Last known orientation, used to detect orientation changes in {@link #onConfigChanged}. */
- private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
-
- /**
- * Last known screen density, used to detect display size changes in {@link #onConfigChanged}.
- */
- private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED;
-
- /**
- * Last known font scale, used to detect font size changes in {@link #onConfigChanged}.
- */
- private float mFontScale = 0;
-
- /** Last known direction, used to detect layout direction changes @link #onConfigChanged}. */
- private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED;
-
- private boolean mInflateSynchronously;
-
- private MultiWindowTaskListener mTaskListener;
-
- // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
- private final List<NotifCallback> mCallbacks = new ArrayList<>();
-
- /**
- * Whether the IME is visible, as reported by the BubbleStackView. If it is, we'll make the
- * Bubbles window NOT_FOCUSABLE so that touches on the Bubbles UI doesn't steal focus from the
- * ActivityView and hide the IME.
- */
- private boolean mImeVisible = false;
-
- /**
- * Listener to find out about stack expansion / collapse events.
- */
- public interface BubbleExpandListener {
- /**
- * Called when the expansion state of the bubble stack changes.
- *
- * @param isExpanding whether it's expanding or collapsing
- * @param key the notification key associated with bubble being expanded
- */
- void onBubbleExpandChanged(boolean isExpanding, String key);
- }
-
- /**
- * Listener to be notified when a bubbles' notification suppression state changes.
- */
- public interface NotificationSuppressionChangedListener {
- /**
- * Called when the notification suppression state of a bubble changes.
- */
- void onBubbleNotificationSuppressionChange(Bubble bubble);
- }
-
- /**
- * Listener to be notified when a pending intent has been canceled for a bubble.
- */
- public interface PendingIntentCanceledListener {
- /**
- * Called when the pending intent for a bubble has been canceled.
- */
- void onPendingIntentCanceled(Bubble bubble);
- }
-
- /**
- * Callback for when the BubbleController wants to interact with the notification pipeline to:
- * - Remove a previously bubbled notification
- * - Update the notification shade since bubbled notification should/shouldn't be showing
- */
- public interface NotifCallback {
- /**
- * Called when a bubbled notification that was hidden from the shade is now being removed
- * This can happen when an app cancels a bubbled notification or when the user dismisses a
- * bubble.
- */
- void removeNotification(
- @NonNull NotificationEntry entry,
- @NonNull DismissedByUserStats stats,
- int reason);
-
- /**
- * Called when a bubbled notification has changed whether it should be
- * filtered from the shade.
- */
- void invalidateNotifications(@NonNull String reason);
-
- /**
- * Called on a bubbled entry that has been removed when there are no longer
- * bubbled entries in its group.
- *
- * Checks whether its group has any other (non-bubbled) children. If it doesn't,
- * removes all remnants of the group's summary from the notification pipeline.
- * TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
- */
- void maybeCancelSummary(@NonNull NotificationEntry entry);
- }
-
- /**
- * Listens for the current state of the status bar and updates the visibility state
- * of bubbles as needed.
- */
- private class StatusBarStateListener implements StatusBarStateController.StateListener {
- private int mState;
- /**
- * Returns the current status bar state.
- */
- public int getCurrentState() {
- return mState;
- }
-
- @Override
- public void onStateChanged(int newState) {
- mState = newState;
- boolean shouldCollapse = (mState != SHADE);
- if (shouldCollapse) {
- collapseStack();
- }
-
- if (mNotifEntryToExpandOnShadeUnlock != null) {
- expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock);
- mNotifEntryToExpandOnShadeUnlock = null;
- }
-
- updateStack();
- }
- }
-
- /**
- * Injected constructor. See {@link BubbleModule}.
- */
- public static BubbleController create(Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- StatusBarStateController statusBarStateController,
- ShadeController shadeController,
- @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
- ConfigurationController configurationController,
- NotificationInterruptStateProvider interruptionStateProvider,
- ZenModeController zenModeController,
- NotificationLockscreenUserManager notifUserManager,
- NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- FeatureFlags featureFlags,
- DumpManager dumpManager,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
- INotificationManager notificationManager,
- @Nullable IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- LauncherApps launcherApps,
- UiEventLogger uiEventLogger,
- @Main Handler mainHandler,
- ShellTaskOrganizer organizer) {
- BubbleLogger logger = new BubbleLogger(uiEventLogger);
- return new BubbleController(context, notificationShadeWindowController,
- statusBarStateController, shadeController, new BubbleData(context, logger),
- synchronizer, configurationController, interruptionStateProvider, zenModeController,
- notifUserManager, groupManager, entryManager, notifPipeline, featureFlags,
- dumpManager, floatingContentCoordinator,
- new BubbleDataRepository(context, launcherApps), sysUiState, notificationManager,
- statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger,
- mainHandler, organizer);
- }
-
- /**
- * Testing constructor.
- */
- @VisibleForTesting
- BubbleController(Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- StatusBarStateController statusBarStateController,
- ShadeController shadeController,
- BubbleData data,
- @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
- ConfigurationController configurationController,
- NotificationInterruptStateProvider interruptionStateProvider,
- ZenModeController zenModeController,
- NotificationLockscreenUserManager notifUserManager,
- NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- FeatureFlags featureFlags,
- DumpManager dumpManager,
- FloatingContentCoordinator floatingContentCoordinator,
- BubbleDataRepository dataRepository,
- SysUiState sysUiState,
- INotificationManager notificationManager,
- @Nullable IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- LauncherApps launcherApps,
- BubbleLogger bubbleLogger,
- Handler mainHandler,
- ShellTaskOrganizer organizer) {
- dumpManager.registerDumpable(TAG, this);
- mContext = context;
- mShadeController = shadeController;
- mNotificationInterruptStateProvider = interruptionStateProvider;
- mNotifUserManager = notifUserManager;
- mZenModeController = zenModeController;
- mFloatingContentCoordinator = floatingContentCoordinator;
- mDataRepository = dataRepository;
- mINotificationManager = notificationManager;
- mLogger = bubbleLogger;
- mMainHandler = mainHandler;
- mZenModeController.addCallback(new ZenModeController.Callback() {
- @Override
- public void onZenChanged(int zen) {
- for (Bubble b : mBubbleData.getBubbles()) {
- b.setShowDot(b.showInShade());
- }
- }
-
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- for (Bubble b : mBubbleData.getBubbles()) {
- b.setShowDot(b.showInShade());
- }
- }
- });
-
- configurationController.addCallback(this /* configurationListener */);
- mSysUiState = sysUiState;
-
- mBubbleData = data;
- mBubbleData.setListener(mBubbleDataListener);
- mBubbleData.setSuppressionChangedListener(new NotificationSuppressionChangedListener() {
- @Override
- public void onBubbleNotificationSuppressionChange(Bubble bubble) {
- // Make sure NoMan knows it's not showing in the shade anymore so anyone querying it
- // can tell.
- try {
- mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(),
- !bubble.showInShade());
- } catch (RemoteException e) {
- // Bad things have happened
- }
- }
- });
- mBubbleData.setPendingIntentCancelledListener(bubble -> {
- if (bubble.getBubbleIntent() == null) {
- return;
- }
- if (bubble.isIntentActive()
- || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- bubble.setPendingIntentCanceled();
- return;
- }
- mHandler.post(
- () -> removeBubble(bubble.getKey(),
- BubbleController.DISMISS_INVALID_INTENT));
- });
-
- mNotificationEntryManager = entryManager;
- mNotificationGroupManager = groupManager;
- mNotifPipeline = notifPipeline;
-
- if (!featureFlags.isNewNotifPipelineRenderingEnabled()) {
- setupNEM();
- } else {
- setupNotifPipeline();
- }
-
- mNotificationShadeWindowController = notificationShadeWindowController;
- mStatusBarStateListener = new StatusBarStateListener();
- statusBarStateController.addCallback(mStatusBarStateListener);
-
- mTaskStackListener = new BubbleTaskStackListener();
- TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
-
- try {
- windowManagerShellWrapper.addPinnedStackListener(new BubblesImeListener());
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- mSurfaceSynchronizer = synchronizer;
-
- mWindowManager = windowManager;
- mBarService = statusBarService == null
- ? IStatusBarService.Stub.asInterface(
- ServiceManager.getService(Context.STATUS_BAR_SERVICE))
- : statusBarService;
-
- mBubbleScrim = new ScrimView(mContext);
- mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
-
- mSavedBubbleKeysPerUser = new SparseSetArray<>();
- mCurrentUserId = mNotifUserManager.getCurrentUserId();
- mBubbleData.setCurrentUserId(mCurrentUserId);
-
- mNotifUserManager.addUserChangedListener(
- new NotificationLockscreenUserManager.UserChangedListener() {
- @Override
- public void onUserChanged(int newUserId) {
- BubbleController.this.saveBubbles(mCurrentUserId);
- mBubbleData.dismissAll(DISMISS_USER_CHANGED);
- BubbleController.this.restoreBubbles(newUserId);
- mCurrentUserId = newUserId;
- mBubbleData.setCurrentUserId(newUserId);
- }
- });
-
- mBubbleIconFactory = new BubbleIconFactory(context);
- mTaskListener = new MultiWindowTaskListener(mMainHandler, organizer);
-
- launcherApps.registerCallback(new LauncherApps.Callback() {
- @Override
- public void onPackageAdded(String s, UserHandle userHandle) {}
-
- @Override
- public void onPackageChanged(String s, UserHandle userHandle) {}
-
- @Override
- public void onPackageRemoved(String s, UserHandle userHandle) {
- // Remove bubbles with this package name, since it has been uninstalled and attempts
- // to open a bubble from an uninstalled app can cause issues.
- mBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED);
- }
-
- @Override
- public void onPackagesAvailable(String[] strings, UserHandle userHandle,
- boolean b) {
-
- }
-
- @Override
- public void onPackagesUnavailable(String[] packages, UserHandle userHandle,
- boolean b) {
- for (String packageName : packages) {
- // Remove bubbles from unavailable apps. This can occur when the app is on
- // external storage that has been removed.
- mBubbleData.removeBubblesWithPackageName(packageName, DISMISS_PACKAGE_REMOVED);
- }
- }
-
- @Override
- public void onShortcutsChanged(String packageName, List<ShortcutInfo> validShortcuts,
- UserHandle user) {
- super.onShortcutsChanged(packageName, validShortcuts, user);
-
- // Remove bubbles whose shortcuts aren't in the latest list of valid shortcuts.
- mBubbleData.removeBubblesWithInvalidShortcuts(
- packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED);
- }
- });
- }
-
- /**
- * See {@link NotifCallback}.
- */
- @Override
- public void addNotifCallback(NotifCallback callback) {
- mCallbacks.add(callback);
- }
-
- /**
- * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal.
- */
- public void hideCurrentInputMethod() {
- try {
- mBarService.hideCurrentInputMethodForBubbles();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- private void onBubbleExpandChanged(boolean shouldExpand) {
- mSysUiState
- .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
- .commitUpdate(mContext.getDisplayId());
- }
-
- private void setupNEM() {
- mNotificationEntryManager.addNotificationEntryListener(
- new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- onEntryAdded(entry);
- }
-
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- onEntryUpdated(entry);
- }
-
- @Override
- public void onEntryRemoved(
- NotificationEntry entry,
- @android.annotation.Nullable NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- BubbleController.this.onEntryRemoved(entry);
- }
-
- @Override
- public void onNotificationRankingUpdated(RankingMap rankingMap) {
- onRankingUpdated(rankingMap);
- }
- });
-
- // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator
- mNotificationEntryManager.addNotificationRemoveInterceptor(
- new NotificationRemoveInterceptor() {
- @Override
- public boolean onNotificationRemoveRequested(
- String key,
- NotificationEntry entry,
- int dismissReason) {
- final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
- final boolean isUserDismiss = dismissReason == REASON_CANCEL
- || dismissReason == REASON_CLICK;
- final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
- || dismissReason == REASON_APP_CANCEL_ALL;
- final boolean isSummaryCancel =
- dismissReason == REASON_GROUP_SUMMARY_CANCELED;
-
- // Need to check for !appCancel here because the notification may have
- // previously been dismissed & entry.isRowDismissed would still be true
- boolean userRemovedNotif =
- (entry != null && entry.isRowDismissed() && !isAppCancel)
- || isClearAll || isUserDismiss || isSummaryCancel;
-
- if (userRemovedNotif) {
- return handleDismissalInterception(entry);
- }
- return false;
- }
- });
-
- mNotificationGroupManager.registerGroupChangeListener(
- new NotificationGroupManagerLegacy.OnGroupChangeListener() {
- @Override
- public void onGroupSuppressionChanged(
- NotificationGroupManagerLegacy.NotificationGroup group,
- boolean suppressed) {
- // More notifications could be added causing summary to no longer
- // be suppressed -- in this case need to remove the key.
- final String groupKey = group.summary != null
- ? group.summary.getSbn().getGroupKey()
- : null;
- if (!suppressed && groupKey != null
- && mBubbleData.isSummarySuppressed(groupKey)) {
- mBubbleData.removeSuppressedSummary(groupKey);
- }
- }
- });
-
- addNotifCallback(new NotifCallback() {
- @Override
- public void removeNotification(
- NotificationEntry entry,
- DismissedByUserStats dismissedByUserStats,
- int reason
- ) {
- mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
- dismissedByUserStats, reason);
- }
-
- @Override
- public void invalidateNotifications(String reason) {
- mNotificationEntryManager.updateNotifications(reason);
- }
-
- @Override
- public void maybeCancelSummary(NotificationEntry entry) {
- // Check if removed bubble has an associated suppressed group summary that needs
- // to be removed now.
- final String groupKey = entry.getSbn().getGroupKey();
- if (mBubbleData.isSummarySuppressed(groupKey)) {
- mBubbleData.removeSuppressedSummary(groupKey);
-
- final NotificationEntry summary =
- mNotificationEntryManager.getActiveNotificationUnfiltered(
- mBubbleData.getSummaryKey(groupKey));
- if (summary != null) {
- mNotificationEntryManager.performRemoveNotification(
- summary.getSbn(),
- getDismissedByUserStats(summary, false),
- UNDEFINED_DISMISS_REASON);
- }
- }
-
- // Check if we still need to remove the summary from NoManGroup because the summary
- // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
- // For example:
- // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
- // 2. User expands bubbles so now their respective notifications in the shade are
- // hidden, including the group summary
- // 3. User removes all bubbles
- // 4. We expect all the removed bubbles AND the summary (note: the summary was
- // never added to the suppressedSummary list in BubbleData, so we add this check)
- NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry);
- if (summary != null) {
- ArrayList<NotificationEntry> summaryChildren =
- mNotificationGroupManager.getLogicalChildren(summary.getSbn());
- boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
- if (!isSummaryThisNotif && (summaryChildren == null
- || summaryChildren.isEmpty())) {
- mNotificationEntryManager.performRemoveNotification(
- summary.getSbn(),
- getDismissedByUserStats(summary, false),
- UNDEFINED_DISMISS_REASON);
- }
- }
- }
- });
- }
-
- private void setupNotifPipeline() {
- mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- BubbleController.this.onEntryAdded(entry);
- }
-
- @Override
- public void onEntryUpdated(NotificationEntry entry) {
- BubbleController.this.onEntryUpdated(entry);
- }
-
- @Override
- public void onRankingUpdate(RankingMap rankingMap) {
- onRankingUpdated(rankingMap);
- }
-
- @Override
- public void onEntryRemoved(NotificationEntry entry,
- @NotifCollection.CancellationReason int reason) {
- BubbleController.this.onEntryRemoved(entry);
- }
- });
- }
-
- /**
- * Returns the scrim drawn behind the bubble stack. This is managed by {@link ScrimController}
- * since we want the scrim's appearance and behavior to be identical to that of the notification
- * shade scrim.
- */
- @Override
- public ScrimView getScrimForBubble() {
- return mBubbleScrim;
- }
-
- /**
- * Called when the status bar has become visible or invisible (either permanently or
- * temporarily).
- */
- @Override
- public void onStatusBarVisibilityChanged(boolean visible) {
- if (mStackView != null) {
- // Hide the stack temporarily if the status bar has been made invisible, and the stack
- // is collapsed. An expanded stack should remain visible until collapsed.
- mStackView.setTemporarilyInvisible(!visible && !isStackExpanded());
- }
- }
-
- /**
- * Sets whether to perform inflation on the same thread as the caller. This method should only
- * be used in tests, not in production.
- */
- @VisibleForTesting
- void setInflateSynchronously(boolean inflateSynchronously) {
- mInflateSynchronously = inflateSynchronously;
- }
-
- @Override
- public void setOverflowListener(BubbleData.Listener listener) {
- mOverflowListener = listener;
- }
-
- /**
- * @return Bubbles for updating overflow.
- */
- @Override
- public List<Bubble> getOverflowBubbles() {
- return mBubbleData.getOverflowBubbles();
- }
-
- @Override
- public MultiWindowTaskListener getTaskManager() {
- return mTaskListener;
- }
-
- /**
- * BubbleStackView is lazily created by this method the first time a Bubble is added. This
- * method initializes the stack view and adds it to the StatusBar just above the scrim.
- */
- private void ensureStackViewCreated() {
- if (mStackView == null) {
- mStackView = new BubbleStackView(
- mContext, mBubbleData, mSurfaceSynchronizer, mFloatingContentCoordinator,
- this::onAllBubblesAnimatedOut, this::onImeVisibilityChanged,
- this::hideCurrentInputMethod, this::onBubbleExpandChanged);
- mStackView.setStackStartPosition(mPositionFromRemovedStack);
- mStackView.addView(mBubbleScrim);
- if (mExpandListener != null) {
- mStackView.setExpandListener(mExpandListener);
- }
-
- mStackView.setUnbubbleConversationCallback(key -> {
- final NotificationEntry entry =
- mNotificationEntryManager.getPendingOrActiveNotif(key);
- if (entry != null) {
- onUserChangedBubble(entry, false /* shouldBubble */);
- }
- });
- }
-
- addToWindowManagerMaybe();
- }
-
- /** Adds the BubbleStackView to the WindowManager if it's not already there. */
- private void addToWindowManagerMaybe() {
- // If the stack is null, or already added, don't add it.
- if (mStackView == null || mAddedToWindowManager) {
- return;
- }
-
- mWmLayoutParams = new WindowManager.LayoutParams(
- // Fill the screen so we can use translation animations to position the bubble
- // stack. We'll use touchable regions to ignore touches that are not on the bubbles
- // themselves.
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
- PixelFormat.TRANSLUCENT);
-
- mWmLayoutParams.setTrustedOverlay();
- mWmLayoutParams.setFitInsetsTypes(0);
- mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
- mWmLayoutParams.token = new Binder();
- mWmLayoutParams.setTitle("Bubbles!");
- mWmLayoutParams.packageName = mContext.getPackageName();
- mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
- try {
- mAddedToWindowManager = true;
- mWindowManager.addView(mStackView, mWmLayoutParams);
- } catch (IllegalStateException e) {
- // This means the stack has already been added. This shouldn't happen...
- e.printStackTrace();
- }
- }
-
- private void onImeVisibilityChanged(boolean imeVisible) {
- mImeVisible = imeVisible;
- }
-
- /** Removes the BubbleStackView from the WindowManager if it's there. */
- private void removeFromWindowManagerMaybe() {
- if (!mAddedToWindowManager) {
- return;
- }
-
- try {
- mAddedToWindowManager = false;
- if (mStackView != null) {
- mPositionFromRemovedStack = mStackView.getRelativeStackPosition();
- mWindowManager.removeView(mStackView);
- mStackView.removeView(mBubbleScrim);
- mStackView = null;
- } else {
- Log.w(TAG, "StackView added to WindowManager, but was null when removing!");
- }
- } catch (IllegalArgumentException e) {
- // This means the stack has already been removed - it shouldn't happen, but ignore if it
- // does, since we wanted it removed anyway.
- e.printStackTrace();
- }
- }
-
- /**
- * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been
- * added in the meantime.
- */
- private void onAllBubblesAnimatedOut() {
- if (mStackView != null) {
- mStackView.setVisibility(INVISIBLE);
- removeFromWindowManagerMaybe();
- }
- }
-
- /**
- * Records the notification key for any active bubbles. These are used to restore active
- * bubbles when the user returns to the foreground.
- *
- * @param userId the id of the user
- */
- private void saveBubbles(@UserIdInt int userId) {
- // First clear any existing keys that might be stored.
- mSavedBubbleKeysPerUser.remove(userId);
- // Add in all active bubbles for the current user.
- for (Bubble bubble: mBubbleData.getBubbles()) {
- mSavedBubbleKeysPerUser.add(userId, bubble.getKey());
- }
- }
-
- /**
- * Promotes existing notifications to Bubbles if they were previously bubbles.
- *
- * @param userId the id of the user
- */
- private void restoreBubbles(@UserIdInt int userId) {
- ArraySet<String> savedBubbleKeys = mSavedBubbleKeysPerUser.get(userId);
- if (savedBubbleKeys == null) {
- // There were no bubbles saved for this used.
- return;
- }
- for (NotificationEntry e :
- mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
- if (savedBubbleKeys.contains(e.getKey())
- && mNotificationInterruptStateProvider.shouldBubbleUp(e)
- && e.isBubble()
- && canLaunchInActivityView(mContext, e)) {
- updateBubble(e, true /* suppressFlyout */, false /* showInShade */);
- }
- }
- // Finally, remove the entries for this user now that bubbles are restored.
- mSavedBubbleKeysPerUser.remove(mCurrentUserId);
- }
-
- @Override
- public void onUiModeChanged() {
- updateForThemeChanges();
- }
-
- @Override
- public void onOverlayChanged() {
- updateForThemeChanges();
- }
-
- private void updateForThemeChanges() {
- if (mStackView != null) {
- mStackView.onThemeChanged();
- }
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- // Reload each bubble
- for (Bubble b: mBubbleData.getBubbles()) {
- b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
- false /* skipInflation */);
- }
- for (Bubble b: mBubbleData.getOverflowBubbles()) {
- b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
- false /* skipInflation */);
- }
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- if (mStackView != null && newConfig != null) {
- if (newConfig.orientation != mOrientation) {
- mOrientation = newConfig.orientation;
- mStackView.onOrientationChanged(newConfig.orientation);
- }
- if (newConfig.densityDpi != mDensityDpi) {
- mDensityDpi = newConfig.densityDpi;
- mBubbleIconFactory = new BubbleIconFactory(mContext);
- mStackView.onDisplaySizeChanged();
- }
- if (newConfig.fontScale != mFontScale) {
- mFontScale = newConfig.fontScale;
- mStackView.updateFlyout(mFontScale);
- }
- if (newConfig.getLayoutDirection() != mLayoutDirection) {
- mLayoutDirection = newConfig.getLayoutDirection();
- mStackView.onLayoutDirectionChanged(mLayoutDirection);
- }
- }
- }
-
- /**
- * Set a listener to be notified of bubble expand events.
- */
- @Override
- public void setExpandListener(BubbleExpandListener listener) {
- mExpandListener = ((isExpanding, key) -> {
- if (listener != null) {
- listener.onBubbleExpandChanged(isExpanding, key);
- }
- });
- if (mStackView != null) {
- mStackView.setExpandListener(mExpandListener);
- }
- }
-
- /**
- * Whether or not there are bubbles present, regardless of them being visible on the
- * screen (e.g. if on AOD).
- */
- @VisibleForTesting
- boolean hasBubbles() {
- if (mStackView == null) {
- return false;
- }
- return mBubbleData.hasBubbles();
- }
-
- @Override
- public boolean isStackExpanded() {
- return mBubbleData.isExpanded();
- }
-
- @Override
- public void collapseStack() {
- mBubbleData.setExpanded(false /* expanded */);
- }
-
- @Override
- public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
- String key = entry.getKey();
- boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
- && !mBubbleData.getAnyBubbleWithkey(key).showInShade());
-
- String groupKey = entry.getSbn().getGroupKey();
- boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey);
- boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey));
- return (isSummary && isSuppressedSummary) || isSuppressedBubble;
- }
-
- @Override
- public boolean isBubbleExpanded(NotificationEntry entry) {
- return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null
- && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey());
- }
-
- @Override
- public void promoteBubbleFromOverflow(Bubble bubble) {
- mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK);
- bubble.setInflateSynchronously(mInflateSynchronously);
- bubble.setShouldAutoExpand(true);
- bubble.markAsAccessedAt(System.currentTimeMillis());
- setIsBubble(bubble, true /* isBubble */);
- }
-
- @Override
- public void expandStackAndSelectBubble(NotificationEntry entry) {
- if (mStatusBarStateListener.getCurrentState() == SHADE) {
- mNotifEntryToExpandOnShadeUnlock = null;
-
- String key = entry.getKey();
- Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
- if (bubble != null) {
- mBubbleData.setSelectedBubble(bubble);
- mBubbleData.setExpanded(true);
- } else {
- bubble = mBubbleData.getOverflowBubbleWithKey(key);
- if (bubble != null) {
- promoteBubbleFromOverflow(bubble);
- } else if (entry.canBubble()) {
- // It can bubble but it's not -- it got aged out of the overflow before it
- // was dismissed or opened, make it a bubble again.
- setIsBubble(entry, true /* isBubble */, true /* autoExpand */);
- }
- }
- } else {
- // Wait until we're unlocked to expand, so that the user can see the expand animation
- // and also to work around bugs with expansion animation + shade unlock happening at the
- // same time.
- mNotifEntryToExpandOnShadeUnlock = entry;
- }
- }
-
- @Override
- public void onUserChangedImportance(NotificationEntry entry) {
- try {
- int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
- mBarService.onNotificationBubbleChanged(entry.getKey(), true, flags);
- } catch (RemoteException e) {
- Log.e(TAG, e.getMessage());
- }
- mShadeController.collapsePanel(true);
- if (entry.getRow() != null) {
- entry.getRow().updateBubbleButton();
- }
- }
-
- /**
- * Adds or updates a bubble associated with the provided notification entry.
- *
- * @param notif the notification associated with this bubble.
- */
- void updateBubble(NotificationEntry notif) {
- updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
- }
-
- /**
- * Fills the overflow bubbles by loading them from disk.
- */
- void loadOverflowBubblesFromDisk() {
- if (!mBubbleData.getOverflowBubbles().isEmpty() || mOverflowDataLoaded) {
- // we don't need to load overflow bubbles from disk if it is already in memory
- return;
- }
- mOverflowDataLoaded = true;
- mDataRepository.loadBubbles((bubbles) -> {
- bubbles.forEach(bubble -> {
- if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) {
- // if the bubble is already active, there's no need to push it to overflow
- return;
- }
- bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble),
- mContext, mStackView, mBubbleIconFactory, true /* skipInflation */);
- });
- return null;
- });
- }
-
- void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
- // If this is an interruptive notif, mark that it's interrupted
- if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
- notif.setInterruption();
- }
- if (!notif.getRanking().visuallyInterruptive()
- && (notif.getBubbleMetadata() != null
- && !notif.getBubbleMetadata().getAutoExpandBubble())
- && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) {
- // Update the bubble but don't promote it out of overflow
- Bubble b = mBubbleData.getOverflowBubbleWithKey(notif.getKey());
- b.setEntry(notifToBubbleEntry(notif));
- } else {
- Bubble bubble = mBubbleData.getOrCreateBubble(
- notifToBubbleEntry(notif), null /* persistedBubble */);
- inflateAndAdd(bubble, suppressFlyout, showInShade);
- }
- }
-
- void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
- // Lazy init stack view when a bubble is created
- ensureStackViewCreated();
- bubble.setInflateSynchronously(mInflateSynchronously);
- bubble.inflate(b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
- mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
- }
-
- @Override
- public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) {
- NotificationChannel channel = entry.getChannel();
- final String appPkg = entry.getSbn().getPackageName();
- final int appUid = entry.getSbn().getUid();
- if (channel == null || appPkg == null) {
- return;
- }
-
- // Update the state in NotificationManagerService
- try {
- int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
- mBarService.onNotificationBubbleChanged(entry.getKey(), shouldBubble, flags);
- } catch (RemoteException e) {
- }
-
- // Change the settings
- channel = NotificationChannelHelper.createConversationChannelIfNeeded(mContext,
- mINotificationManager, entry, channel);
- channel.setAllowBubbles(shouldBubble);
- try {
- int currentPref = mINotificationManager.getBubblePreferenceForPackage(appPkg, appUid);
- if (shouldBubble && currentPref == BUBBLE_PREFERENCE_NONE) {
- mINotificationManager.setBubblesAllowed(appPkg, appUid, BUBBLE_PREFERENCE_SELECTED);
- }
- mINotificationManager.updateNotificationChannelForPackage(appPkg, appUid, channel);
- } catch (RemoteException e) {
- Log.e(TAG, e.getMessage());
- }
-
- if (shouldBubble) {
- mShadeController.collapsePanel(true);
- if (entry.getRow() != null) {
- entry.getRow().updateBubbleButton();
- }
- }
- }
-
- @MainThread
- @Override
- public void removeBubble(String key, int reason) {
- if (mBubbleData.hasAnyBubbleWithKey(key)) {
- mBubbleData.dismissBubbleWithKey(key, reason);
- }
- }
-
- private void onEntryAdded(NotificationEntry entry) {
- if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
- && entry.isBubble()
- && canLaunchInActivityView(mContext, entry)) {
- updateBubble(entry);
- }
- }
-
- private void onEntryUpdated(NotificationEntry entry) {
- // shouldBubbleUp checks canBubble & for bubble metadata
- boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry)
- && canLaunchInActivityView(mContext, entry);
- if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
- // It was previously a bubble but no longer a bubble -- lets remove it
- removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
- } else if (shouldBubble && entry.isBubble()) {
- updateBubble(entry);
- }
- }
-
- private void onEntryRemoved(NotificationEntry entry) {
- if (isSummaryOfBubbles(entry)) {
- final String groupKey = entry.getSbn().getGroupKey();
- mBubbleData.removeSuppressedSummary(groupKey);
-
- // Remove any associated bubble children with the summary
- final List<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
- for (int i = 0; i < bubbleChildren.size(); i++) {
- removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
- }
- } else {
- removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL);
- }
- }
-
- /**
- * Called when NotificationListener has received adjusted notification rank and reapplied
- * filtering and sorting. This is used to dismiss or create bubbles based on changes in
- * permissions on the notification channel or the global setting.
- *
- * @param rankingMap the updated ranking map from NotificationListenerService
- */
- private void onRankingUpdated(RankingMap rankingMap) {
- if (mTmpRanking == null) {
- mTmpRanking = new NotificationListenerService.Ranking();
- }
- String[] orderedKeys = rankingMap.getOrderedKeys();
- for (int i = 0; i < orderedKeys.length; i++) {
- String key = orderedKeys[i];
- NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
- rankingMap.getRanking(key, mTmpRanking);
- boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
- if (isActiveBubble && !mTmpRanking.canBubble()) {
- // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason.
- // This means that the app or channel's ability to bubble has been revoked.
- mBubbleData.dismissBubbleWithKey(
- key, BubbleController.DISMISS_BLOCKED);
- } else if (isActiveBubble
- && !mNotificationInterruptStateProvider.shouldBubbleUp(entry)) {
- // If this entry is allowed to bubble, but cannot currently bubble up, dismiss it.
- // This happens when DND is enabled and configured to hide bubbles. Dismissing with
- // the reason DISMISS_NO_BUBBLE_UP will retain the underlying notification, so that
- // the bubble will be re-created if shouldBubbleUp returns true.
- mBubbleData.dismissBubbleWithKey(
- key, BubbleController.DISMISS_NO_BUBBLE_UP);
- } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
- entry.setFlagBubble(true);
- onEntryUpdated(entry);
- }
- }
- }
-
- /**
- * Retrieves any bubbles that are part of the notification group represented by the provided
- * group key.
- */
- private ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) {
- ArrayList<Bubble> bubbleChildren = new ArrayList<>();
- if (groupKey == null) {
- return bubbleChildren;
- }
- for (Bubble bubble : mBubbleData.getActiveBubbles()) {
- final NotificationEntry entry =
- mNotificationEntryManager.getPendingOrActiveNotif(bubble.getKey());
- if (entry != null && groupKey.equals(entry.getSbn().getGroupKey())) {
- bubbleChildren.add(bubble);
- }
- }
- return bubbleChildren;
- }
-
- private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble,
- final boolean autoExpand) {
- Objects.requireNonNull(entry);
- if (isBubble) {
- entry.getSbn().getNotification().flags |= FLAG_BUBBLE;
- } else {
- entry.getSbn().getNotification().flags &= ~FLAG_BUBBLE;
- }
- try {
- int flags = 0;
- if (autoExpand) {
- flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
- }
- mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, flags);
- } catch (RemoteException e) {
- // Bad things have happened
- }
- }
-
- private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
- Objects.requireNonNull(b);
- b.setIsBubble(isBubble);
- final NotificationEntry entry = mNotificationEntryManager
- .getPendingOrActiveNotif(b.getKey());
- if (entry != null) {
- // Updating the entry to be a bubble will trigger our normal update flow
- setIsBubble(entry, isBubble, b.shouldAutoExpand());
- } else if (isBubble) {
- // If bubble doesn't exist, it's a persisted bubble so we need to add it to the
- // stack ourselves
- Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */);
- inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */,
- !bubble.shouldAutoExpand() /* showInShade */);
- }
- }
-
- @SuppressWarnings("FieldCanBeLocal")
- private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
-
- @Override
- public void applyUpdate(BubbleData.Update update) {
- ensureStackViewCreated();
-
- // Lazy load overflow bubbles from disk
- loadOverflowBubblesFromDisk();
-
- mStackView.updateOverflowButtonDot();
-
- // Update bubbles in overflow.
- if (mOverflowListener != null) {
- mOverflowListener.applyUpdate(update);
- }
-
- // Collapsing? Do this first before remaining steps.
- if (update.expandedChanged && !update.expanded) {
- mStackView.setExpanded(false);
- mNotificationShadeWindowController.setRequestTopUi(false, TAG);
- }
-
- // Do removals, if any.
- ArrayList<Pair<Bubble, Integer>> removedBubbles =
- new ArrayList<>(update.removedBubbles);
- ArrayList<Bubble> bubblesToBeRemovedFromRepository = new ArrayList<>();
- for (Pair<Bubble, Integer> removed : removedBubbles) {
- final Bubble bubble = removed.first;
- @DismissReason final int reason = removed.second;
-
- if (mStackView != null) {
- mStackView.removeBubble(bubble);
- }
-
- // Leave the notification in place if we're dismissing due to user switching, or
- // because DND is suppressing the bubble. In both of those cases, we need to be able
- // to restore the bubble from the notification later.
- if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) {
- continue;
- }
- if (reason == DISMISS_NOTIF_CANCEL) {
- bubblesToBeRemovedFromRepository.add(bubble);
- }
- final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
- bubble.getKey());
- if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
- && (!bubble.showInShade()
- || reason == DISMISS_NOTIF_CANCEL
- || reason == DISMISS_GROUP_CANCELLED)) {
- // The bubble is now gone & the notification is hidden from the shade, so
- // time to actually remove it
- for (NotifCallback cb : mCallbacks) {
- if (entry != null) {
- cb.removeNotification(
- entry,
- getDismissedByUserStats(entry, true),
- REASON_CANCEL);
- }
- }
- } else {
- if (bubble.isBubble()) {
- setIsBubble(bubble, false /* isBubble */);
- }
- if (entry != null && entry.getRow() != null) {
- entry.getRow().updateBubbleButton();
- }
- }
-
- }
- if (entry != null) {
- final String groupKey = entry.getSbn().getGroupKey();
- if (getBubblesInGroup(groupKey).isEmpty()) {
- // Time to potentially remove the summary
- for (NotifCallback cb : mCallbacks) {
- cb.maybeCancelSummary(entry);
- }
- }
- }
- }
- mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository);
-
- if (update.addedBubble != null && mStackView != null) {
- mDataRepository.addBubble(mCurrentUserId, update.addedBubble);
- mStackView.addBubble(update.addedBubble);
- }
-
- if (update.updatedBubble != null && mStackView != null) {
- mStackView.updateBubble(update.updatedBubble);
- }
-
- // At this point, the correct bubbles are inflated in the stack.
- // Make sure the order in bubble data is reflected in bubble row.
- if (update.orderChanged && mStackView != null) {
- mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
- mStackView.updateBubbleOrder(update.bubbles);
- }
-
- if (update.selectionChanged && mStackView != null) {
- mStackView.setSelectedBubble(update.selectedBubble);
- if (update.selectedBubble != null) {
- final NotificationEntry entry = mNotificationEntryManager
- .getPendingOrActiveNotif(update.selectedBubble.getKey());
- if (entry != null) {
- mNotificationGroupManager.updateSuppression(entry);
- }
- }
- }
-
- // Expanding? Apply this last.
- if (update.expandedChanged && update.expanded) {
- if (mStackView != null) {
- mStackView.setExpanded(true);
- mNotificationShadeWindowController.setRequestTopUi(true, TAG);
- }
- }
-
- for (NotifCallback cb : mCallbacks) {
- cb.invalidateNotifications("BubbleData.Listener.applyUpdate");
- }
- updateStack();
- }
- };
-
- @Override
- public boolean handleDismissalInterception(NotificationEntry entry) {
- if (entry == null) {
- return false;
- }
- if (isSummaryOfBubbles(entry)) {
- handleSummaryDismissalInterception(entry);
- } else {
- Bubble bubble = mBubbleData.getBubbleInStackWithKey(entry.getKey());
- if (bubble == null || !entry.isBubble()) {
- bubble = mBubbleData.getOverflowBubbleWithKey(entry.getKey());
- }
- if (bubble == null) {
- return false;
- }
- bubble.setSuppressNotification(true);
- bubble.setShowDot(false /* show */);
- }
- // Update the shade
- for (NotifCallback cb : mCallbacks) {
- cb.invalidateNotifications("BubbleController.handleDismissalInterception");
- }
- return true;
- }
-
- private boolean isSummaryOfBubbles(NotificationEntry entry) {
- if (entry == null) {
- return false;
- }
-
- String groupKey = entry.getSbn().getGroupKey();
- ArrayList<Bubble> bubbleChildren = getBubblesInGroup(groupKey);
- boolean isSuppressedSummary = (mBubbleData.isSummarySuppressed(groupKey)
- && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()));
- boolean isSummary = entry.getSbn().getNotification().isGroupSummary();
- return (isSuppressedSummary || isSummary)
- && bubbleChildren != null
- && !bubbleChildren.isEmpty();
- }
-
- private void handleSummaryDismissalInterception(NotificationEntry summary) {
- // current children in the row:
- final List<NotificationEntry> children = summary.getAttachedNotifChildren();
- if (children != null) {
- for (int i = 0; i < children.size(); i++) {
- NotificationEntry child = children.get(i);
- if (mBubbleData.hasAnyBubbleWithKey(child.getKey())) {
- // Suppress the bubbled child
- // As far as group manager is concerned, once a child is no longer shown
- // in the shade, it is essentially removed.
- Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey());
- if (bubbleChild != null) {
- final NotificationEntry entry = mNotificationEntryManager
- .getPendingOrActiveNotif(bubbleChild.getKey());
- if (entry != null) {
- mNotificationGroupManager.onEntryRemoved(entry);
- }
- bubbleChild.setSuppressNotification(true);
- bubbleChild.setShowDot(false /* show */);
- }
- } else {
- // non-bubbled children can be removed
- for (NotifCallback cb : mCallbacks) {
- cb.removeNotification(
- child,
- getDismissedByUserStats(child, true),
- REASON_GROUP_SUMMARY_CANCELED);
- }
- }
- }
- }
-
- // And since all children are removed, remove the summary.
- mNotificationGroupManager.onEntryRemoved(summary);
-
- // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
- mBubbleData.addSummaryToSuppress(summary.getSbn().getGroupKey(),
- summary.getKey());
- }
-
- /**
- * Gets the DismissedByUserStats used by {@link NotificationEntryManager}.
- * Will not be necessary when using the new notification pipeline's {@link NotifCollection}.
- * Instead, this is taken care of by {@link BubbleCoordinator}.
- */
- private DismissedByUserStats getDismissedByUserStats(
- NotificationEntry entry,
- boolean isVisible) {
- return new DismissedByUserStats(
- DISMISSAL_BUBBLE,
- DISMISS_SENTIMENT_NEUTRAL,
- NotificationVisibility.obtain(
- entry.getKey(),
- entry.getRanking().getRank(),
- mNotificationEntryManager.getActiveNotificationsCount(),
- isVisible,
- NotificationLogger.getNotificationLocation(entry)));
- }
-
- /**
- * Updates the visibility of the bubbles based on current state.
- * Does not un-bubble, just hides or un-hides.
- * Updates stack description for TalkBack focus.
- */
- public void updateStack() {
- if (mStackView == null) {
- return;
- }
-
- if (mStatusBarStateListener.getCurrentState() != SHADE) {
- // Bubbles don't appear over the locked shade.
- mStackView.setVisibility(INVISIBLE);
- } else if (hasBubbles()) {
- // If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the
- // stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate
- // out.
- mStackView.setVisibility(VISIBLE);
- }
-
- mStackView.updateContentDescription();
- }
-
- /**
- * The task id of the expanded view, if the stack is expanded and not occluded by the
- * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}.
- */
- private int getExpandedTaskId() {
- if (mStackView == null) {
- return INVALID_TASK_ID;
- }
- final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble();
- if (expandedViewProvider != null && isStackExpanded()
- && !mStackView.isExpansionAnimating()
- && !mNotificationShadeWindowController.getPanelExpanded()) {
- return expandedViewProvider.getTaskId();
- }
- return INVALID_TASK_ID;
- }
-
- @VisibleForTesting
- BubbleStackView getStackView() {
- return mStackView;
- }
-
- /**
- * Description of current bubble state.
- */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("BubbleController state:");
- mBubbleData.dump(fd, pw, args);
- pw.println();
- if (mStackView != null) {
- mStackView.dump(fd, pw, args);
- }
- pw.println();
- }
-
- /**
- * This task stack listener is responsible for responding to tasks moved to the front
- * which are on the default (main) display. When this happens, expanded bubbles must be
- * collapsed so the user may interact with the app which was just moved to the front.
- * <p>
- * This listener is registered with SystemUI's ActivityManagerWrapper which dispatches
- * these calls via a main thread Handler.
- */
- @MainThread
- private class BubbleTaskStackListener extends TaskStackChangeListener {
-
- @Override
- public void onTaskMovedToFront(RunningTaskInfo taskInfo) {
- int expandedId = getExpandedTaskId();
- if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) {
- mBubbleData.setExpanded(false);
- }
- }
-
- @Override
- public void onActivityRestartAttempt(RunningTaskInfo taskInfo, boolean homeTaskVisible,
- boolean clearedTask, boolean wasVisible) {
- for (Bubble b : mBubbleData.getBubbles()) {
- if (taskInfo.taskId == b.getTaskId()) {
- mBubbleData.setSelectedBubble(b);
- mBubbleData.setExpanded(true);
- return;
- }
- }
- }
-
- }
-
- /**
- * Whether an intent is properly configured to display in an {@link android.app.ActivityView}.
- *
- * Keep checks in sync with NotificationManagerService#canLaunchInActivityView. Typically
- * that should filter out any invalid bubbles, but should protect SysUI side just in case.
- *
- * @param context the context to use.
- * @param entry the entry to bubble.
- */
- static boolean canLaunchInActivityView(Context context, NotificationEntry entry) {
- PendingIntent intent = entry.getBubbleMetadata() != null
- ? entry.getBubbleMetadata().getIntent()
- : null;
- if (entry.getBubbleMetadata() != null
- && entry.getBubbleMetadata().getShortcutId() != null) {
- return true;
- }
- if (intent == null) {
- Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey());
- return false;
- }
- PackageManager packageManager = StatusBar.getPackageManagerForUser(
- context, entry.getSbn().getUser().getIdentifier());
- ActivityInfo info =
- intent.getIntent().resolveActivityInfo(packageManager, 0);
- if (info == null) {
- Log.w(TAG, "Unable to send as bubble, "
- + entry.getKey() + " couldn't find activity info for intent: "
- + intent);
- return false;
- }
- if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
- Log.w(TAG, "Unable to send as bubble, "
- + entry.getKey() + " activity is not resizable for intent: "
- + intent);
- return false;
- }
- return true;
- }
-
- /** PinnedStackListener that dispatches IME visibility updates to the stack. */
- //TODO(b/170442945): Better way to do this / insets listener?
- private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener {
- @Override
- public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
- if (mStackView != null) {
- mStackView.post(() -> mStackView.onImeVisibilityChanged(imeVisible, imeHeight));
- }
- }
- }
-
- static BubbleEntry notifToBubbleEntry(NotificationEntry e) {
- return new BubbleEntry(e.getSbn(), e.getRanking(), e.isClearable(),
- e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(),
- e.shouldSuppressPeek());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
deleted file mode 100644
index b4626f27d370..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ /dev/null
@@ -1,824 +0,0 @@
-/*
- * 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.bubbles;
-
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.annotation.NonNull;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.util.Log;
-import android.util.Pair;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController.DismissReason;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-/**
- * Keeps track of active bubbles.
- */
-public class BubbleData {
-
- private BubbleLogger mLogger;
-
- private int mCurrentUserId;
-
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleData" : TAG_BUBBLES;
-
- private static final Comparator<Bubble> BUBBLES_BY_SORT_KEY_DESCENDING =
- Comparator.comparing(BubbleData::sortKey).reversed();
-
- /** Contains information about changes that have been made to the state of bubbles. */
- static final class Update {
- boolean expandedChanged;
- boolean selectionChanged;
- boolean orderChanged;
- boolean expanded;
- @Nullable Bubble selectedBubble;
- @Nullable Bubble addedBubble;
- @Nullable Bubble updatedBubble;
- @Nullable Bubble addedOverflowBubble;
- @Nullable Bubble removedOverflowBubble;
- // Pair with Bubble and @DismissReason Integer
- final List<Pair<Bubble, Integer>> removedBubbles = new ArrayList<>();
-
- // A read-only view of the bubbles list, changes there will be reflected here.
- final List<Bubble> bubbles;
- final List<Bubble> overflowBubbles;
-
- private Update(List<Bubble> row, List<Bubble> overflow) {
- bubbles = Collections.unmodifiableList(row);
- overflowBubbles = Collections.unmodifiableList(overflow);
- }
-
- boolean anythingChanged() {
- return expandedChanged
- || selectionChanged
- || addedBubble != null
- || updatedBubble != null
- || !removedBubbles.isEmpty()
- || addedOverflowBubble != null
- || removedOverflowBubble != null
- || orderChanged;
- }
-
- void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
- removedBubbles.add(new Pair<>(bubbleToRemove, reason));
- }
- }
-
- /**
- * This interface reports changes to the state and appearance of bubbles which should be applied
- * as necessary to the UI.
- */
- interface Listener {
- /** Reports changes have have occurred as a result of the most recent operation. */
- void applyUpdate(Update update);
- }
-
- interface TimeSource {
- long currentTimeMillis();
- }
-
- private final Context mContext;
- /** Bubbles that are actively in the stack. */
- private final List<Bubble> mBubbles;
- /** Bubbles that aged out to overflow. */
- private final List<Bubble> mOverflowBubbles;
- /** Bubbles that are being loaded but haven't been added to the stack just yet. */
- private final HashMap<String, Bubble> mPendingBubbles;
- private Bubble mSelectedBubble;
- private boolean mShowingOverflow;
- private boolean mExpanded;
- private final int mMaxBubbles;
- private int mMaxOverflowBubbles;
-
- // State tracked during an operation -- keeps track of what listener events to dispatch.
- private Update mStateChange;
-
- private TimeSource mTimeSource = System::currentTimeMillis;
-
- @Nullable
- private Listener mListener;
-
- @Nullable
- private BubbleController.NotificationSuppressionChangedListener mSuppressionListener;
- private BubbleController.PendingIntentCanceledListener mCancelledListener;
-
- /**
- * We track groups with summaries that aren't visibly displayed but still kept around because
- * the bubble(s) associated with the summary still exist.
- *
- * The summary must be kept around so that developers can cancel it (and hence the bubbles
- * associated with it). This list is used to check if the summary should be hidden from the
- * shade.
- *
- * Key: group key of the notification
- * Value: key of the notification
- */
- private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
-
- public BubbleData(Context context, BubbleLogger bubbleLogger) {
- mContext = context;
- mLogger = bubbleLogger;
- mBubbles = new ArrayList<>();
- mOverflowBubbles = new ArrayList<>();
- mPendingBubbles = new HashMap<>();
- mStateChange = new Update(mBubbles, mOverflowBubbles);
- mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
- mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
- }
-
- public void setSuppressionChangedListener(
- BubbleController.NotificationSuppressionChangedListener listener) {
- mSuppressionListener = listener;
- }
-
- public void setPendingIntentCancelledListener(
- BubbleController.PendingIntentCanceledListener listener) {
- mCancelledListener = listener;
- }
-
- public boolean hasBubbles() {
- return !mBubbles.isEmpty();
- }
-
- public boolean isExpanded() {
- return mExpanded;
- }
-
- public boolean hasAnyBubbleWithKey(String key) {
- return hasBubbleInStackWithKey(key) || hasOverflowBubbleWithKey(key);
- }
-
- public boolean hasBubbleInStackWithKey(String key) {
- return getBubbleInStackWithKey(key) != null;
- }
-
- public boolean hasOverflowBubbleWithKey(String key) {
- return getOverflowBubbleWithKey(key) != null;
- }
-
- @Nullable
- public Bubble getSelectedBubble() {
- return mSelectedBubble;
- }
-
- /** Return a read-only current active bubble lists. */
- public List<Bubble> getActiveBubbles() {
- return Collections.unmodifiableList(mBubbles);
- }
-
- public void setExpanded(boolean expanded) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpanded: " + expanded);
- }
- setExpandedInternal(expanded);
- dispatchPendingChanges();
- }
-
- public void setSelectedBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubble: " + bubble);
- }
- setSelectedBubbleInternal(bubble);
- dispatchPendingChanges();
- }
-
- void setShowingOverflow(boolean showingOverflow) {
- mShowingOverflow = showingOverflow;
- }
-
- /**
- * Constructs a new bubble or returns an existing one. Does not add new bubbles to
- * bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
- * for that.
- *
- * @param entry The notification entry to use, only null if it's a bubble being promoted from
- * the overflow that was persisted over reboot.
- * @param persistedBubble The bubble to use, only non-null if it's a bubble being promoted from
- * the overflow that was persisted over reboot.
- */
- public Bubble getOrCreateBubble(BubbleEntry entry, Bubble persistedBubble) {
- String key = persistedBubble != null ? persistedBubble.getKey() : entry.getKey();
- Bubble bubbleToReturn = getBubbleInStackWithKey(key);
-
- if (bubbleToReturn == null) {
- bubbleToReturn = getOverflowBubbleWithKey(key);
- if (bubbleToReturn != null) {
- // Promoting from overflow
- mOverflowBubbles.remove(bubbleToReturn);
- } else if (mPendingBubbles.containsKey(key)) {
- // Update while it was pending
- bubbleToReturn = mPendingBubbles.get(key);
- } else if (entry != null) {
- // New bubble
- bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener);
- } else {
- // Persisted bubble being promoted
- bubbleToReturn = persistedBubble;
- }
- }
-
- if (entry != null) {
- bubbleToReturn.setEntry(entry);
- }
- mPendingBubbles.put(key, bubbleToReturn);
- return bubbleToReturn;
- }
-
- /**
- * When this method is called it is expected that all info in the bubble has completed loading.
- * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context,
- * BubbleStackView, BubbleIconFactory, boolean).
- */
- void notificationEntryUpdated(Bubble bubble, boolean suppressFlyout, boolean showInShade) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryUpdated: " + bubble);
- }
- mPendingBubbles.remove(bubble.getKey()); // No longer pending once we're here
- Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
- suppressFlyout |= !bubble.isVisuallyInterruptive();
-
- if (prevBubble == null) {
- // Create a new bubble
- bubble.setSuppressFlyout(suppressFlyout);
- doAdd(bubble);
- trim();
- } else {
- // Updates an existing bubble
- bubble.setSuppressFlyout(suppressFlyout);
- // If there is no flyout, we probably shouldn't show the bubble at the top
- doUpdate(bubble, !suppressFlyout /* reorder */);
- }
-
- if (bubble.shouldAutoExpand()) {
- bubble.setShouldAutoExpand(false);
- setSelectedBubbleInternal(bubble);
- if (!mExpanded) {
- setExpandedInternal(true);
- }
- }
-
- boolean isBubbleExpandedAndSelected = mExpanded && mSelectedBubble == bubble;
- boolean suppress = isBubbleExpandedAndSelected || !showInShade || !bubble.showInShade();
- bubble.setSuppressNotification(suppress);
- bubble.setShowDot(!isBubbleExpandedAndSelected /* show */);
-
- dispatchPendingChanges();
- }
-
- /**
- * Dismisses the bubble with the matching key, if it exists.
- */
- public void dismissBubbleWithKey(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
- }
- doRemove(key, reason);
- dispatchPendingChanges();
- }
-
- /**
- * Adds a group key indicating that the summary for this group should be suppressed.
- *
- * @param groupKey the group key of the group whose summary should be suppressed.
- * @param notifKey the notification entry key of that summary.
- */
- void addSummaryToSuppress(String groupKey, String notifKey) {
- mSuppressedGroupKeys.put(groupKey, notifKey);
- }
-
- /**
- * Retrieves the notif entry key of the summary associated with the provided group key.
- *
- * @param groupKey the group to look up
- * @return the key for the notification that is the summary of this group.
- */
- String getSummaryKey(String groupKey) {
- return mSuppressedGroupKeys.get(groupKey);
- }
-
- /**
- * Removes a group key indicating that summary for this group should no longer be suppressed.
- */
- void removeSuppressedSummary(String groupKey) {
- mSuppressedGroupKeys.remove(groupKey);
- }
-
- /**
- * Whether the summary for the provided group key is suppressed.
- */
- boolean isSummarySuppressed(String groupKey) {
- return mSuppressedGroupKeys.containsKey(groupKey);
- }
-
- /**
- * Removes bubbles from the given package whose shortcut are not in the provided list of valid
- * shortcuts.
- */
- public void removeBubblesWithInvalidShortcuts(
- String packageName, List<ShortcutInfo> validShortcuts, int reason) {
-
- final Set<String> validShortcutIds = new HashSet<String>();
- for (ShortcutInfo info : validShortcuts) {
- validShortcutIds.add(info.getId());
- }
-
- final Predicate<Bubble> invalidBubblesFromPackage = bubble -> {
- final boolean bubbleIsFromPackage = packageName.equals(bubble.getPackageName());
- final boolean isShortcutBubble = bubble.hasMetadataShortcutId();
- if (!bubbleIsFromPackage || !isShortcutBubble) {
- return false;
- }
- final boolean hasShortcutIdAndValidShortcut =
- bubble.hasMetadataShortcutId()
- && bubble.getShortcutInfo() != null
- && bubble.getShortcutInfo().isEnabled()
- && validShortcutIds.contains(bubble.getShortcutInfo().getId());
- return bubbleIsFromPackage && !hasShortcutIdAndValidShortcut;
- };
-
- final Consumer<Bubble> removeBubble = bubble ->
- dismissBubbleWithKey(bubble.getKey(), reason);
-
- performActionOnBubblesMatching(getBubbles(), invalidBubblesFromPackage, removeBubble);
- performActionOnBubblesMatching(
- getOverflowBubbles(), invalidBubblesFromPackage, removeBubble);
- }
-
- /** Dismisses all bubbles from the given package. */
- public void removeBubblesWithPackageName(String packageName, int reason) {
- final Predicate<Bubble> bubbleMatchesPackage = bubble ->
- bubble.getPackageName().equals(packageName);
-
- final Consumer<Bubble> removeBubble = bubble ->
- dismissBubbleWithKey(bubble.getKey(), reason);
-
- performActionOnBubblesMatching(getBubbles(), bubbleMatchesPackage, removeBubble);
- performActionOnBubblesMatching(getOverflowBubbles(), bubbleMatchesPackage, removeBubble);
- }
-
- private void doAdd(Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doAdd: " + bubble);
- }
- mBubbles.add(0, bubble);
- mStateChange.addedBubble = bubble;
- // Adding the first bubble doesn't change the order
- mStateChange.orderChanged = mBubbles.size() > 1;
- if (!isExpanded()) {
- setSelectedBubbleInternal(mBubbles.get(0));
- }
- }
-
- private void trim() {
- if (mBubbles.size() > mMaxBubbles) {
- mBubbles.stream()
- // sort oldest first (ascending lastActivity)
- .sorted(Comparator.comparingLong(Bubble::getLastActivity))
- // skip the selected bubble
- .filter((b) -> !b.equals(mSelectedBubble))
- .findFirst()
- .ifPresent((b) -> doRemove(b.getKey(), BubbleController.DISMISS_AGED));
- }
- }
-
- private void doUpdate(Bubble bubble, boolean reorder) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doUpdate: " + bubble);
- }
- mStateChange.updatedBubble = bubble;
- if (!isExpanded() && reorder) {
- int prevPos = mBubbles.indexOf(bubble);
- mBubbles.remove(bubble);
- mBubbles.add(0, bubble);
- mStateChange.orderChanged = prevPos != 0;
- setSelectedBubbleInternal(mBubbles.get(0));
- }
- }
-
- /** Runs the given action on Bubbles that match the given predicate. */
- private void performActionOnBubblesMatching(
- List<Bubble> bubbles, Predicate<Bubble> predicate, Consumer<Bubble> action) {
- final List<Bubble> matchingBubbles = new ArrayList<>();
- for (Bubble bubble : bubbles) {
- if (predicate.test(bubble)) {
- matchingBubbles.add(bubble);
- }
- }
-
- for (Bubble matchingBubble : matchingBubbles) {
- action.accept(matchingBubble);
- }
- }
-
- private void doRemove(String key, @DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "doRemove: " + key);
- }
- // If it was pending remove it
- if (mPendingBubbles.containsKey(key)) {
- mPendingBubbles.remove(key);
- }
- int indexToRemove = indexForKey(key);
- if (indexToRemove == -1) {
- if (hasOverflowBubbleWithKey(key)
- && (reason == BubbleController.DISMISS_NOTIF_CANCEL
- || reason == BubbleController.DISMISS_GROUP_CANCELLED
- || reason == BubbleController.DISMISS_NO_LONGER_BUBBLE
- || reason == BubbleController.DISMISS_BLOCKED
- || reason == BubbleController.DISMISS_SHORTCUT_REMOVED
- || reason == BubbleController.DISMISS_PACKAGE_REMOVED)) {
-
- Bubble b = getOverflowBubbleWithKey(key);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Cancel overflow bubble: " + b);
- }
- if (b != null) {
- b.stopInflation();
- }
- mLogger.logOverflowRemove(b, reason);
- mOverflowBubbles.remove(b);
- mStateChange.bubbleRemoved(b, reason);
- mStateChange.removedOverflowBubble = b;
- }
- return;
- }
- Bubble bubbleToRemove = mBubbles.get(indexToRemove);
- bubbleToRemove.stopInflation();
- if (mBubbles.size() == 1) {
- // Going to become empty, handle specially.
- setExpandedInternal(false);
- // Don't use setSelectedBubbleInternal because we don't want to trigger an applyUpdate
- mSelectedBubble = null;
- }
- if (indexToRemove < mBubbles.size() - 1) {
- // Removing anything but the last bubble means positions will change.
- mStateChange.orderChanged = true;
- }
- mBubbles.remove(indexToRemove);
- mStateChange.bubbleRemoved(bubbleToRemove, reason);
- if (!isExpanded()) {
- mStateChange.orderChanged |= repackAll();
- }
-
- overflowBubble(reason, bubbleToRemove);
-
- // Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
- if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
- // Move selection to the new bubble at the same position.
- int newIndex = Math.min(indexToRemove, mBubbles.size() - 1);
- Bubble newSelected = mBubbles.get(newIndex);
- setSelectedBubbleInternal(newSelected);
- }
- maybeSendDeleteIntent(reason, bubbleToRemove);
- }
-
- void overflowBubble(@DismissReason int reason, Bubble bubble) {
- if (bubble.getPendingIntentCanceled()
- || !(reason == BubbleController.DISMISS_AGED
- || reason == BubbleController.DISMISS_USER_GESTURE)) {
- return;
- }
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflowing: " + bubble);
- }
- mLogger.logOverflowAdd(bubble, reason);
- mOverflowBubbles.add(0, bubble);
- mStateChange.addedOverflowBubble = bubble;
- bubble.stopInflation();
- if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
- // Remove oldest bubble.
- Bubble oldest = mOverflowBubbles.get(mOverflowBubbles.size() - 1);
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflow full. Remove: " + oldest);
- }
- mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
- mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_MAX_REACHED);
- mOverflowBubbles.remove(oldest);
- mStateChange.removedOverflowBubble = oldest;
- }
- }
-
- public void dismissAll(@DismissReason int reason) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "dismissAll: reason=" + reason);
- }
- if (mBubbles.isEmpty()) {
- return;
- }
- setExpandedInternal(false);
- setSelectedBubbleInternal(null);
- while (!mBubbles.isEmpty()) {
- doRemove(mBubbles.get(0).getKey(), reason);
- }
- dispatchPendingChanges();
- }
-
- private void dispatchPendingChanges() {
- if (mListener != null && mStateChange.anythingChanged()) {
- mListener.applyUpdate(mStateChange);
- }
- mStateChange = new Update(mBubbles, mOverflowBubbles);
- }
-
- /**
- * Requests a change to the selected bubble.
- *
- * @param bubble the new selected bubble
- */
- private void setSelectedBubbleInternal(@Nullable Bubble bubble) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
- }
- if (!mShowingOverflow && Objects.equals(bubble, mSelectedBubble)) {
- return;
- }
- // Otherwise, if we are showing the overflow menu, return to the previously selected bubble.
-
- if (bubble != null && !mBubbles.contains(bubble) && !mOverflowBubbles.contains(bubble)) {
- Log.e(TAG, "Cannot select bubble which doesn't exist!"
- + " (" + bubble + ") bubbles=" + mBubbles);
- return;
- }
- if (mExpanded && bubble != null) {
- bubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
- }
- mSelectedBubble = bubble;
- mStateChange.selectedBubble = bubble;
- mStateChange.selectionChanged = true;
- }
-
- void setCurrentUserId(int uid) {
- mCurrentUserId = uid;
- }
-
- /**
- * Logs the bubble UI event.
- *
- * @param provider The bubble view provider that is being interacted on. Null value indicates
- * that the user interaction is not specific to one bubble.
- * @param action The user interaction enum
- * @param packageName SystemUI package
- * @param bubbleCount Number of bubbles in the stack
- * @param bubbleIndex Index of bubble in the stack
- * @param normalX Normalized x position of the stack
- * @param normalY Normalized y position of the stack
- */
- void logBubbleEvent(@Nullable BubbleViewProvider provider, int action, String packageName,
- int bubbleCount, int bubbleIndex, float normalX, float normalY) {
- if (provider == null) {
- mLogger.logStackUiChanged(packageName, action, bubbleCount, normalX, normalY);
- } else if (provider.getKey().equals(BubbleOverflow.KEY)) {
- if (action == FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED) {
- mLogger.logShowOverflow(packageName, mCurrentUserId);
- }
- } else {
- mLogger.logBubbleUiChanged((Bubble) provider, packageName, action, bubbleCount, normalX,
- normalY, bubbleIndex);
- }
- }
-
- /**
- * Requests a change to the expanded state.
- *
- * @param shouldExpand the new requested state
- */
- private void setExpandedInternal(boolean shouldExpand) {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "setExpandedInternal: shouldExpand=" + shouldExpand);
- }
- if (mExpanded == shouldExpand) {
- return;
- }
- if (shouldExpand) {
- if (mBubbles.isEmpty()) {
- Log.e(TAG, "Attempt to expand stack when empty!");
- return;
- }
- if (mSelectedBubble == null) {
- Log.e(TAG, "Attempt to expand stack without selected bubble!");
- return;
- }
- mSelectedBubble.markAsAccessedAt(mTimeSource.currentTimeMillis());
- mStateChange.orderChanged |= repackAll();
- } else if (!mBubbles.isEmpty()) {
- // Apply ordering and grouping rules from expanded -> collapsed, then save
- // the result.
- mStateChange.orderChanged |= repackAll();
- // Save the state which should be returned to when expanded (with no other changes)
-
- if (mShowingOverflow) {
- // Show previously selected bubble instead of overflow menu on next expansion.
- setSelectedBubbleInternal(mSelectedBubble);
- }
- if (mBubbles.indexOf(mSelectedBubble) > 0) {
- // Move the selected bubble to the top while collapsed.
- int index = mBubbles.indexOf(mSelectedBubble);
- if (index != 0) {
- mBubbles.remove(mSelectedBubble);
- mBubbles.add(0, mSelectedBubble);
- mStateChange.orderChanged = true;
- }
- }
- }
- mExpanded = shouldExpand;
- mStateChange.expanded = shouldExpand;
- mStateChange.expandedChanged = true;
- }
-
- private static long sortKey(Bubble bubble) {
- return bubble.getLastActivity();
- }
-
- /**
- * This applies a full sort and group pass to all existing bubbles.
- * Bubbles are sorted by lastUpdated descending.
- *
- * @return true if the position of any bubbles changed as a result
- */
- private boolean repackAll() {
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "repackAll()");
- }
- if (mBubbles.isEmpty()) {
- return false;
- }
- List<Bubble> repacked = new ArrayList<>(mBubbles.size());
- // Add bubbles, freshest to oldest
- mBubbles.stream()
- .sorted(BUBBLES_BY_SORT_KEY_DESCENDING)
- .forEachOrdered(repacked::add);
- if (repacked.equals(mBubbles)) {
- return false;
- }
- mBubbles.clear();
- mBubbles.addAll(repacked);
- return true;
- }
-
- private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final Bubble bubble) {
- if (reason != BubbleController.DISMISS_USER_GESTURE) return;
- PendingIntent deleteIntent = bubble.getDeleteIntent();
- if (deleteIntent == null) return;
- try {
- deleteIntent.send();
- } catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Failed to send delete intent for bubble with key: " + bubble.getKey());
- }
- }
-
- private int indexForKey(String key) {
- for (int i = 0; i < mBubbles.size(); i++) {
- Bubble bubble = mBubbles.get(i);
- if (bubble.getKey().equals(key)) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * The set of bubbles in row.
- */
- @VisibleForTesting(visibility = PACKAGE)
- public List<Bubble> getBubbles() {
- return Collections.unmodifiableList(mBubbles);
- }
-
- /**
- * The set of bubbles in overflow.
- */
- @VisibleForTesting(visibility = PRIVATE)
- List<Bubble> getOverflowBubbles() {
- return Collections.unmodifiableList(mOverflowBubbles);
- }
-
- @VisibleForTesting(visibility = PRIVATE)
- @Nullable
- Bubble getAnyBubbleWithkey(String key) {
- Bubble b = getBubbleInStackWithKey(key);
- if (b == null) {
- b = getOverflowBubbleWithKey(key);
- }
- return b;
- }
-
- @VisibleForTesting(visibility = PRIVATE)
- @Nullable
- Bubble getBubbleInStackWithKey(String key) {
- for (int i = 0; i < mBubbles.size(); i++) {
- Bubble bubble = mBubbles.get(i);
- if (bubble.getKey().equals(key)) {
- return bubble;
- }
- }
- return null;
- }
-
- @Nullable
- Bubble getBubbleWithView(View view) {
- for (int i = 0; i < mBubbles.size(); i++) {
- Bubble bubble = mBubbles.get(i);
- if (bubble.getIconView() != null && bubble.getIconView().equals(view)) {
- return bubble;
- }
- }
- return null;
- }
-
- @VisibleForTesting(visibility = PRIVATE)
- Bubble getOverflowBubbleWithKey(String key) {
- for (int i = 0; i < mOverflowBubbles.size(); i++) {
- Bubble bubble = mOverflowBubbles.get(i);
- if (bubble.getKey().equals(key)) {
- return bubble;
- }
- }
- return null;
- }
-
- @VisibleForTesting(visibility = PRIVATE)
- void setTimeSource(TimeSource timeSource) {
- mTimeSource = timeSource;
- }
-
- public void setListener(Listener listener) {
- mListener = listener;
- }
-
- /**
- * Set maximum number of bubbles allowed in overflow.
- * This method should only be used in tests, not in production.
- */
- @VisibleForTesting
- void setMaxOverflowBubbles(int maxOverflowBubbles) {
- mMaxOverflowBubbles = maxOverflowBubbles;
- }
-
- /**
- * Description of current bubble data state.
- */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.print("selected: ");
- pw.println(mSelectedBubble != null
- ? mSelectedBubble.getKey()
- : "null");
- pw.print("expanded: ");
- pw.println(mExpanded);
-
- pw.print("stack bubble count: ");
- pw.println(mBubbles.size());
- for (Bubble bubble : mBubbles) {
- bubble.dump(fd, pw, args);
- }
-
- pw.print("overflow bubble count: ");
- pw.println(mOverflowBubbles.size());
- for (Bubble bubble : mOverflowBubbles) {
- bubble.dump(fd, pw, args);
- }
-
- pw.print("summaryKeys: ");
- pw.println(mSuppressedGroupKeys.size());
- for (String key : mSuppressedGroupKeys.keySet()) {
- pw.println(" suppressing: " + key);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
deleted file mode 100644
index 2ab9e8734bef..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ /dev/null
@@ -1,178 +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.
- */
-package com.android.systemui.bubbles
-
-import android.annotation.SuppressLint
-import android.annotation.UserIdInt
-import android.content.Context
-import android.content.pm.LauncherApps
-import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED
-import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC
-import android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
-import android.os.UserHandle
-import android.util.Log
-import com.android.systemui.bubbles.storage.BubbleEntity
-import com.android.systemui.bubbles.storage.BubblePersistentRepository
-import com.android.systemui.bubbles.storage.BubbleVolatileRepository
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.cancelAndJoin
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
-
-internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps) {
- private val volatileRepository = BubbleVolatileRepository(launcherApps)
- private val persistentRepository = BubblePersistentRepository(context)
-
- private val ioScope = CoroutineScope(Dispatchers.IO)
- private val uiScope = CoroutineScope(Dispatchers.Main)
- private var job: Job? = null
-
- /**
- * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
- * asynchronously.
- */
- fun addBubble(@UserIdInt userId: Int, bubble: Bubble) = addBubbles(userId, listOf(bubble))
-
- /**
- * Adds the bubble in memory, then persists the snapshot after adding the bubble to disk
- * asynchronously.
- */
- fun addBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
- if (DEBUG) Log.d(TAG, "adding ${bubbles.size} bubbles")
- val entities = transform(userId, bubbles).also(volatileRepository::addBubbles)
- if (entities.isNotEmpty()) persistToDisk()
- }
-
- /**
- * Removes the bubbles from memory, then persists the snapshot to disk asynchronously.
- */
- fun removeBubbles(@UserIdInt userId: Int, bubbles: List<Bubble>) {
- if (DEBUG) Log.d(TAG, "removing ${bubbles.size} bubbles")
- val entities = transform(userId, bubbles).also(volatileRepository::removeBubbles)
- if (entities.isNotEmpty()) persistToDisk()
- }
-
- private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
- return bubbles.mapNotNull { b ->
- BubbleEntity(
- userId,
- b.packageName,
- b.metadataShortcutId ?: return@mapNotNull null,
- b.key,
- b.rawDesiredHeight,
- b.rawDesiredHeightResId,
- b.title
- )
- }
- }
-
- /**
- * Persists the bubbles to disk. When being called multiple times, it waits for first ongoing
- * write operation to finish then run another write operation exactly once.
- *
- * e.g.
- * Job A started -> blocking I/O
- * Job B started, cancels A, wait for blocking I/O in A finishes
- * Job C started, cancels B, wait for job B to finish
- * Job D started, cancels C, wait for job C to finish
- * Job A completed
- * Job B resumes and reaches yield() and is then cancelled
- * Job C resumes and reaches yield() and is then cancelled
- * Job D resumes and performs another blocking I/O
- */
- private fun persistToDisk() {
- val prev = job
- job = ioScope.launch {
- // if there was an ongoing disk I/O operation, they can be cancelled
- prev?.cancelAndJoin()
- // check for cancellation before disk I/O
- yield()
- // save to disk
- persistentRepository.persistsToDisk(volatileRepository.bubbles)
- }
- }
-
- /**
- * Load bubbles from disk.
- */
- @SuppressLint("WrongConstant")
- fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
- /**
- * Load BubbleEntity from disk.
- * e.g.
- * [
- * BubbleEntity(0, "com.example.messenger", "id-2"),
- * BubbleEntity(10, "com.example.chat", "my-id1")
- * BubbleEntity(0, "com.example.messenger", "id-1")
- * ]
- */
- val entities = persistentRepository.readFromDisk()
- volatileRepository.addBubbles(entities)
- /**
- * Extract userId/packageName from these entities.
- * e.g.
- * [
- * ShortcutKey(0, "com.example.messenger"), ShortcutKey(0, "com.example.chat")
- * ]
- */
- val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet()
- /**
- * Retrieve shortcuts with given userId/packageName combination, then construct a mapping
- * from the userId/packageName pair to a list of associated ShortcutInfo.
- * e.g.
- * {
- * ShortcutKey(0, "com.example.messenger") -> [
- * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"),
- * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2")
- * ]
- * ShortcutKey(10, "com.example.chat") -> [
- * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"),
- * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3")
- * ]
- * }
- */
- val shortcutMap = shortcutKeys.flatMap { key ->
- launcherApps.getShortcuts(
- LauncherApps.ShortcutQuery()
- .setPackage(key.pkg)
- .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId))
- ?: emptyList()
- }.groupBy { ShortcutKey(it.userId, it.`package`) }
- // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them
- // into Bubble.
- val bubbles = entities.mapNotNull { entity ->
- shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
- ?.firstOrNull { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
- ?.let { shortcutInfo -> Bubble(
- entity.key,
- shortcutInfo,
- entity.desiredHeight,
- entity.desiredHeightResId,
- entity.title
- ) }
- }
- uiScope.launch { cb(bubbles) }
- }
-}
-
-data class ShortcutKey(val userId: Int, val pkg: String)
-
-private const val TAG = "BubbleDataRepository"
-private const val DEBUG = false
-private const val SHORTCUT_QUERY_FLAG =
- FLAG_MATCH_DYNAMIC or FLAG_MATCH_PINNED_BY_ANY_LAUNCHER or FLAG_MATCH_CACHED \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
deleted file mode 100644
index d98fee399470..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDebugConfig.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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.bubbles;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import java.util.List;
-
-/**
- * Common class for the various debug {@link android.util.Log} output configuration in the Bubbles
- * package.
- */
-public class BubbleDebugConfig {
-
- // All output logs in the Bubbles package use the {@link #TAG_BUBBLES} string for tagging their
- // log output. This makes it easy to identify the origin of the log message when sifting
- // through a large amount of log output from multiple sources. However, it also makes trying
- // to figure-out the origin of a log message while debugging the Bubbles a little painful. By
- // setting this constant to true, log messages from the Bubbles package will be tagged with
- // their class names instead fot the generic tag.
- static final boolean TAG_WITH_CLASS_NAME = false;
-
- // Default log tag for the Bubbles package.
- static final String TAG_BUBBLES = "Bubbles";
-
- static final boolean DEBUG_BUBBLE_CONTROLLER = false;
- static final boolean DEBUG_BUBBLE_DATA = false;
- static final boolean DEBUG_BUBBLE_STACK_VIEW = false;
- static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
- static final boolean DEBUG_EXPERIMENTS = true;
- static final boolean DEBUG_OVERFLOW = false;
- static final boolean DEBUG_USER_EDUCATION = false;
-
- private static final boolean FORCE_SHOW_USER_EDUCATION = false;
- private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
- "force_show_bubbles_user_education";
-
- /**
- * @return whether we should force show user education for bubbles. Used for debugging & demos.
- */
- static boolean forceShowUserEducation(Context context) {
- boolean forceShow = Settings.Secure.getInt(context.getContentResolver(),
- FORCE_SHOW_USER_EDUCATION_SETTING, 0) != 0;
- return FORCE_SHOW_USER_EDUCATION || forceShow;
- }
-
- static String formatBubblesString(List<Bubble> bubbles, BubbleViewProvider selected) {
- StringBuilder sb = new StringBuilder();
- for (Bubble bubble : bubbles) {
- if (bubble == null) {
- sb.append(" <null> !!!!!\n");
- } else {
- boolean isSelected = (selected != null
- && selected.getKey() != BubbleOverflow.KEY
- && bubble == selected);
- String arrow = isSelected ? "=>" : " ";
- sb.append(String.format("%s Bubble{act=%12d, showInShade=%d, key=%s}\n",
- arrow,
- bubble.getLastActivity(),
- (bubble.showInShade() ? 1 : 0),
- bubble.getKey()));
- }
- }
- return sb.toString();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
deleted file mode 100644
index 6a1302518699..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleEntry.java
+++ /dev/null
@@ -1,98 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import android.app.Notification.BubbleMetadata;
-import android.app.NotificationManager.Policy;
-import android.service.notification.NotificationListenerService.Ranking;
-import android.service.notification.StatusBarNotification;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Represents a notification with needed data and flag for bubbles.
- *
- * @see Bubble
- */
-public class BubbleEntry {
-
- private StatusBarNotification mSbn;
- private Ranking mRanking;
-
- private boolean mIsClearable;
- private boolean mShouldSuppressNotificationDot;
- private boolean mShouldSuppressNotificationList;
- private boolean mShouldSuppressPeek;
-
- public BubbleEntry(@NonNull StatusBarNotification sbn,
- Ranking ranking, boolean isClearable, boolean shouldSuppressNotificationDot,
- boolean shouldSuppressNotificationList, boolean shouldSuppressPeek) {
- mSbn = sbn;
- mRanking = ranking;
-
- mIsClearable = isClearable;
- mShouldSuppressNotificationDot = shouldSuppressNotificationDot;
- mShouldSuppressNotificationList = shouldSuppressNotificationList;
- mShouldSuppressPeek = shouldSuppressPeek;
- }
-
- /** @return the {@link StatusBarNotification} for this entry. */
- @NonNull
- public StatusBarNotification getStatusBarNotification() {
- return mSbn;
- }
-
- /** @return the {@link Ranking} for this entry. */
- public Ranking getRanking() {
- return mRanking;
- }
-
- /** @return the key in the {@link StatusBarNotification}. */
- public String getKey() {
- return mSbn.getKey();
- }
-
- /** @return the {@link BubbleMetadata} in the {@link StatusBarNotification}. */
- @Nullable
- public BubbleMetadata getBubbleMetadata() {
- return getStatusBarNotification().getNotification().getBubbleMetadata();
- }
-
- /** @return true if this notification is clearable. */
- public boolean isClearable() {
- return mIsClearable;
- }
-
- /** @return true if {@link Policy#SUPPRESSED_EFFECT_BADGE} set for this notification. */
- public boolean shouldSuppressNotificationDot() {
- return mShouldSuppressNotificationDot;
- }
-
- /**
- * @return true if {@link Policy#SUPPRESSED_EFFECT_NOTIFICATION_LIST}
- * set for this notification.
- */
- public boolean shouldSuppressNotificationList() {
- return mShouldSuppressNotificationList;
- }
-
- /** @return true if {@link Policy#SUPPRESSED_EFFECT_PEEK} set for this notification. */
- public boolean shouldSuppressPeek() {
- return mShouldSuppressPeek;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
deleted file mode 100644
index 98a2257d2daa..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ /dev/null
@@ -1,630 +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.bubbles;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.systemui.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_CONTROLLER;
-
-import android.annotation.NonNull;
-import android.annotation.SuppressLint;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-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.Outline;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.Nullable;
-
-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.statusbar.AlphaOptimizedButton;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Container for the expanded bubble view, handles rendering the caret and settings icon.
- */
-public class BubbleExpandedView extends LinearLayout {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
-
- // The triangle pointing to the expanded view
- private View mPointerView;
- private int mPointerMargin;
- @Nullable private int[] mExpandedViewContainerLocation;
-
- private AlphaOptimizedButton mSettingsIcon;
- private TaskView mTaskView;
-
- private int mTaskId = INVALID_TASK_ID;
-
- private boolean mImeVisible;
- private boolean mNeedsNewHeight;
-
- private Point mDisplaySize;
- private int mMinHeight;
- private int mOverflowHeight;
- private int mSettingsIconHeight;
- private int mPointerWidth;
- private int mPointerHeight;
- private ShapeDrawable mPointerDrawable;
- private int mExpandedViewPadding;
- private float mCornerRadius = 0f;
-
- @Nullable private Bubble mBubble;
- private PendingIntent mPendingIntent;
-
- private boolean mIsOverflow;
-
- private Bubbles mBubbles = Dependency.get(Bubbles.class);
- private WindowManager mWindowManager;
- private BubbleStackView mStackView;
-
- /**
- * Container for the ActivityView that has a solid, round-rect background that shows if the
- * ActivityView hasn't loaded.
- */
- private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext());
-
- private final TaskView.Listener mTaskViewListener = new TaskView.Listener() {
- private boolean mInitialized = false;
- private boolean mDestroyed = false;
-
- @Override
- public void onInitialized() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewReady: destroyed=" + mDestroyed
- + " initialized=" + mInitialized
- + " bubble=" + getBubbleKey());
- }
-
- if (mDestroyed || mInitialized) {
- return;
- }
- // Custom options so there is no activity transition animation
- ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
- 0 /* enterResId */, 0 /* exitResId */);
-
- // TODO: I notice inconsistencies in lifecycle
- // Post to keep the lifecycle normal
- post(() -> {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onActivityViewReady: calling startActivity, bubble="
- + getBubbleKey());
- }
- try {
- if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
- mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
- options, null /* sourceBounds */);
- } else {
- Intent fillInIntent = new Intent();
- // Apply flags to make behaviour match documentLaunchMode=always.
- fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- if (mBubble != null) {
- mBubble.setIntentActive();
- }
- mTaskView.startActivity(mPendingIntent, fillInIntent, options);
- }
- } catch (RuntimeException e) {
- // If there's a runtime exception here then there's something
- // wrong with the intent, we can't really recover / try to populate
- // the bubble again so we'll just remove it.
- Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
- + ", " + e.getMessage() + "; removing bubble");
- mBubbles.removeBubble(getBubbleKey(),
- BubbleController.DISMISS_INVALID_INTENT);
- }
- });
- mInitialized = true;
- }
-
- @Override
- public void onReleased() {
- mDestroyed = true;
- }
-
- @Override
- public void onTaskCreated(int taskId, ComponentName name) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskCreated: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
- // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
- mTaskId = taskId;
-
- // With the task org, the taskAppeared callback will only happen once the task has
- // already drawn
- setContentVisibility(true);
- }
-
- @Override
- public void onTaskVisibilityChanged(int taskId, boolean visible) {
- setContentVisibility(visible);
- }
-
- @Override
- public void onTaskRemovalStarted(int taskId) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId
- + " bubble=" + getBubbleKey());
- }
- if (mBubble != null) {
- // Must post because this is called from a binder thread.
- post(() -> mBubbles.removeBubble(mBubble.getKey(),
- BubbleController.DISMISS_TASK_FINISHED));
- }
- }
-
- @Override
- public void onBackPressedOnTaskRoot(int taskId) {
- if (mTaskId == taskId && mStackView.isExpanded()) {
- mBubbles.collapseStack();
- }
- }
- };
-
- public BubbleExpandedView(Context context) {
- this(context, null);
- }
-
- public BubbleExpandedView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- updateDimensions();
- }
-
- void updateDimensions() {
- mDisplaySize = new Point();
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
- // Get the real size -- this includes screen decorations (notches, statusbar, navbar).
- mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
- Resources res = getResources();
- 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);
- }
-
- @SuppressLint("ClickableViewAccessibility")
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
-
- Resources res = getResources();
- mPointerView = findViewById(R.id.pointer_view);
- 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.setVisibility(INVISIBLE);
-
- mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
- R.dimen.bubble_manage_button_height);
- mSettingsIcon = findViewById(R.id.settings_button);
-
- mTaskView = new TaskView(mContext, mBubbles.getTaskManager());
- // Set ActivityView's alpha value as zero, since there is no view content to be shown.
- setContentVisibility(false);
-
- mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
- }
- });
- mExpandedViewContainer.setClipToOutline(true);
- mExpandedViewContainer.addView(mTaskView);
- mExpandedViewContainer.setLayoutParams(
- new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- addView(mExpandedViewContainer);
-
- // Expanded stack layout, top to bottom:
- // Expanded view container
- // ==> bubble row
- // ==> expanded view
- // ==> activity view
- // ==> manage button
- bringChildToFront(mTaskView);
- bringChildToFront(mSettingsIcon);
- mTaskView.setListener(mTaskViewListener);
-
- applyThemeAttrs();
-
- mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
- setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding,
- mExpandedViewPadding);
- setOnTouchListener((view, motionEvent) -> {
- if (mTaskView == null) {
- return false;
- }
-
- final Rect avBounds = new Rect();
- mTaskView.getBoundsOnScreen(avBounds);
-
- // Consume and ignore events on the expanded view padding that are within the
- // ActivityView's vertical bounds. These events are part of a back gesture, and so they
- // should not collapse the stack (which all other touches on areas around the AV would
- // do).
- if (motionEvent.getRawY() >= avBounds.top
- && motionEvent.getRawY() <= avBounds.bottom
- && (motionEvent.getRawX() < avBounds.left
- || motionEvent.getRawX() > avBounds.right)) {
- return true;
- }
-
- return false;
- });
-
- // BubbleStackView is forced LTR, but we want to respect the locale for expanded view layout
- // so the Manage button appears on the right.
- setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
- }
-
- private String getBubbleKey() {
- return mBubble != null ? mBubble.getKey() : "null";
- }
-
- /**
- * Sets whether the surface displaying app content should sit on top. This is useful for
- * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
- * being dragged out, the manage menu) this is set to false, otherwise it should be true.
- */
- void setSurfaceZOrderedOnTop(boolean onTop) {
- if (mTaskView == null) {
- return;
- }
- mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */);
- }
-
- void setImeVisible(boolean visible) {
- mImeVisible = visible;
- if (!mImeVisible && mNeedsNewHeight) {
- updateHeight();
- }
- }
-
- /** Return a GraphicBuffer with the contents of the task view surface. */
- @Nullable
- SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() {
- if (mTaskView == null) {
- return null;
- }
- return SurfaceControl.captureLayers(
- mTaskView.getSurfaceControl(),
- new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()),
- 1 /* scale */);
- }
-
- int[] getTaskViewLocationOnScreen() {
- if (mTaskView != null) {
- return mTaskView.getLocationOnScreen();
- } else {
- return new int[]{0, 0};
- }
- }
-
- // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this
- void setManageClickListener(OnClickListener manageClickListener) {
- mSettingsIcon.setOnClickListener(manageClickListener);
- }
-
- /**
- * Updates the obscured touchable region for the task surface. 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() {
- if (mTaskView != null) {
- mTaskView.onLocationChanged();
- }
- }
-
- void applyThemeAttrs() {
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
- android.R.attr.dialogCornerRadius,
- android.R.attr.colorBackgroundFloating});
- mCornerRadius = ta.getDimensionPixelSize(0, 0);
- mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE));
- ta.recycle();
-
- if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
- mContext.getResources())) {
- mTaskView.setCornerRadius(mCornerRadius);
- }
-
- final int mode =
- getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- switch (mode) {
- case Configuration.UI_MODE_NIGHT_NO:
- mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_light));
- break;
- case Configuration.UI_MODE_NIGHT_YES:
- mPointerDrawable.setTint(getResources().getColor(R.color.bubbles_dark));
- break;
- }
- mPointerView.setBackground(mPointerDrawable);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mImeVisible = false;
- mNeedsNewHeight = false;
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey());
- }
- }
-
- /**
- * Set visibility of contents in the expanded state.
- *
- * @param visibility {@code true} if the contents should be visible on the screen.
- *
- * Note that this contents visibility doesn't affect visibility at {@link android.view.View},
- * and setting {@code false} actually means rendering the contents in transparent.
- */
- void setContentVisibility(boolean visibility) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "setContentVisibility: visibility=" + visibility
- + " bubble=" + getBubbleKey());
- }
- final float alpha = visibility ? 1f : 0f;
-
- mPointerView.setAlpha(alpha);
- if (mTaskView == null) {
- return;
- }
- if (alpha != mTaskView.getAlpha()) {
- mTaskView.setAlpha(alpha);
- }
- }
-
- @Nullable
- View getTaskView() {
- return mTaskView;
- }
-
- int getTaskId() {
- return mTaskId;
- }
-
- void setStackView(BubbleStackView stackView) {
- mStackView = stackView;
- }
-
- public void setOverflow(boolean overflow) {
- mIsOverflow = overflow;
-
- Intent target = new Intent(mContext, BubbleOverflowActivity.class);
- Bundle extras = new Bundle();
- extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mBubbles));
- target.putExtras(extras);
- mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */,
- target, PendingIntent.FLAG_UPDATE_CURRENT);
- mSettingsIcon.setVisibility(GONE);
- }
-
- /**
- * Sets the bubble used to populate this view.
- */
- void update(Bubble bubble) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "update: bubble=" + bubble);
- }
- if (mStackView == null) {
- Log.w(TAG, "Stack is null for bubble: " + bubble);
- return;
- }
- boolean isNew = mBubble == null || didBackingContentChange(bubble);
- if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
- mBubble = bubble;
- mSettingsIcon.setContentDescription(getResources().getString(
- R.string.bubbles_settings_button_description, bubble.getAppName()));
- mSettingsIcon.setAccessibilityDelegate(
- new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host,
- AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- // On focus, have TalkBack say
- // "Actions available. Use swipe up then right to view."
- // in addition to the default "double tap to activate".
- mStackView.setupLocalMenu(info);
- }
- });
-
- if (isNew) {
- mPendingIntent = mBubble.getBubbleIntent();
- if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) {
- setContentVisibility(false);
- mTaskView.setVisibility(VISIBLE);
- }
- }
- applyThemeAttrs();
- } else {
- Log.w(TAG, "Trying to update entry with different key, new bubble: "
- + bubble.getKey() + " old bubble: " + bubble.getKey());
- }
- }
-
- /**
- * Bubbles are backed by a pending intent or a shortcut, once the activity is
- * started we never change it / restart it on notification updates -- unless the bubbles'
- * backing data switches.
- *
- * This indicates if the new bubble is backed by a different data source than what was
- * previously shown here (e.g. previously a pending intent & now a shortcut).
- *
- * @param newBubble the bubble this view is being updated with.
- * @return true if the backing content has changed.
- */
- private boolean didBackingContentChange(Bubble newBubble) {
- boolean prevWasIntentBased = mBubble != null && mPendingIntent != null;
- boolean newIsIntentBased = newBubble.getBubbleIntent() != null;
- return prevWasIntentBased != newIsIntentBased;
- }
-
- void updateHeight() {
- if (mExpandedViewContainerLocation == null) {
- return;
- }
-
- if (mBubble != null || mIsOverflow) {
- float desiredHeight = mOverflowHeight;
- if (!mIsOverflow) {
- desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
- }
- float height = Math.min(desiredHeight, getMaxExpandedHeight());
- height = Math.max(height, mIsOverflow ? mOverflowHeight : mMinHeight);
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams();
- mNeedsNewHeight = lp.height != height;
- if (!mImeVisible) {
- // If the ime is visible... don't adjust the height because that will cause
- // a configuration change and the ime will be lost.
- lp.height = (int) height;
- mTaskView.setLayoutParams(lp);
- mNeedsNewHeight = false;
- }
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
- + " height=" + height
- + " mNeedsNewHeight=" + mNeedsNewHeight);
- }
- }
- }
-
- private int getMaxExpandedHeight() {
- mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
- int expandedContainerY = mExpandedViewContainerLocation != null
- ? mExpandedViewContainerLocation[1]
- : 0;
- int bottomInset = getRootWindowInsets() != null
- ? getRootWindowInsets().getStableInsetBottom()
- : 0;
-
- return mDisplaySize.y
- - expandedContainerY
- - getPaddingTop()
- - getPaddingBottom()
- - mSettingsIconHeight
- - mPointerHeight
- - mPointerMargin - bottomInset;
- }
-
- /**
- * Update appearance of the expanded view being displayed.
- *
- * @param containerLocationOnScreen The location on-screen of the container the expanded view is
- * added to. This allows us to calculate max height without
- * waiting for layout.
- */
- public void updateView(int[] containerLocationOnScreen) {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "updateView: bubble="
- + getBubbleKey());
- }
- mExpandedViewContainerLocation = containerLocationOnScreen;
- if (mTaskView != null
- && mTaskView.getVisibility() == VISIBLE
- && mTaskView.isAttachedToWindow()) {
- updateHeight();
- mTaskView.onLocationChanged();
- }
- }
-
- /**
- * Set the x position that the tip of the triangle should point to.
- */
- public void setPointerPosition(float x) {
- float halfPointerWidth = mPointerWidth / 2f;
- float pointerLeft = x - halfPointerWidth - mExpandedViewPadding;
- mPointerView.setTranslationX(pointerLeft);
- mPointerView.setVisibility(VISIBLE);
- }
-
- /**
- * Position of the manage button displayed in the expanded view. Used for placing user
- * education about the manage button.
- */
- public void getManageButtonBoundsOnScreen(Rect rect) {
- mSettingsIcon.getBoundsOnScreen(rect);
- }
-
- /**
- * Cleans up anything related to the task and TaskView.
- */
- public void cleanUpExpandedState() {
- if (DEBUG_BUBBLE_EXPANDED_VIEW) {
- Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
- }
- if (mTaskView != null) {
- mTaskView.release();
- }
- if (mTaskView != null) {
- removeView(mTaskView);
- mTaskView = null;
- }
- }
-
- /**
- * Description of current expanded view state.
- */
- public void dump(
- @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
- pw.print("BubbleExpandedView");
- pw.print(" taskId: "); pw.println(mTaskId);
- pw.print(" stackView: "); pw.println(mStackView);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
deleted file mode 100644
index ffb650d62064..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * 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.bubbles;
-
-import static android.app.Notification.EXTRA_MESSAGES;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.Person;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Color;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.os.Parcelable;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.util.Log;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Common class for experiments controlled via secure settings.
- */
-public class BubbleExperimentConfig {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
-
- private static final int BUBBLE_HEIGHT = 10000;
-
- private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble";
- private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false;
-
- private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble";
- private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false;
-
- private static final String ALLOW_SHORTCUTS_TO_BUBBLE = "allow_shortcuts_to_bubble";
- private static final boolean ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT = false;
-
- private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
-
- /**
- * When true, if a notification has the information necessary to bubble (i.e. valid
- * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
- * object will be created by the system and added to the notification.
- * <p>
- * This does not produce a bubble, only adds the metadata based on the notification info.
- */
- static boolean allowAnyNotifToBubble(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ALLOW_ANY_NOTIF_TO_BUBBLE,
- ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
- }
-
- /**
- * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
- * are using {@link Notification.MessagingStyle} and have remote input.
- */
- static boolean allowMessageNotifsToBubble(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
- ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
- }
-
- /**
- * When true, if the notification is able to bubble via {@link #allowAnyNotifToBubble(Context)}
- * or {@link #allowMessageNotifsToBubble(Context)} or via normal BubbleMetadata, then a new
- * BubbleMetadata object is constructed based on the shortcut info.
- * <p>
- * This does not produce a bubble, only adds the metadata based on shortcut info.
- */
- static boolean useShortcutInfoToBubble(Context context) {
- return Settings.Secure.getInt(context.getContentResolver(),
- ALLOW_SHORTCUTS_TO_BUBBLE,
- ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
- }
-
- /**
- * Returns whether the provided package is whitelisted to bubble.
- */
- static boolean isPackageWhitelistedToAutoBubble(Context context, String packageName) {
- String unsplitList = Settings.Secure.getString(context.getContentResolver(),
- WHITELISTED_AUTO_BUBBLE_APPS);
- if (unsplitList != null) {
- // We expect the list to be separated by commas and no white space (but we trim in case)
- String[] packageList = unsplitList.split(",");
- for (int i = 0; i < packageList.length; i++) {
- if (packageList[i].trim().equals(packageName)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
- * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
- * the notification has necessary info for BubbleMetadata.
- *
- * @return whether an adjustment was made.
- */
- static boolean adjustForExperiments(Context context, NotificationEntry entry,
- boolean previouslyUserCreated, boolean userBlocked) {
- Notification.BubbleMetadata metadata = null;
- boolean addedMetadata = false;
- boolean whiteListedToAutoBubble =
- isPackageWhitelistedToAutoBubble(context, entry.getSbn().getPackageName());
-
- Notification notification = entry.getSbn().getNotification();
- boolean isMessage = Notification.MessagingStyle.class.equals(
- notification.getNotificationStyle());
- boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
- || allowAnyNotifToBubble(context);
-
- boolean useShortcutInfo = useShortcutInfoToBubble(context);
- String shortcutId = entry.getSbn().getNotification().getShortcutId();
-
- boolean hasMetadata = entry.getBubbleMetadata() != null;
- if ((!hasMetadata && (previouslyUserCreated || bubbleNotifForExperiment))
- || useShortcutInfo) {
- if (DEBUG_EXPERIMENTS) {
- Log.d(TAG, "Adjusting " + entry.getKey() + " for bubble experiment."
- + " allowMessages=" + allowMessageNotifsToBubble(context)
- + " isMessage=" + isMessage
- + " allowNotifs=" + allowAnyNotifToBubble(context)
- + " useShortcutInfo=" + useShortcutInfo
- + " previouslyUserCreated=" + previouslyUserCreated);
- }
- }
-
- if (useShortcutInfo && shortcutId != null) {
- // We don't actually get anything useful from ShortcutInfo so just check existence
- ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
- entry.getSbn().getUser(), shortcutId);
- if (info != null) {
- metadata = createForShortcut(shortcutId);
- }
-
- // Replace existing metadata with shortcut, or we're bubbling for experiment
- boolean shouldBubble = entry.getBubbleMetadata() != null
- || bubbleNotifForExperiment
- || previouslyUserCreated;
- if (shouldBubble && metadata != null) {
- if (DEBUG_EXPERIMENTS) {
- Log.d(TAG, "Adding experimental shortcut bubble for: " + entry.getKey());
- }
- entry.setBubbleMetadata(metadata);
- addedMetadata = true;
- }
- }
-
- // Didn't get metadata from a shortcut & we're bubbling for experiment
- if (entry.getBubbleMetadata() == null
- && (bubbleNotifForExperiment || previouslyUserCreated)) {
- metadata = createFromNotif(context, entry);
- if (metadata != null) {
- if (DEBUG_EXPERIMENTS) {
- Log.d(TAG, "Adding experimental notification bubble for: " + entry.getKey());
- }
- entry.setBubbleMetadata(metadata);
- addedMetadata = true;
- }
- }
-
- boolean bubbleForWhitelist = !userBlocked
- && whiteListedToAutoBubble
- && (addedMetadata || hasMetadata);
- if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
- // Update to a previous bubble (or new autobubble), set its flag now.
- if (DEBUG_EXPERIMENTS) {
- Log.d(TAG, "Setting FLAG_BUBBLE for: " + entry.getKey());
- }
- entry.setFlagBubble(true);
- return true;
- }
- return addedMetadata;
- }
-
- static Notification.BubbleMetadata createFromNotif(Context context, NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- final PendingIntent intent = notification.contentIntent;
- Icon icon = null;
- // Use the icon of the person if available
- List<Person> personList = getPeopleFromNotification(entry);
- if (personList.size() > 0) {
- final Person person = personList.get(0);
- if (person != null) {
- icon = person.getIcon();
- if (icon == null) {
- // Lets try and grab the icon constructed by the layout
- Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
- if (d instanceof BitmapDrawable) {
- icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
- }
- }
- }
- }
- if (icon == null) {
- boolean shouldTint = notification.getLargeIcon() == null;
- icon = shouldTint
- ? notification.getSmallIcon()
- : notification.getLargeIcon();
- if (shouldTint) {
- int notifColor = entry.getSbn().getNotification().color;
- notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
- notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
- true /* findFg */, 3f);
- icon.setTint(notifColor);
- }
- }
- if (intent != null) {
- return new Notification.BubbleMetadata.Builder(intent, icon)
- .setDesiredHeight(BUBBLE_HEIGHT)
- .build();
- }
- return null;
- }
-
- static Notification.BubbleMetadata createForShortcut(String shortcutId) {
- return new Notification.BubbleMetadata.Builder(shortcutId)
- .setDesiredHeight(BUBBLE_HEIGHT)
- .build();
- }
-
- static ShortcutInfo getShortcutInfo(Context context, String packageName, UserHandle user,
- String shortcutId) {
- LauncherApps launcherAppService =
- (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
- LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
- if (packageName != null) {
- query.setPackage(packageName);
- }
- if (shortcutId != null) {
- query.setShortcutIds(Arrays.asList(shortcutId));
- }
- query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST);
- List<ShortcutInfo> shortcuts = launcherAppService.getShortcuts(query, user);
- return shortcuts != null && shortcuts.size() > 0
- ? shortcuts.get(0)
- : null;
- }
-
- static List<Person> getPeopleFromNotification(NotificationEntry entry) {
- Bundle extras = entry.getSbn().getNotification().extras;
- ArrayList<Person> personList = new ArrayList<>();
- if (extras == null) {
- return personList;
- }
-
- List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
-
- if (p != null) {
- personList.addAll(p);
- }
-
- if (Notification.MessagingStyle.class.equals(
- entry.getSbn().getNotification().getNotificationStyle())) {
- final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (!ArrayUtils.isEmpty(messages)) {
- for (Notification.MessagingStyle.Message message :
- Notification.MessagingStyle.Message
- .getMessagesFromBundleArray(messages)) {
- personList.add(message.getSenderPerson());
- }
- }
- }
- return personList;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
deleted file mode 100644
index 009114ffa0be..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleFlyoutView.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * 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.bubbles;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-import static com.android.systemui.Interpolators.ALPHA_IN;
-import static com.android.systemui.Interpolators.ALPHA_OUT;
-
-import android.animation.ArgbEvaluator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.text.TextUtils;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-import com.android.systemui.recents.TriangleShape;
-
-/**
- * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
- * transform into the 'new' dot, which is used during flyout dismiss animations/gestures.
- */
-public class BubbleFlyoutView extends FrameLayout {
- /** Max width of the flyout, in terms of percent of the screen width. */
- private static final float FLYOUT_MAX_WIDTH_PERCENT = .6f;
-
- /** Translation Y of fade animation. */
- private static final float FLYOUT_FADE_Y = 40f;
-
- private static final long FLYOUT_FADE_DURATION = 200L;
-
- private final int mFlyoutPadding;
- private final int mFlyoutSpaceFromBubble;
- private final int mPointerSize;
- private final int mBubbleSize;
- private final int mBubbleBitmapSize;
- private final float mBubbleIconTopPadding;
-
- private final int mFlyoutElevation;
- private final int mBubbleElevation;
- private final int mFloatingBackgroundColor;
- private final float mCornerRadius;
-
- private final ViewGroup mFlyoutTextContainer;
- private final ImageView mSenderAvatar;
- private final TextView mSenderText;
- private final TextView mMessageText;
-
- /** Values related to the 'new' dot which we use to figure out where to collapse the flyout. */
- private final float mNewDotRadius;
- private final float mNewDotSize;
- private final float mOriginalDotSize;
-
- /**
- * The paint used to draw the background, whose color changes as the flyout transitions to the
- * tinted 'new' dot.
- */
- private final Paint mBgPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
- private final ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();
-
- /**
- * Triangular ShapeDrawables used for the triangle that points from the flyout to the bubble
- * stack (a chat-bubble effect).
- */
- private final ShapeDrawable mLeftTriangleShape;
- private final ShapeDrawable mRightTriangleShape;
-
- /** Whether the flyout arrow is on the left (pointing left) or right (pointing right). */
- private boolean mArrowPointingLeft = true;
-
- /** Color of the 'new' dot that the flyout will transform into. */
- private int mDotColor;
-
- /** The outline of the triangle, used for elevation shadows. */
- private final Outline mTriangleOutline = new Outline();
-
- /** The bounds of the flyout background, kept up to date as it transitions to the 'new' dot. */
- private final RectF mBgRect = new RectF();
-
- /** The y position of the flyout, relative to the top of the screen. */
- private float mFlyoutY = 0f;
-
- /**
- * Percent progress in the transition from flyout to 'new' dot. These two values are the inverse
- * of each other (if we're 40% transitioned to the dot, we're 60% flyout), but it makes the code
- * much more readable.
- */
- private float mPercentTransitionedToDot = 1f;
- private float mPercentStillFlyout = 0f;
-
- /**
- * The difference in values between the flyout and the dot. These differences are gradually
- * added over the course of the animation to transform the flyout into the 'new' dot.
- */
- private float mFlyoutToDotWidthDelta = 0f;
- private float mFlyoutToDotHeightDelta = 0f;
-
- /** The translation values when the flyout is completely transitioned into the dot. */
- private float mTranslationXWhenDot = 0f;
- private float mTranslationYWhenDot = 0f;
-
- /**
- * The current translation values applied to the flyout background as it transitions into the
- * 'new' dot.
- */
- private float mBgTranslationX;
- private float mBgTranslationY;
-
- private float[] mDotCenter;
-
- /** The flyout's X translation when at rest (not animating or dragging). */
- private float mRestingTranslationX = 0f;
-
- /** The badge sizes are defined as percentages of the app icon size. Same value as Launcher3. */
- private static final float SIZE_PERCENTAGE = 0.228f;
-
- private static final float DOT_SCALE = 1f;
-
- /** Callback to run when the flyout is hidden. */
- @Nullable private Runnable mOnHide;
-
- public BubbleFlyoutView(Context context) {
- super(context);
- LayoutInflater.from(context).inflate(R.layout.bubble_flyout, this, true);
-
- mFlyoutTextContainer = findViewById(R.id.bubble_flyout_text_container);
- mSenderText = findViewById(R.id.bubble_flyout_name);
- mSenderAvatar = findViewById(R.id.bubble_flyout_avatar);
- mMessageText = mFlyoutTextContainer.findViewById(R.id.bubble_flyout_text);
-
- final Resources res = getResources();
- mFlyoutPadding = res.getDimensionPixelSize(R.dimen.bubble_flyout_padding_x);
- mFlyoutSpaceFromBubble = res.getDimensionPixelSize(R.dimen.bubble_flyout_space_from_bubble);
- mPointerSize = res.getDimensionPixelSize(R.dimen.bubble_flyout_pointer_size);
-
- mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mBubbleIconTopPadding = (mBubbleSize - mBubbleBitmapSize) / 2f;
-
- mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mFlyoutElevation = res.getDimensionPixelSize(R.dimen.bubble_flyout_elevation);
-
- mOriginalDotSize = SIZE_PERCENTAGE * mBubbleBitmapSize;
- mNewDotRadius = (DOT_SCALE * mOriginalDotSize) / 2f;
- mNewDotSize = mNewDotRadius * 2f;
-
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[] {
- android.R.attr.colorBackgroundFloating,
- android.R.attr.dialogCornerRadius});
- mFloatingBackgroundColor = ta.getColor(0, Color.WHITE);
- mCornerRadius = ta.getDimensionPixelSize(1, 0);
- ta.recycle();
-
- // Add padding for the pointer on either side, onDraw will draw it in this space.
- setPadding(mPointerSize, 0, mPointerSize, 0);
- setWillNotDraw(false);
- setClipChildren(false);
- setTranslationZ(mFlyoutElevation);
- setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- BubbleFlyoutView.this.getOutline(outline);
- }
- });
-
- // Use locale direction so the text is aligned correctly.
- setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
-
- mBgPaint.setColor(mFloatingBackgroundColor);
-
- mLeftTriangleShape =
- new ShapeDrawable(TriangleShape.createHorizontal(
- mPointerSize, mPointerSize, true /* isPointingLeft */));
- mLeftTriangleShape.setBounds(0, 0, mPointerSize, mPointerSize);
- mLeftTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
-
- mRightTriangleShape =
- new ShapeDrawable(TriangleShape.createHorizontal(
- mPointerSize, mPointerSize, false /* isPointingLeft */));
- mRightTriangleShape.setBounds(0, 0, mPointerSize, mPointerSize);
- mRightTriangleShape.getPaint().setColor(mFloatingBackgroundColor);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- renderBackground(canvas);
- invalidateOutline();
- super.onDraw(canvas);
- }
-
- void updateFontSize(float fontScale) {
- final float fontSize = mContext.getResources()
- .getDimensionPixelSize(com.android.internal.R.dimen.text_size_body_2_material);
- final float newFontSize = fontSize * fontScale;
- mMessageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
- mSenderText.setTextSize(TypedValue.COMPLEX_UNIT_PX, newFontSize);
- }
-
- /*
- * Fade animation for consecutive flyouts.
- */
- void animateUpdate(Bubble.FlyoutMessage flyoutMessage, float parentWidth, float stackY) {
- fade(false /* in */);
- updateFlyoutMessage(flyoutMessage, parentWidth);
- // Wait for TextViews to layout with updated height.
- post(() -> {
- mFlyoutY = stackY + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
- fade(true /* in */);
- });
- }
-
- private void fade(boolean in) {
- setAlpha(in ? 0f : 1f);
- setTranslationY(in ? mFlyoutY : mFlyoutY + FLYOUT_FADE_Y);
- animate()
- .alpha(in ? 1f : 0f)
- .setDuration(FLYOUT_FADE_DURATION)
- .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
- animate()
- .translationY(in ? mFlyoutY : mFlyoutY - FLYOUT_FADE_Y)
- .setDuration(FLYOUT_FADE_DURATION)
- .setInterpolator(in ? ALPHA_IN : ALPHA_OUT);
- }
-
- private void updateFlyoutMessage(Bubble.FlyoutMessage flyoutMessage, float parentWidth) {
- final Drawable senderAvatar = flyoutMessage.senderAvatar;
- if (senderAvatar != null && flyoutMessage.isGroupChat) {
- mSenderAvatar.setVisibility(VISIBLE);
- mSenderAvatar.setImageDrawable(senderAvatar);
- } else {
- mSenderAvatar.setVisibility(GONE);
- mSenderAvatar.setTranslationX(0);
- mMessageText.setTranslationX(0);
- mSenderText.setTranslationX(0);
- }
-
- final int maxTextViewWidth =
- (int) (parentWidth * FLYOUT_MAX_WIDTH_PERCENT) - mFlyoutPadding * 2;
-
- // Name visibility
- if (!TextUtils.isEmpty(flyoutMessage.senderName)) {
- mSenderText.setMaxWidth(maxTextViewWidth);
- mSenderText.setText(flyoutMessage.senderName);
- mSenderText.setVisibility(VISIBLE);
- } else {
- mSenderText.setVisibility(GONE);
- }
-
- // Set the flyout TextView's max width in terms of percent, and then subtract out the
- // padding so that the entire flyout view will be the desired width (rather than the
- // TextView being the desired width + extra padding).
- mMessageText.setMaxWidth(maxTextViewWidth);
- mMessageText.setText(flyoutMessage.message);
- }
-
- /** Configures the flyout, collapsed into dot form. */
- void setupFlyoutStartingAsDot(
- Bubble.FlyoutMessage flyoutMessage,
- PointF stackPos,
- float parentWidth,
- boolean arrowPointingLeft,
- int dotColor,
- @Nullable Runnable onLayoutComplete,
- @Nullable Runnable onHide,
- float[] dotCenter,
- boolean hideDot) {
-
- updateFlyoutMessage(flyoutMessage, parentWidth);
-
- mArrowPointingLeft = arrowPointingLeft;
- mDotColor = dotColor;
- mOnHide = onHide;
- mDotCenter = dotCenter;
-
- setCollapsePercent(1f);
-
- // Wait for TextViews to layout with updated height.
- post(() -> {
- // Flyout is vertically centered with respect to the bubble.
- mFlyoutY =
- stackPos.y + (mBubbleSize - mFlyoutTextContainer.getHeight()) / 2f;
- setTranslationY(mFlyoutY);
-
- // Calculate the translation required to position the flyout next to the bubble stack,
- // with the desired padding.
- mRestingTranslationX = mArrowPointingLeft
- ? stackPos.x + mBubbleSize + mFlyoutSpaceFromBubble
- : stackPos.x - getWidth() - mFlyoutSpaceFromBubble;
-
- // Calculate the difference in size between the flyout and the 'dot' so that we can
- // transform into the dot later.
- final float newDotSize = hideDot ? 0f : mNewDotSize;
- mFlyoutToDotWidthDelta = getWidth() - newDotSize;
- mFlyoutToDotHeightDelta = getHeight() - newDotSize;
-
- // Calculate the translation values needed to be in the correct 'new dot' position.
- final float adjustmentForScaleAway = hideDot ? 0f : (mOriginalDotSize / 2f);
- final float dotPositionX = stackPos.x + mDotCenter[0] - adjustmentForScaleAway;
- final float dotPositionY = stackPos.y + mDotCenter[1] - adjustmentForScaleAway;
-
- final float distanceFromFlyoutLeftToDotCenterX = mRestingTranslationX - dotPositionX;
- final float distanceFromLayoutTopToDotCenterY = mFlyoutY - dotPositionY;
-
- mTranslationXWhenDot = -distanceFromFlyoutLeftToDotCenterX;
- mTranslationYWhenDot = -distanceFromLayoutTopToDotCenterY;
- if (onLayoutComplete != null) {
- onLayoutComplete.run();
- }
- });
- }
-
- /**
- * Hides the flyout and runs the optional callback passed into setupFlyoutStartingAsDot.
- * The flyout has been animated into the 'new' dot by the time we call this, so no animations
- * are needed.
- */
- void hideFlyout() {
- if (mOnHide != null) {
- mOnHide.run();
- mOnHide = null;
- }
-
- setVisibility(GONE);
- }
-
- /** Sets the percentage that the flyout should be collapsed into dot form. */
- void setCollapsePercent(float percentCollapsed) {
- // This is unlikely, but can happen in a race condition where the flyout view hasn't been
- // laid out and returns 0 for getWidth(). We check for this condition at the sites where
- // this method is called, but better safe than sorry.
- if (Float.isNaN(percentCollapsed)) {
- return;
- }
-
- mPercentTransitionedToDot = Math.max(0f, Math.min(percentCollapsed, 1f));
- mPercentStillFlyout = (1f - mPercentTransitionedToDot);
-
- // Move and fade out the text.
- final float translationX = mPercentTransitionedToDot
- * (mArrowPointingLeft ? -getWidth() : getWidth());
- final float alpha = clampPercentage(
- (mPercentStillFlyout - (1f - BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS))
- / BubbleStackView.FLYOUT_DRAG_PERCENT_DISMISS);
-
- mMessageText.setTranslationX(translationX);
- mMessageText.setAlpha(alpha);
-
- mSenderText.setTranslationX(translationX);
- mSenderText.setAlpha(alpha);
-
- mSenderAvatar.setTranslationX(translationX);
- mSenderAvatar.setAlpha(alpha);
-
- // Reduce the elevation towards that of the topmost bubble.
- setTranslationZ(
- mFlyoutElevation
- - (mFlyoutElevation - mBubbleElevation) * mPercentTransitionedToDot);
- invalidate();
- }
-
- /** Return the flyout's resting X translation (translation when not dragging or animating). */
- float getRestingTranslationX() {
- return mRestingTranslationX;
- }
-
- /** Clamps a float to between 0 and 1. */
- private float clampPercentage(float percent) {
- return Math.min(1f, Math.max(0f, percent));
- }
-
- /**
- * Renders the background, which is either the rounded 'chat bubble' flyout, or some state
- * between that and the 'new' dot over the bubbles.
- */
- private void renderBackground(Canvas canvas) {
- // Calculate the width, height, and corner radius of the flyout given the current collapsed
- // percentage.
- final float width = getWidth() - (mFlyoutToDotWidthDelta * mPercentTransitionedToDot);
- final float height = getHeight() - (mFlyoutToDotHeightDelta * mPercentTransitionedToDot);
- final float interpolatedRadius = getInterpolatedRadius();
-
- // Translate the flyout background towards the collapsed 'dot' state.
- mBgTranslationX = mTranslationXWhenDot * mPercentTransitionedToDot;
- mBgTranslationY = mTranslationYWhenDot * mPercentTransitionedToDot;
-
- // Set the bounds of the rounded rectangle that serves as either the flyout background or
- // the collapsed 'dot'. These bounds will also be used to provide the outline for elevation
- // shadows. In the expanded flyout state, the left and right bounds leave space for the
- // pointer triangle - as the flyout collapses, this space is reduced since the triangle
- // retracts into the flyout.
- mBgRect.set(
- mPointerSize * mPercentStillFlyout /* left */,
- 0 /* top */,
- width - mPointerSize * mPercentStillFlyout /* right */,
- height /* bottom */);
-
- mBgPaint.setColor(
- (int) mArgbEvaluator.evaluate(
- mPercentTransitionedToDot, mFloatingBackgroundColor, mDotColor));
-
- canvas.save();
- canvas.translate(mBgTranslationX, mBgTranslationY);
- renderPointerTriangle(canvas, width, height);
- canvas.drawRoundRect(mBgRect, interpolatedRadius, interpolatedRadius, mBgPaint);
- canvas.restore();
- }
-
- /** Renders the 'pointer' triangle that points from the flyout to the bubble stack. */
- private void renderPointerTriangle(
- Canvas canvas, float currentFlyoutWidth, float currentFlyoutHeight) {
- canvas.save();
-
- // Translation to apply for the 'retraction' effect as the flyout collapses.
- final float retractionTranslationX =
- (mArrowPointingLeft ? 1 : -1) * (mPercentTransitionedToDot * mPointerSize * 2f);
-
- // Place the arrow either at the left side, or the far right, depending on whether the
- // flyout is on the left or right side.
- final float arrowTranslationX =
- mArrowPointingLeft
- ? retractionTranslationX
- : currentFlyoutWidth - mPointerSize + retractionTranslationX;
-
- // Vertically center the arrow at all times.
- final float arrowTranslationY = currentFlyoutHeight / 2f - mPointerSize / 2f;
-
- // Draw the appropriate direction of arrow.
- final ShapeDrawable relevantTriangle =
- mArrowPointingLeft ? mLeftTriangleShape : mRightTriangleShape;
- canvas.translate(arrowTranslationX, arrowTranslationY);
- relevantTriangle.setAlpha((int) (255f * mPercentStillFlyout));
- relevantTriangle.draw(canvas);
-
- // Save the triangle's outline for use in the outline provider, offsetting it to reflect its
- // current position.
- relevantTriangle.getOutline(mTriangleOutline);
- mTriangleOutline.offset((int) arrowTranslationX, (int) arrowTranslationY);
-
- canvas.restore();
- }
-
- /** Builds an outline that includes the transformed flyout background and triangle. */
- private void getOutline(Outline outline) {
- if (!mTriangleOutline.isEmpty()) {
- // Draw the rect into the outline as a path so we can merge the triangle path into it.
- final Path rectPath = new Path();
- final float interpolatedRadius = getInterpolatedRadius();
- rectPath.addRoundRect(mBgRect, interpolatedRadius,
- interpolatedRadius, Path.Direction.CW);
- outline.setPath(rectPath);
-
- // Get rid of the triangle path once it has disappeared behind the flyout.
- if (mPercentStillFlyout > 0.5f) {
- outline.mPath.addPath(mTriangleOutline.mPath);
- }
-
- // Translate the outline to match the background's position.
- final Matrix outlineMatrix = new Matrix();
- outlineMatrix.postTranslate(getLeft() + mBgTranslationX, getTop() + mBgTranslationY);
-
- // At the very end, retract the outline into the bubble so the shadow will be pulled
- // into the flyout-dot as it (visually) becomes part of the bubble. We can't do this by
- // animating translationZ to zero since then it'll go under the bubbles, which have
- // elevation.
- if (mPercentTransitionedToDot > 0.98f) {
- final float percentBetween99and100 = (mPercentTransitionedToDot - 0.98f) / .02f;
- final float percentShadowVisible = 1f - percentBetween99and100;
-
- // Keep it centered.
- outlineMatrix.postTranslate(
- mNewDotRadius * percentBetween99and100,
- mNewDotRadius * percentBetween99and100);
- outlineMatrix.preScale(percentShadowVisible, percentShadowVisible);
- }
-
- outline.mPath.transform(outlineMatrix);
- }
- }
-
- private float getInterpolatedRadius() {
- return mNewDotRadius * mPercentTransitionedToDot
- + mCornerRadius * (1 - mPercentTransitionedToDot);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
deleted file mode 100644
index 371e8490d235..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ /dev/null
@@ -1,159 +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.bubbles;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.ShadowGenerator;
-import com.android.systemui.R;
-
-/**
- * Factory for creating normalized bubble icons.
- * We are not using Launcher's IconFactory because bubbles only runs on the UI thread,
- * so there is no need to manage a pool across multiple threads.
- */
-public class BubbleIconFactory extends BaseIconFactory {
-
- private int mBadgeSize;
-
- protected BubbleIconFactory(Context context) {
- super(context, context.getResources().getConfiguration().densityDpi,
- context.getResources().getDimensionPixelSize(R.dimen.individual_bubble_size));
- mBadgeSize = mContext.getResources().getDimensionPixelSize(
- com.android.launcher3.icons.R.dimen.profile_badge_size);
- }
-
- /**
- * Returns the drawable that the developer has provided to display in the bubble.
- */
- Drawable getBubbleDrawable(@NonNull final Context context,
- @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
- if (shortcutInfo != null) {
- LauncherApps launcherApps =
- (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
- int density = context.getResources().getConfiguration().densityDpi;
- return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
- } else {
- if (ic != null) {
- if (ic.getType() == Icon.TYPE_URI
- || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
- context.grantUriPermission(context.getPackageName(),
- ic.getUri(),
- Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return ic.loadDrawable(context);
- }
- return null;
- }
- }
-
- /**
- * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
- * will include the workprofile indicator on the badge if appropriate.
- */
- BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
- ShadowGenerator shadowGenerator = new ShadowGenerator(mBadgeSize);
- Bitmap userBadgedBitmap = createIconBitmap(userBadgedAppIcon, 1f, mBadgeSize);
-
- if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
- userBadgedBitmap = Bitmap.createScaledBitmap(
- getCircleBitmap((AdaptiveIconDrawable) userBadgedAppIcon, /* size */
- userBadgedAppIcon.getIntrinsicWidth()),
- mBadgeSize, mBadgeSize, /* filter */ true);
- }
-
- if (isImportantConversation) {
- final float ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.importance_ring_stroke_width);
- final int importantConversationColor = mContext.getResources().getColor(
- com.android.settingslib.R.color.important_conversation, null);
- Bitmap badgeAndRing = Bitmap.createBitmap(userBadgedBitmap.getWidth(),
- userBadgedBitmap.getHeight(), userBadgedBitmap.getConfig());
- Canvas c = new Canvas(badgeAndRing);
-
- final int bitmapTop = (int) ringStrokeWidth;
- final int bitmapLeft = (int) ringStrokeWidth;
- final int bitmapWidth = c.getWidth() - 2 * (int) ringStrokeWidth;
- final int bitmapHeight = c.getHeight() - 2 * (int) ringStrokeWidth;
-
- Bitmap scaledBitmap = Bitmap.createScaledBitmap(userBadgedBitmap, bitmapWidth,
- bitmapHeight, /* filter */ true);
- c.drawBitmap(scaledBitmap, bitmapTop, bitmapLeft, /* paint */null);
-
- Paint ringPaint = new Paint();
- ringPaint.setStyle(Paint.Style.STROKE);
- ringPaint.setColor(importantConversationColor);
- ringPaint.setAntiAlias(true);
- ringPaint.setStrokeWidth(ringStrokeWidth);
- c.drawCircle(c.getWidth() / 2, c.getHeight() / 2, c.getWidth() / 2 - ringStrokeWidth,
- ringPaint);
-
- shadowGenerator.recreateIcon(Bitmap.createBitmap(badgeAndRing), c);
- return createIconBitmap(badgeAndRing);
- } else {
- Canvas c = new Canvas();
- c.setBitmap(userBadgedBitmap);
- shadowGenerator.recreateIcon(Bitmap.createBitmap(userBadgedBitmap), c);
- return createIconBitmap(userBadgedBitmap);
- }
- }
-
- public Bitmap getCircleBitmap(AdaptiveIconDrawable icon, int size) {
- Drawable foreground = icon.getForeground();
- Drawable background = icon.getBackground();
- Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas();
- canvas.setBitmap(bitmap);
-
- // Clip canvas to circle.
- Path circlePath = new Path();
- circlePath.addCircle(/* x */ size / 2f,
- /* y */ size / 2f,
- /* radius */ size / 2f,
- Path.Direction.CW);
- canvas.clipPath(circlePath);
-
- // Draw background.
- background.setBounds(0, 0, size, size);
- background.draw(canvas);
-
- // Draw foreground. The foreground and background drawables are derived from adaptive icons
- // Some icon shapes fill more space than others, so adaptive icons are normalized to about
- // the same size. This size is smaller than the original bounds, so we estimate
- // the difference in this offset.
- int offset = size / 5;
- foreground.setBounds(-offset, -offset, size + offset, size + offset);
- foreground.draw(canvas);
-
- canvas.setBitmap(null);
- return bitmap;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
deleted file mode 100644
index 48c809d1b0a7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleLogger.java
+++ /dev/null
@@ -1,154 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEvent;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.util.FrameworkStatsLog;
-
-/**
- * Implementation of UiEventLogger for logging bubble UI events.
- *
- * See UiEventReported atom in atoms.proto for more context.
- */
-public class BubbleLogger {
-
- private final UiEventLogger mUiEventLogger;
-
- /**
- * Bubble UI event.
- */
- @VisibleForTesting
- public enum Event implements UiEventLogger.UiEventEnum {
-
- @UiEvent(doc = "User dismissed the bubble via gesture, add bubble to overflow.")
- BUBBLE_OVERFLOW_ADD_USER_GESTURE(483),
-
- @UiEvent(doc = "No more space in top row, add bubble to overflow.")
- BUBBLE_OVERFLOW_ADD_AGED(484),
-
- @UiEvent(doc = "No more space in overflow, remove bubble from overflow")
- BUBBLE_OVERFLOW_REMOVE_MAX_REACHED(485),
-
- @UiEvent(doc = "Notification canceled, remove bubble from overflow.")
- BUBBLE_OVERFLOW_REMOVE_CANCEL(486),
-
- @UiEvent(doc = "Notification group canceled, remove bubble for child notif from overflow.")
- BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL(487),
-
- @UiEvent(doc = "Notification no longer bubble, remove bubble from overflow.")
- BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE(488),
-
- @UiEvent(doc = "User tapped overflow bubble. Promote bubble back to top row.")
- BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK(489),
-
- @UiEvent(doc = "User blocked notification from bubbling, remove bubble from overflow.")
- BUBBLE_OVERFLOW_REMOVE_BLOCKED(490),
-
- @UiEvent(doc = "User selected the overflow.")
- BUBBLE_OVERFLOW_SELECTED(600);
-
- private final int mId;
-
- Event(int id) {
- mId = id;
- }
-
- @Override
- public int getId() {
- return mId;
- }
- }
-
- public BubbleLogger(UiEventLogger uiEventLogger) {
- mUiEventLogger = uiEventLogger;
- }
-
- /**
- * @param b Bubble involved in this UI event
- * @param e UI event
- */
- public void log(Bubble b, UiEventLogger.UiEventEnum e) {
- mUiEventLogger.logWithInstanceId(e, b.getAppUid(), b.getPackageName(), b.getInstanceId());
- }
-
- /**
- * @param b Bubble removed from overflow
- * @param r Reason that bubble was removed
- */
- public void logOverflowRemove(Bubble b, @BubbleController.DismissReason int r) {
- if (r == BubbleController.DISMISS_NOTIF_CANCEL) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_CANCEL);
- } else if (r == BubbleController.DISMISS_GROUP_CANCELLED) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_GROUP_CANCEL);
- } else if (r == BubbleController.DISMISS_NO_LONGER_BUBBLE) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_NO_LONGER_BUBBLE);
- } else if (r == BubbleController.DISMISS_BLOCKED) {
- log(b, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BLOCKED);
- }
- }
-
- /**
- * @param b Bubble added to overflow
- * @param r Reason that bubble was added to overflow
- */
- public void logOverflowAdd(Bubble b, @BubbleController.DismissReason int r) {
- if (r == BubbleController.DISMISS_AGED) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_AGED);
- } else if (r == BubbleController.DISMISS_USER_GESTURE) {
- log(b, Event.BUBBLE_OVERFLOW_ADD_USER_GESTURE);
- }
- }
-
- void logStackUiChanged(String packageName, int action, int bubbleCount, float normalX,
- float normalY) {
- FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
- packageName,
- null /* notification channel */,
- 0 /* notification ID */,
- 0 /* bubble position */,
- bubbleCount,
- action,
- normalX,
- normalY,
- false /* unread bubble */,
- false /* on-going bubble */,
- false /* isAppForeground (unused) */);
- }
-
- void logShowOverflow(String packageName, int currentUserId) {
- mUiEventLogger.log(BubbleLogger.Event.BUBBLE_OVERFLOW_SELECTED, currentUserId,
- packageName);
- }
-
- void logBubbleUiChanged(Bubble bubble, String packageName, int action, int bubbleCount,
- float normalX, float normalY, int index) {
- FrameworkStatsLog.write(FrameworkStatsLog.BUBBLE_UI_CHANGED,
- packageName,
- bubble.getChannelId() /* notification channel */,
- bubble.getNotificationId() /* notification ID */,
- index,
- bubbleCount,
- action,
- normalX,
- normalY,
- bubble.showInShade() /* isUnread */,
- false /* isOngoing (unused) */,
- false /* isAppForeground (unused) */);
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
deleted file mode 100644
index 102055de2bea..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
+++ /dev/null
@@ -1,178 +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.
- */
-
-package com.android.systemui.bubbles
-
-import android.app.ActivityTaskManager.INVALID_TASK_ID
-import android.content.Context
-import android.content.res.Configuration
-import android.graphics.Bitmap
-import android.graphics.Matrix
-import android.graphics.Path
-import android.graphics.drawable.AdaptiveIconDrawable
-import android.graphics.drawable.ColorDrawable
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.InsetDrawable
-import android.util.PathParser
-import android.util.TypedValue
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.FrameLayout
-import com.android.systemui.R
-
-class BubbleOverflow(
- private val context: Context,
- private val stack: BubbleStackView
-) : BubbleViewProvider {
-
- private lateinit var bitmap: Bitmap
- private lateinit var dotPath: Path
-
- private var bitmapSize = 0
- private var iconBitmapSize = 0
- private var dotColor = 0
- private var showDot = false
-
- private val inflater: LayoutInflater = LayoutInflater.from(context)
- private val expandedView: BubbleExpandedView = inflater
- .inflate(R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */)
- as BubbleExpandedView
- private val overflowBtn: BadgedImageView = inflater
- .inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */)
- as BadgedImageView
- init {
- updateResources()
- with(expandedView) {
- setOverflow(true)
- setStackView(stack)
- applyThemeAttrs()
- }
- with(overflowBtn) {
- setContentDescription(context.resources.getString(
- R.string.bubble_overflow_button_content_description))
- updateBtnTheme()
- }
- }
-
- fun update() {
- updateResources()
- expandedView.applyThemeAttrs()
- // Apply inset and new style to fresh icon drawable.
- overflowBtn.setImageResource(R.drawable.ic_bubble_overflow_button)
- updateBtnTheme()
- }
-
- fun updateResources() {
- bitmapSize = context.resources.getDimensionPixelSize(R.dimen.bubble_bitmap_size)
- iconBitmapSize = context.resources.getDimensionPixelSize(
- R.dimen.bubble_overflow_icon_bitmap_size)
- val bubbleSize = context.resources.getDimensionPixelSize(R.dimen.individual_bubble_size)
- overflowBtn.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
- expandedView.updateDimensions()
- }
-
- private fun updateBtnTheme() {
- val res = context.resources
-
- // Set overflow button accent color, dot color
- val typedValue = TypedValue()
- context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)
- val colorAccent = res.getColor(typedValue.resourceId)
- overflowBtn.drawable?.setTint(colorAccent)
- dotColor = colorAccent
-
- val iconFactory = BubbleIconFactory(context)
-
- // Update bitmap
- val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- == Configuration.UI_MODE_NIGHT_YES)
- val bg = ColorDrawable(res.getColor(
- if (nightMode) R.color.bubbles_dark else R.color.bubbles_light))
-
- val fg = InsetDrawable(overflowBtn.drawable,
- bitmapSize - iconBitmapSize /* inset */)
- bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg),
- null /* user */, true /* shrinkNonAdaptiveIcons */).icon
-
- // Update dot path
- dotPath = PathParser.createPathFromPathData(
- res.getString(com.android.internal.R.string.config_icon_mask))
- val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(),
- null /* outBounds */, null /* path */, null /* outMaskShape */)
- val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
- val matrix = Matrix()
- matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
- radius /* pivot y */)
- dotPath.transform(matrix)
-
- // Attach BubbleOverflow to BadgedImageView
- overflowBtn.setRenderedBubble(this)
- overflowBtn.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE)
- }
-
- fun setVisible(visible: Int) {
- overflowBtn.visibility = visible
- }
-
- fun setShowDot(show: Boolean) {
- showDot = show
- overflowBtn.updateDotVisibility(true /* animate */)
- }
-
- override fun getExpandedView(): BubbleExpandedView? {
- return expandedView
- }
-
- override fun getDotColor(): Int {
- return dotColor
- }
-
- override fun getAppBadge(): Drawable? {
- return null
- }
-
- override fun getBubbleIcon(): Bitmap {
- return bitmap
- }
-
- override fun showDot(): Boolean {
- return showDot
- }
-
- override fun getDotPath(): Path? {
- return dotPath
- }
-
- override fun setContentVisibility(visible: Boolean) {
- expandedView.setContentVisibility(visible)
- }
-
- override fun getIconView(): View? {
- return overflowBtn
- }
-
- override fun getKey(): String {
- return KEY
- }
-
- override fun getTaskId(): Int {
- return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID
- }
-
- companion object {
- @JvmField val KEY = "Overflow"
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
deleted file mode 100644
index fc3f5b6cbf5e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ /dev/null
@@ -1,358 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_OVERFLOW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.app.Activity;
-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.os.Bundle;
-import android.os.IBinder;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-
-/**
- * Activity for showing aged out bubbles.
- * Must be public to be accessible to androidx...AppComponentFactory
- */
-public class BubbleOverflowActivity extends Activity {
- static final String EXTRA_BUBBLE_CONTROLLER = "bubble_controller";
-
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
-
- private LinearLayout mEmptyState;
- private TextView mEmptyStateTitle;
- private TextView mEmptyStateSubtitle;
- private ImageView mEmptyStateImage;
- private Bubbles mBubbles;
- private BubbleOverflowAdapter mAdapter;
- private RecyclerView mRecyclerView;
- private List<Bubble> mOverflowBubbles = new ArrayList<>();
-
- private class NoScrollGridLayoutManager extends GridLayoutManager {
- NoScrollGridLayoutManager(Context context, int columns) {
- super(context, columns);
- }
- @Override
- public boolean canScrollVertically() {
- if (getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE) {
- return super.canScrollVertically();
- }
- return false;
- }
-
- @Override
- public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
- RecyclerView.State state) {
- int bubbleCount = state.getItemCount();
- int columnCount = super.getColumnCountForAccessibility(recycler, state);
- if (bubbleCount < columnCount) {
- // If there are 4 columns and bubbles <= 3,
- // TalkBack says "AppName 1 of 4 in list 4 items"
- // This is a workaround until TalkBack bug is fixed for GridLayoutManager
- return bubbleCount;
- }
- return columnCount;
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.bubble_overflow_activity);
-
- mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
- mEmptyState = findViewById(R.id.bubble_overflow_empty_state);
- mEmptyStateTitle = findViewById(R.id.bubble_overflow_empty_title);
- mEmptyStateSubtitle = findViewById(R.id.bubble_overflow_empty_subtitle);
- mEmptyStateImage = findViewById(R.id.bubble_overflow_empty_state_image);
-
- Intent intent = getIntent();
- if (intent != null && intent.getExtras() != null) {
- IBinder binder = intent.getExtras().getBinder(EXTRA_BUBBLE_CONTROLLER);
- if (binder instanceof ObjectWrapper) {
- mBubbles = ((ObjectWrapper<Bubbles>) binder).get();
- }
- } else {
- Log.w(TAG, "Bubble overflow activity created without bubble controller!");
- }
- updateOverflow();
- }
-
- void updateOverflow() {
- Resources res = getResources();
- final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
- mRecyclerView.setLayoutManager(
- new NoScrollGridLayoutManager(getApplicationContext(), columns));
-
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
-
- final int overflowPadding = res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
- final int recyclerViewWidth = displayMetrics.widthPixels - (overflowPadding * 2);
- final int viewWidth = recyclerViewWidth / columns;
-
- final int maxOverflowBubbles = res.getInteger(R.integer.bubbles_max_overflow);
- final int rows = (int) Math.ceil((double) maxOverflowBubbles / columns);
- final int recyclerViewHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height)
- - res.getDimensionPixelSize(R.dimen.bubble_overflow_padding);
- final int viewHeight = recyclerViewHeight / rows;
-
- mAdapter = new BubbleOverflowAdapter(getApplicationContext(), mOverflowBubbles,
- mBubbles::promoteBubbleFromOverflow, viewWidth, viewHeight);
- mRecyclerView.setAdapter(mAdapter);
-
- mOverflowBubbles.clear();
- mOverflowBubbles.addAll(mBubbles.getOverflowBubbles());
- mAdapter.notifyDataSetChanged();
- updateEmptyStateVisibility();
-
- mBubbles.setOverflowListener(mDataListener);
- updateTheme();
- }
-
- void updateEmptyStateVisibility() {
- if (mOverflowBubbles.isEmpty()) {
- mEmptyState.setVisibility(View.VISIBLE);
- } else {
- mEmptyState.setVisibility(View.GONE);
- }
- }
-
- /**
- * Handle theme changes.
- */
- void updateTheme() {
- Resources res = getResources();
- final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
- final boolean isNightMode = (mode == Configuration.UI_MODE_NIGHT_YES);
-
- mEmptyStateImage.setImageDrawable(isNightMode
- ? res.getDrawable(R.drawable.ic_empty_bubble_overflow_dark)
- : res.getDrawable(R.drawable.ic_empty_bubble_overflow_light));
-
- findViewById(android.R.id.content)
- .setBackgroundColor(isNightMode
- ? res.getColor(R.color.bubbles_dark)
- : res.getColor(R.color.bubbles_light));
-
- final TypedArray typedArray = getApplicationContext().obtainStyledAttributes(
- new int[]{android.R.attr.colorBackgroundFloating,
- android.R.attr.textColorSecondary});
- int bgColor = typedArray.getColor(0, isNightMode ? Color.BLACK : Color.WHITE);
- int textColor = typedArray.getColor(1, isNightMode ? Color.WHITE : Color.BLACK);
- textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, isNightMode);
- typedArray.recycle();
-
- mEmptyStateTitle.setTextColor(textColor);
- mEmptyStateSubtitle.setTextColor(textColor);
- }
-
- private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
-
- @Override
- public void applyUpdate(BubbleData.Update update) {
-
- Bubble toRemove = update.removedOverflowBubble;
- if (toRemove != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "remove: " + toRemove);
- }
- toRemove.cleanupViews();
- final int i = mOverflowBubbles.indexOf(toRemove);
- mOverflowBubbles.remove(toRemove);
- mAdapter.notifyItemRemoved(i);
- }
-
- Bubble toAdd = update.addedOverflowBubble;
- if (toAdd != null) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "add: " + toAdd);
- }
- mOverflowBubbles.add(0, toAdd);
- mAdapter.notifyItemInserted(0);
- }
-
- updateEmptyStateVisibility();
-
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(
- mBubbles.getOverflowBubbles(), null));
- }
- }
- };
-
- @Override
- public void onStart() {
- super.onStart();
- }
-
- @Override
- public void onRestart() {
- super.onRestart();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- updateOverflow();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- }
-
- @Override
- public void onStop() {
- super.onStop();
- }
-
- public void onDestroy() {
- super.onDestroy();
- }
-}
-
-class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowAdapter" : TAG_BUBBLES;
-
- private Context mContext;
- private Consumer<Bubble> mPromoteBubbleFromOverflow;
- private List<Bubble> mBubbles;
- private int mWidth;
- private int mHeight;
-
- public BubbleOverflowAdapter(Context context, List<Bubble> list, Consumer<Bubble> promoteBubble,
- int width, int height) {
- mContext = context;
- mBubbles = list;
- mPromoteBubbleFromOverflow = promoteBubble;
- mWidth = width;
- mHeight = height;
- }
-
- @Override
- public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
- int viewType) {
-
- // Set layout for overflow bubble view.
- LinearLayout overflowView = (LinearLayout) LayoutInflater.from(parent.getContext())
- .inflate(R.layout.bubble_overflow_view, parent, false);
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
- LinearLayout.LayoutParams.WRAP_CONTENT,
- LinearLayout.LayoutParams.WRAP_CONTENT);
- params.width = mWidth;
- params.height = mHeight;
- overflowView.setLayoutParams(params);
-
- // Ensure name has enough contrast.
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[]{android.R.attr.colorBackgroundFloating, android.R.attr.textColorPrimary});
- final int bgColor = ta.getColor(0, Color.WHITE);
- int textColor = ta.getColor(1, Color.BLACK);
- textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true);
- ta.recycle();
-
- TextView viewName = overflowView.findViewById(R.id.bubble_view_name);
- viewName.setTextColor(textColor);
-
- return new ViewHolder(overflowView);
- }
-
- @Override
- public void onBindViewHolder(ViewHolder vh, int index) {
- Bubble b = mBubbles.get(index);
-
- vh.iconView.setRenderedBubble(b);
- vh.iconView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
- vh.iconView.setOnClickListener(view -> {
- mBubbles.remove(b);
- notifyDataSetChanged();
- mPromoteBubbleFromOverflow.accept(b);
- });
-
- String titleStr = b.getTitle();
- if (titleStr == null) {
- titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
- }
- vh.iconView.setContentDescription(mContext.getResources().getString(
- R.string.bubble_content_description_single, titleStr, b.getAppName()));
-
- vh.iconView.setAccessibilityDelegate(
- new View.AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host,
- AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- // Talkback prompts "Double tap to add back to stack"
- // instead of the default "Double tap to activate"
- info.addAction(
- new AccessibilityNodeInfo.AccessibilityAction(
- AccessibilityNodeInfo.ACTION_CLICK,
- mContext.getResources().getString(
- R.string.bubble_accessibility_action_add_back)));
- }
- });
-
- CharSequence label = b.getShortcutInfo() != null
- ? b.getShortcutInfo().getLabel()
- : b.getAppName();
- vh.textView.setText(label);
- }
-
- @Override
- public int getItemCount() {
- return mBubbles.size();
- }
-
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public BadgedImageView iconView;
- public TextView textView;
-
- public ViewHolder(LinearLayout v) {
- super(v);
- iconView = v.findViewById(R.id.bubble_view);
- textView = v.findViewById(R.id.bubble_view_name);
- }
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
deleted file mode 100644
index 431719f98ad9..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ /dev/null
@@ -1,2723 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.bubbles;
-
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.SuppressLint;
-import android.content.ContentResolver;
-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.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Outline;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Region;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.Choreographer;
-import android.view.DisplayCutout;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-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.NonNull;
-import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
-import com.android.systemui.bubbles.animation.ExpandedAnimationController;
-import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
-import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.wm.shell.animation.PhysicsAnimator;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.math.BigDecimal;
-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.
- */
-public class BubbleStackView extends FrameLayout
- implements ViewTreeObserver.OnComputeInternalInsetsListener {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
-
- /** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
- static final float FLYOUT_DRAG_PERCENT_DISMISS = 0.25f;
-
- /** Velocity required to dismiss the flyout via drag. */
- private static final float FLYOUT_DISMISS_VELOCITY = 2000f;
-
- /**
- * Factor for attenuating translation when the flyout is overscrolled (8f = flyout moves 1 pixel
- * for every 8 pixels overscrolled).
- */
- private static final float FLYOUT_OVERSCROLL_ATTENUATION_FACTOR = 8f;
-
- /** Duration of the flyout alpha animations. */
- private static final int FLYOUT_ALPHA_ANIMATION_DURATION = 100;
-
- private static final int FADE_IN_DURATION = 320;
-
- /** Percent to darken the bubbles when they're in the dismiss target. */
- private static final float DARKEN_PERCENT = 0.3f;
-
- /** How long to wait, in milliseconds, before hiding the flyout. */
- @VisibleForTesting
- static final int FLYOUT_HIDE_AFTER = 5000;
-
- /**
- * How long to wait to animate the stack temporarily invisible after a drag/flyout hide
- * animation ends, if we are in fact temporarily invisible.
- */
- private static final int ANIMATE_TEMPORARILY_INVISIBLE_DELAY = 1000;
-
- private static final PhysicsAnimator.SpringConfig FLYOUT_IME_ANIMATION_SPRING_CONFIG =
- new PhysicsAnimator.SpringConfig(
- StackAnimationController.IME_ANIMATION_STIFFNESS,
- StackAnimationController.DEFAULT_BOUNCINESS);
-
- private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
- new PhysicsAnimator.SpringConfig(300f, 0.9f);
-
- private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
- new PhysicsAnimator.SpringConfig(900f, 1f);
-
- private final PhysicsAnimator.SpringConfig mTranslateSpringConfig =
- new PhysicsAnimator.SpringConfig(
- SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
-
- /**
- * Handler to use for all delayed animations - this way, we can easily cancel them before
- * starting a new animation.
- */
- private final Handler mDelayedAnimationHandler = new Handler();
-
- /**
- * Interface to synchronize {@link View} state and the screen.
- *
- * {@hide}
- */
- interface SurfaceSynchronizer {
- /**
- * Wait until requested change on a {@link View} is reflected on the screen.
- *
- * @param callback callback to run after the change is reflected on the screen.
- */
- void syncSurfaceAndRun(Runnable callback);
- }
-
- private static final SurfaceSynchronizer DEFAULT_SURFACE_SYNCHRONIZER =
- new SurfaceSynchronizer() {
- @Override
- public void syncSurfaceAndRun(Runnable callback) {
- Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
- // Just wait 2 frames. There is no guarantee, but this is usually enough time that
- // the requested change is reflected on the screen.
- // TODO: Once SurfaceFlinger provide APIs to sync the state of {@code View} and
- // surfaces, rewrite this logic with them.
- private int mFrameWait = 2;
-
- @Override
- public void doFrame(long frameTimeNanos) {
- if (--mFrameWait > 0) {
- Choreographer.getInstance().postFrameCallback(this);
- } else {
- callback.run();
- }
- }
- });
- }
- };
-
- private Point mDisplaySize;
-
- private final BubbleData mBubbleData;
-
- private final ValueAnimator mDesaturateAndDarkenAnimator;
- private final Paint mDesaturateAndDarkenPaint = new Paint();
-
- private PhysicsAnimationLayout mBubbleContainer;
- private StackAnimationController mStackAnimationController;
- private ExpandedAnimationController mExpandedAnimationController;
-
- private FrameLayout mExpandedViewContainer;
-
- /** Matrix used to scale the expanded view container with a given pivot point. */
- private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
-
- /**
- * SurfaceView that we draw screenshots of animating-out bubbles into. This allows us to animate
- * between bubble activities without needing both to be alive at the same time.
- */
- private SurfaceView mAnimatingOutSurfaceView;
-
- /** Container for the animating-out SurfaceView. */
- private FrameLayout mAnimatingOutSurfaceContainer;
-
- /**
- * Buffer containing a screenshot of the animating-out bubble. This is drawn into the
- * SurfaceView during animations.
- */
- private SurfaceControl.ScreenshotHardwareBuffer mAnimatingOutBubbleBuffer;
-
- private BubbleFlyoutView mFlyout;
- /** Runnable that fades out the flyout and then sets it to GONE. */
- private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
- /**
- * Callback to run after the flyout hides. Also called if a new flyout is shown before the
- * previous one animates out.
- */
- private Runnable mAfterFlyoutHidden;
- /**
- * Set when the flyout is tapped, so that we can expand the bubble associated with the flyout
- * once it collapses.
- */
- @Nullable
- private Bubble mBubbleToExpandAfterFlyoutCollapse = null;
-
- /** Layout change listener that moves the stack to the nearest valid position on rotation. */
- private OnLayoutChangeListener mOrientationChangedListener;
-
- @Nullable private RelativeStackPosition mRelativeStackPositionBeforeRotation;
-
- private int mMaxBubbles;
- private int mBubbleSize;
- private int mBubbleElevation;
- private int mBubblePaddingTop;
- private int mBubbleTouchPadding;
- private int mExpandedViewPadding;
- private int mCornerRadius;
- private int mStatusBarHeight;
- private int mImeOffset;
- @Nullable private BubbleViewProvider mExpandedBubble;
- private boolean mIsExpanded;
-
- /** Whether the stack is currently on the left side of the screen, or animating there. */
- private boolean mStackOnLeftOrWillBe = true;
-
- /** Whether a touch gesture, such as a stack/bubble drag or flyout drag, is in progress. */
- private boolean mIsGestureInProgress = false;
-
- /** Whether or not the stack is temporarily invisible off the side of the screen. */
- private boolean mTemporarilyInvisible = false;
-
- /** Whether we're in the middle of dragging the stack around by touch. */
- private boolean mIsDraggingStack = false;
-
- /**
- * The pointer index of the ACTION_DOWN event we received prior to an ACTION_UP. We'll ignore
- * touches from other pointer indices.
- */
- private int mPointerIndexDown = -1;
-
- /** Description of current animation controller state. */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("Stack view state:");
-
- String bubblesOnScreen = BubbleDebugConfig.formatBubblesString(
- getBubblesOnScreen(), getExpandedBubble());
- pw.print(" bubbles on screen: "); pw.println(bubblesOnScreen);
- pw.print(" gestureInProgress: "); pw.println(mIsGestureInProgress);
- pw.print(" showingDismiss: "); pw.println(mDismissView.isShowing());
- pw.print(" isExpansionAnimating: "); pw.println(mIsExpansionAnimating);
- pw.print(" expandedContainerVis: "); pw.println(mExpandedViewContainer.getVisibility());
- pw.print(" expandedContainerAlpha: "); pw.println(mExpandedViewContainer.getAlpha());
- pw.print(" expandedContainerMatrix: ");
- pw.println(mExpandedViewContainer.getAnimationMatrix());
-
- mStackAnimationController.dump(fd, pw, args);
- mExpandedAnimationController.dump(fd, pw, args);
-
- if (mExpandedBubble != null) {
- pw.println("Expanded bubble state:");
- pw.println(" expandedBubbleKey: " + mExpandedBubble.getKey());
-
- final BubbleExpandedView expandedView = mExpandedBubble.getExpandedView();
-
- if (expandedView != null) {
- pw.println(" expandedViewVis: " + expandedView.getVisibility());
- pw.println(" expandedViewAlpha: " + expandedView.getAlpha());
- pw.println(" expandedViewTaskId: " + expandedView.getTaskId());
-
- final View av = expandedView.getTaskView();
-
- if (av != null) {
- pw.println(" activityViewVis: " + av.getVisibility());
- pw.println(" activityViewAlpha: " + av.getAlpha());
- } else {
- pw.println(" activityView is null");
- }
- } else {
- pw.println("Expanded bubble view state: expanded bubble view is null");
- }
- } else {
- pw.println("Expanded bubble state: expanded bubble is null");
- }
- }
-
- private BubbleController.BubbleExpandListener mExpandListener;
-
- /** Callback to run when we want to unbubble the given notification's conversation. */
- private Consumer<String> mUnbubbleConversationCallback;
-
- private boolean mViewUpdatedRequested = false;
- private boolean mIsExpansionAnimating = false;
- private boolean mIsBubbleSwitchAnimating = false;
-
- /** The view to desaturate/darken when magneted to the dismiss target. */
- @Nullable private View mDesaturateAndDarkenTargetView;
-
- private Rect mTempRect = new Rect();
-
- private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
-
- private ViewTreeObserver.OnPreDrawListener mViewUpdater =
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
- updateExpandedView();
- mViewUpdatedRequested = false;
- return true;
- }
- };
-
- private ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater =
- this::updateSystemGestureExcludeRects;
-
- /** Float property that 'drags' the flyout. */
- private final FloatPropertyCompat mFlyoutCollapseProperty =
- new FloatPropertyCompat("FlyoutCollapseSpring") {
- @Override
- public float getValue(Object o) {
- return mFlyoutDragDeltaX;
- }
-
- @Override
- public void setValue(Object o, float v) {
- setFlyoutStateForDragLength(v);
- }
- };
-
- /** SpringAnimation that springs the flyout collapsed via onFlyoutDragged. */
- private final SpringAnimation mFlyoutTransitionSpring =
- new SpringAnimation(this, mFlyoutCollapseProperty);
-
- /** Distance the flyout has been dragged in the X axis. */
- private float mFlyoutDragDeltaX = 0f;
-
- /**
- * Runnable that animates in the flyout. This reference is needed to cancel delayed postings.
- */
- private Runnable mAnimateInFlyout;
-
- /**
- * End listener for the flyout spring that either posts a runnable to hide the flyout, or hides
- * it immediately.
- */
- private final DynamicAnimation.OnAnimationEndListener mAfterFlyoutTransitionSpring =
- (dynamicAnimation, b, v, v1) -> {
- if (mFlyoutDragDeltaX == 0) {
- mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- } else {
- mFlyout.hideFlyout();
- }
- };
-
- @NonNull
- private final SurfaceSynchronizer mSurfaceSynchronizer;
-
- /**
- * Callback to run when the IME visibility changes - BubbleController uses this to update the
- * Bubbles window focusability flags with the WindowManager.
- */
- public final Consumer<Boolean> mOnImeVisibilityChanged;
-
- /**
- * Callback to run when the bubble expand status changes.
- */
- private final Consumer<Boolean> mOnBubbleExpandChanged;
-
- /**
- * Callback to run to ask BubbleController to hide the current IME.
- */
- private final Runnable mHideCurrentInputMethodCallback;
-
- /**
- * The currently magnetized object, which is being dragged and will be attracted to the magnetic
- * dismiss target.
- *
- * This is either the stack itself, or an individual bubble.
- */
- private MagnetizedObject<?> mMagnetizedObject;
-
- /**
- * The MagneticTarget instance for our circular dismiss view. This is added to the
- * MagnetizedObject instances for the stack and any dragged-out bubbles.
- */
- private MagnetizedObject.MagneticTarget mMagneticTarget;
-
- /** Magnet listener that handles animating and dismissing individual dragged-out bubbles. */
- private final MagnetizedObject.MagnetListener mIndividualBubbleMagnetListener =
- new MagnetizedObject.MagnetListener() {
- @Override
- public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
- }
-
- animateDesaturateAndDarken(
- mExpandedAnimationController.getDraggedOutBubble(), true);
- }
-
- @Override
- public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- float velX, float velY, boolean wasFlungOut) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
- }
-
- animateDesaturateAndDarken(
- mExpandedAnimationController.getDraggedOutBubble(), false);
-
- if (wasFlungOut) {
- mExpandedAnimationController.snapBubbleBack(
- mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
- mDismissView.hide();
- } else {
- mExpandedAnimationController.onUnstuckFromTarget();
- }
- }
-
- @Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- if (mExpandedAnimationController.getDraggedOutBubble() == null) {
- return;
- }
-
- mExpandedAnimationController.dismissDraggedOutBubble(
- mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
- mDismissView.getHeight() /* translationYBy */,
- BubbleStackView.this::dismissMagnetizedObject /* after */);
- mDismissView.hide();
- }
- };
-
- /** Magnet listener that handles animating and dismissing the entire stack. */
- private final MagnetizedObject.MagnetListener mStackMagnetListener =
- new MagnetizedObject.MagnetListener() {
- @Override
- public void onStuckToTarget(
- @NonNull MagnetizedObject.MagneticTarget target) {
- animateDesaturateAndDarken(mBubbleContainer, true);
- }
-
- @Override
- public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
- float velX, float velY, boolean wasFlungOut) {
- animateDesaturateAndDarken(mBubbleContainer, false);
-
- if (wasFlungOut) {
- mStackAnimationController.flingStackThenSpringToEdge(
- mStackAnimationController.getStackPosition().x, velX, velY);
- mDismissView.hide();
- } else {
- mStackAnimationController.onUnstuckFromTarget();
- }
- }
-
- @Override
- public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mStackAnimationController.animateStackDismissal(
- mDismissView.getHeight() /* translationYBy */,
- () -> {
- resetDesaturationAndDarken();
- dismissMagnetizedObject();
- }
- );
-
- mDismissView.hide();
- }
- };
-
- /**
- * Click listener set on each bubble view. When collapsed, clicking a bubble expands the stack.
- * When expanded, clicking a bubble either expands that bubble, or collapses the stack.
- */
- private OnClickListener mBubbleClickListener = new OnClickListener() {
- @Override
- public void onClick(View view) {
- mIsDraggingStack = false; // If the touch ended in a click, we're no longer dragging.
-
- // Bubble clicks either trigger expansion/collapse or a bubble switch, both of which we
- // shouldn't interrupt. These are quick transitions, so it's not worth trying to adjust
- // the animations inflight.
- if (mIsExpansionAnimating || mIsBubbleSwitchAnimating) {
- return;
- }
-
- final Bubble clickedBubble = mBubbleData.getBubbleWithView(view);
-
- // If the bubble has since left us, ignore the click.
- if (clickedBubble == null) {
- return;
- }
-
- final boolean clickedBubbleIsCurrentlyExpandedBubble =
- clickedBubble.getKey().equals(mExpandedBubble.getKey());
-
- if (isExpanded()) {
- mExpandedAnimationController.onGestureFinished();
- }
-
- if (isExpanded() && !clickedBubbleIsCurrentlyExpandedBubble) {
- if (clickedBubble != mBubbleData.getSelectedBubble()) {
- // Select the clicked bubble.
- mBubbleData.setSelectedBubble(clickedBubble);
- } else {
- // If the clicked bubble is the selected bubble (but not the expanded bubble),
- // that means overflow was previously expanded. Set the selected bubble
- // internally without going through BubbleData (which would ignore it since it's
- // already selected).
- setSelectedBubble(clickedBubble);
- }
- } else {
- // Otherwise, we either tapped the stack (which means we're collapsed
- // and should expand) or the currently selected bubble (we're expanded
- // and should collapse).
- if (!maybeShowStackEdu()) {
- mBubbleData.setExpanded(!mBubbleData.isExpanded());
- }
- }
- }
- };
-
- /**
- * Touch listener set on each bubble view. This enables dragging and dismissing the stack (when
- * collapsed), or individual bubbles (when expanded).
- */
- private RelativeTouchListener mBubbleTouchListener = new RelativeTouchListener() {
-
- @Override
- public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
- // If we're expanding or collapsing, consume but ignore all touch events.
- if (mIsExpansionAnimating) {
- return true;
- }
-
- // If the manage menu is visible, just hide it.
- if (mShowingManage) {
- showManageMenu(false /* show */);
- }
-
- if (mBubbleData.isExpanded()) {
- if (mManageEduView != null) {
- mManageEduView.hide(false /* show */);
- }
-
- // If we're expanded, tell the animation controller to prepare to drag this bubble,
- // dispatching to the individual bubble magnet listener.
- mExpandedAnimationController.prepareForBubbleDrag(
- v /* bubble */,
- mMagneticTarget,
- mIndividualBubbleMagnetListener);
-
- hideCurrentInputMethod();
-
- // Save the magnetized individual bubble so we can dispatch touch events to it.
- mMagnetizedObject = mExpandedAnimationController.getMagnetizedBubbleDraggingOut();
- } else {
- // If we're collapsed, prepare to drag the stack. Cancel active animations, set the
- // animation controller, and hide the flyout.
- mStackAnimationController.cancelStackPositionAnimations();
- mBubbleContainer.setActiveController(mStackAnimationController);
- hideFlyoutImmediate();
-
- // Also, save the magnetized stack so we can dispatch touch events to it.
- mMagnetizedObject = mStackAnimationController.getMagnetizedStack(mMagneticTarget);
- mMagnetizedObject.setMagnetListener(mStackMagnetListener);
-
- mIsDraggingStack = true;
-
- // Cancel animations to make the stack temporarily invisible, since we're now
- // dragging it.
- updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
- }
-
- passEventToMagnetizedObject(ev);
-
- // Bubbles are always interested in all touch events!
- return true;
- }
-
- @Override
- public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy) {
- // If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating) {
- return;
- }
-
- // Show the dismiss target, if we haven't already.
- mDismissView.show();
-
- // First, see if the magnetized object consumes the event - if so, we shouldn't move the
- // bubble since it's stuck to the target.
- if (!passEventToMagnetizedObject(ev)) {
- if (mBubbleData.isExpanded()) {
- mExpandedAnimationController.dragBubbleOut(
- v, viewInitialX + dx, viewInitialY + dy);
- } else {
- if (mStackEduView != null) {
- mStackEduView.hide(false /* fromExpansion */);
- }
- mStackAnimationController.moveStackFromTouch(
- viewInitialX + dx, viewInitialY + dy);
- }
- }
- }
-
- @Override
- public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy, float velX, float velY) {
- // If we're expanding or collapsing, ignore all touch events.
- if (mIsExpansionAnimating) {
- return;
- }
-
- // First, see if the magnetized object consumes the event - if so, the bubble was
- // released in the target or flung out of it, and we should ignore the event.
- if (!passEventToMagnetizedObject(ev)) {
- if (mBubbleData.isExpanded()) {
- mExpandedAnimationController.snapBubbleBack(v, velX, velY);
- } else {
- // Fling the stack to the edge, and save whether or not it's going to end up on
- // the left side of the screen.
- mStackOnLeftOrWillBe =
- mStackAnimationController.flingStackThenSpringToEdge(
- viewInitialX + dx, velX, velY) <= 0;
- updateBubbleIcons();
- logBubbleEvent(null /* no bubble associated with bubble stack move */,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
- }
- mDismissView.hide();
- }
-
- mIsDraggingStack = false;
-
- // Hide the stack after a delay, if needed.
- updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
- }
- };
-
- /** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
- private OnClickListener mFlyoutClickListener = new OnClickListener() {
- @Override
- public void onClick(View view) {
- if (maybeShowStackEdu()) {
- // If we're showing user education, don't open the bubble show the education first
- mBubbleToExpandAfterFlyoutCollapse = null;
- } else {
- mBubbleToExpandAfterFlyoutCollapse = mBubbleData.getSelectedBubble();
- }
-
- mFlyout.removeCallbacks(mHideFlyout);
- mHideFlyout.run();
- }
- };
-
- /** Touch listener for the flyout. This enables the drag-to-dismiss gesture on the flyout. */
- private RelativeTouchListener mFlyoutTouchListener = new RelativeTouchListener() {
-
- @Override
- public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
- mFlyout.removeCallbacks(mHideFlyout);
- return true;
- }
-
- @Override
- public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy) {
- setFlyoutStateForDragLength(dx);
- }
-
- @Override
- public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
- float viewInitialY, float dx, float dy, float velX, float velY) {
- final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
- final boolean metRequiredVelocity =
- onLeft ? velX < -FLYOUT_DISMISS_VELOCITY : velX > FLYOUT_DISMISS_VELOCITY;
- final boolean metRequiredDeltaX =
- onLeft
- ? dx < -mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS
- : dx > mFlyout.getWidth() * FLYOUT_DRAG_PERCENT_DISMISS;
- final boolean isCancelFling = onLeft ? velX > 0 : velX < 0;
- final boolean shouldDismiss = metRequiredVelocity
- || (metRequiredDeltaX && !isCancelFling);
-
- mFlyout.removeCallbacks(mHideFlyout);
- animateFlyoutCollapsed(shouldDismiss, velX);
-
- maybeShowStackEdu();
- }
- };
-
- private DismissView mDismissView;
- private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
-
- @Nullable
- private BubbleOverflow mBubbleOverflow;
- private StackEducationView mStackEduView;
- private ManageEducationView mManageEduView;
-
- 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,
- FloatingContentCoordinator floatingContentCoordinator,
- Runnable allBubblesAnimatedOutAction,
- Consumer<Boolean> onImeVisibilityChanged,
- Runnable hideCurrentInputMethodCallback,
- Consumer<Boolean> onBubbleExpandChanged) {
- super(context);
-
- mBubbleData = data;
-
- Resources res = getResources();
- mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered);
- mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
-
- mStatusBarHeight =
- res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- mImeOffset = res.getDimensionPixelSize(R.dimen.pip_ime_offset);
-
- mDisplaySize = new Point();
- WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- // We use the real size & subtract screen decorations / window insets ourselves when needed
- wm.getDefaultDisplay().getRealSize(mDisplaySize);
-
- mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
- int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
-
- final TypedArray ta = mContext.obtainStyledAttributes(
- new int[] {android.R.attr.dialogCornerRadius});
- mCornerRadius = ta.getDimensionPixelSize(0, 0);
- ta.recycle();
-
- final Runnable onBubbleAnimatedOut = () -> {
- if (getBubbleCount() == 0) {
- allBubblesAnimatedOutAction.run();
- }
- };
-
- mStackAnimationController = new StackAnimationController(
- floatingContentCoordinator, this::getBubbleCount, onBubbleAnimatedOut);
-
- mExpandedAnimationController = new ExpandedAnimationController(
- mDisplaySize, mExpandedViewPadding, res.getConfiguration().orientation,
- onBubbleAnimatedOut);
- mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
-
- // Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
- // is centered. It greatly simplifies translation positioning/animations. Views that will
- // actually lay out differently in RTL, such as the flyout and expanded view, will set their
- // layout direction to LOCALE.
- setLayoutDirection(LAYOUT_DIRECTION_LTR);
-
- mBubbleContainer = new PhysicsAnimationLayout(context);
- mBubbleContainer.setActiveController(mStackAnimationController);
- mBubbleContainer.setElevation(elevation);
- mBubbleContainer.setClipChildren(false);
- addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
- updateUserEdu();
-
- mExpandedViewContainer = new FrameLayout(context);
- mExpandedViewContainer.setElevation(elevation);
- mExpandedViewContainer.setClipChildren(false);
- addView(mExpandedViewContainer);
-
- mAnimatingOutSurfaceContainer = new FrameLayout(getContext());
- mAnimatingOutSurfaceContainer.setLayoutParams(
- new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- addView(mAnimatingOutSurfaceContainer);
-
- mAnimatingOutSurfaceView = new SurfaceView(getContext());
- mAnimatingOutSurfaceView.setUseAlpha();
- mAnimatingOutSurfaceView.setZOrderOnTop(true);
- mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius);
- mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
- mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView);
-
- mAnimatingOutSurfaceContainer.setPadding(
- mExpandedViewPadding,
- mExpandedViewPadding,
- mExpandedViewPadding,
- mExpandedViewPadding);
-
- setUpManageMenu();
-
- setUpFlyout();
- mFlyoutTransitionSpring.setSpring(new SpringForce()
- .setStiffness(SpringForce.STIFFNESS_LOW)
- .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
- mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);
-
- mDismissView = new DismissView(context);
- addView(mDismissView);
-
- final ContentResolver contentResolver = getContext().getContentResolver();
- final int dismissRadius = Settings.Secure.getInt(
- contentResolver, "bubble_dismiss_radius", mBubbleSize * 2 /* default */);
-
- // Save the MagneticTarget instance for the newly set up view - we'll add this to the
- // MagnetizedObjects.
- mMagneticTarget = new MagnetizedObject.MagneticTarget(
- mDismissView.getCircle(), dismissRadius);
-
- setClipChildren(false);
- setFocusable(true);
- mBubbleContainer.bringToFront();
-
- mBubbleOverflow = new BubbleOverflow(getContext(), this);
- mBubbleContainer.addView(mBubbleOverflow.getIconView(),
- mBubbleContainer.getChildCount() /* index */,
- new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT));
- updateOverflow();
- mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
- setSelectedBubble(mBubbleOverflow);
- showManageMenu(false);
- });
-
- mOnImeVisibilityChanged = onImeVisibilityChanged;
- mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback;
- mOnBubbleExpandChanged = onBubbleExpandChanged;
-
- setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
- onImeVisibilityChanged.accept(insets.getInsets(WindowInsets.Type.ime()).bottom > 0);
- if (!mIsExpanded || mIsExpansionAnimating) {
- return view.onApplyWindowInsets(insets);
- }
- return view.onApplyWindowInsets(insets);
- });
-
- mOrientationChangedListener =
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
- mStackAnimationController.updateResources(mOrientation);
- mBubbleOverflow.updateResources();
-
- // Need to update the padding around the view
- WindowInsets insets = getRootWindowInsets();
- int leftPadding = mExpandedViewPadding;
- int rightPadding = mExpandedViewPadding;
- if (insets != null) {
- // Can't have the expanded view overlaying notches
- int cutoutLeft = 0;
- int cutoutRight = 0;
- DisplayCutout cutout = insets.getDisplayCutout();
- if (cutout != null) {
- cutoutLeft = cutout.getSafeInsetLeft();
- cutoutRight = cutout.getSafeInsetRight();
- }
- // Or overlaying nav or status bar
- leftPadding += Math.max(cutoutLeft, insets.getStableInsetLeft());
- rightPadding += Math.max(cutoutRight, insets.getStableInsetRight());
- }
- mExpandedViewContainer.setPadding(leftPadding, mExpandedViewPadding,
- rightPadding, mExpandedViewPadding);
-
- if (mIsExpanded) {
- // Re-draw bubble row and pointer for new orientation.
- beforeExpandedViewAnimation();
- updateOverflowVisibility();
- updatePointerPosition();
- mExpandedAnimationController.expandFromStack(() -> {
- afterExpandedViewAnimation();
- } /* after */);
- mExpandedViewContainer.setTranslationX(0);
- mExpandedViewContainer.setTranslationY(getExpandedViewY());
- mExpandedViewContainer.setAlpha(1f);
- }
- if (mRelativeStackPositionBeforeRotation != null) {
- mStackAnimationController.setStackPosition(
- mRelativeStackPositionBeforeRotation);
- mRelativeStackPositionBeforeRotation = null;
- }
- removeOnLayoutChangeListener(mOrientationChangedListener);
- };
-
- // This must be a separate OnDrawListener since it should be called for every draw.
- getViewTreeObserver().addOnDrawListener(mSystemGestureExcludeUpdater);
-
- final ColorMatrix animatedMatrix = new ColorMatrix();
- final ColorMatrix darkenMatrix = new ColorMatrix();
-
- mDesaturateAndDarkenAnimator = ValueAnimator.ofFloat(1f, 0f);
- mDesaturateAndDarkenAnimator.addUpdateListener(animation -> {
- final float animatedValue = (float) animation.getAnimatedValue();
- animatedMatrix.setSaturation(animatedValue);
-
- final float animatedDarkenValue = (1f - animatedValue) * DARKEN_PERCENT;
- darkenMatrix.setScale(
- 1f - animatedDarkenValue /* red */,
- 1f - animatedDarkenValue /* green */,
- 1f - animatedDarkenValue /* blue */,
- 1f /* alpha */);
-
- // Concat the matrices so that the animatedMatrix both desaturates and darkens.
- animatedMatrix.postConcat(darkenMatrix);
-
- // Update the paint and apply it to the bubble container.
- mDesaturateAndDarkenPaint.setColorFilter(new ColorMatrixColorFilter(animatedMatrix));
-
- if (mDesaturateAndDarkenTargetView != null) {
- mDesaturateAndDarkenTargetView.setLayerPaint(mDesaturateAndDarkenPaint);
- }
- });
-
- // If the stack itself is touched, it means none of its touchable views (bubbles, flyouts,
- // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
- setOnTouchListener((view, ev) -> {
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mShowingManage) {
- showManageMenu(false /* show */);
- } else if (mBubbleData.isExpanded()) {
- mBubbleData.setExpanded(false);
- }
- }
-
- return true;
- });
-
- animate()
- .setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED)
- .setDuration(FADE_IN_DURATION);
- }
-
- /**
- * Sets whether or not the stack should become temporarily invisible by moving off the side of
- * the screen.
- *
- * If a flyout comes in while it's invisible, it will animate back in while the flyout is
- * showing but disappear again when the flyout is gone.
- */
- public void setTemporarilyInvisible(boolean invisible) {
- mTemporarilyInvisible = invisible;
-
- // If we are animating out, hide immediately if possible so we animate out with the status
- // bar.
- updateTemporarilyInvisibleAnimation(invisible /* hideImmediately */);
- }
-
- /**
- * Animates the stack to be temporarily invisible, if needed.
- *
- * If we're currently dragging the stack, or a flyout is visible, the stack will remain visible.
- * regardless of the value of {@link #mTemporarilyInvisible}. This method is called on ACTION_UP
- * as well as whenever a flyout hides, so we will animate invisible at that point if needed.
- */
- private void updateTemporarilyInvisibleAnimation(boolean hideImmediately) {
- removeCallbacks(mAnimateTemporarilyInvisibleImmediate);
-
- if (mIsDraggingStack) {
- // If we're dragging the stack, don't animate it invisible.
- return;
- }
-
- final boolean shouldHide =
- mTemporarilyInvisible && mFlyout.getVisibility() != View.VISIBLE;
-
- postDelayed(mAnimateTemporarilyInvisibleImmediate,
- shouldHide && !hideImmediately ? ANIMATE_TEMPORARILY_INVISIBLE_DELAY : 0);
- }
-
- private final Runnable mAnimateTemporarilyInvisibleImmediate = () -> {
- if (mTemporarilyInvisible && mFlyout.getVisibility() != View.VISIBLE) {
- if (mStackAnimationController.isStackOnLeftSide()) {
- animate().translationX(-mBubbleSize).start();
- } else {
- animate().translationX(mBubbleSize).start();
- }
- } else {
- animate().translationX(0).start();
- }
- };
-
- 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);
-
- mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
- }
- });
- 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 */);
- mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey());
- });
-
- mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
- view -> {
- showManageMenu(false /* show */);
- final Bubble bubble = mBubbleData.getSelectedBubble();
- if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- final Intent intent = bubble.getSettingsIntent(mContext);
- mBubbleData.setExpanded(false);
- mContext.startActivityAsUser(intent, bubble.getUser());
- logBubbleEvent(bubble,
- FrameworkStatsLog.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);
-
- // The menu itself should respect locale direction so the icons are on the correct side.
- mManageMenu.setLayoutDirection(LAYOUT_DIRECTION_LOCALE);
- addView(mManageMenu);
- }
-
- /**
- * Whether the educational view should show for the expanded view "manage" menu.
- */
- private boolean shouldShowManageEdu() {
- final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION);
- final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext))
- && mExpandedBubble != null;
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show manage edu: " + shouldShow);
- }
- return shouldShow;
- }
-
- private void maybeShowManageEdu() {
- if (!shouldShowManageEdu()) {
- return;
- }
- if (mManageEduView == null) {
- mManageEduView = new ManageEducationView(mContext);
- addView(mManageEduView);
- }
- mManageEduView.show(mExpandedBubble.getExpandedView(), mTempRect);
- }
-
- /**
- * Whether education view should show for the collapsed stack.
- */
- private boolean shouldShowStackEdu() {
- final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION);
- final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext);
- if (BubbleDebugConfig.DEBUG_USER_EDUCATION) {
- Log.d(TAG, "Show stack edu: " + shouldShow);
- }
- return shouldShow;
- }
-
- private boolean getPrefBoolean(String key) {
- return mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE)
- .getBoolean(key, false /* default */);
- }
-
- /**
- * @return true if education view for collapsed stack should show and was not showing before.
- */
- private boolean maybeShowStackEdu() {
- if (!shouldShowStackEdu()) {
- return false;
- }
- if (mStackEduView == null) {
- mStackEduView = new StackEducationView(mContext);
- addView(mStackEduView);
- }
- return mStackEduView.show(mStackAnimationController.getStartPosition());
- }
-
- private void updateUserEdu() {
- maybeShowStackEdu();
- if (mManageEduView != null) {
- mManageEduView.invalidate();
- }
- maybeShowManageEdu();
- if (mStackEduView != null) {
- mStackEduView.invalidate();
- }
- }
-
- @SuppressLint("ClickableViewAccessibility")
- private void setUpFlyout() {
- if (mFlyout != null) {
- removeView(mFlyout);
- }
- mFlyout = new BubbleFlyoutView(getContext());
- mFlyout.setVisibility(GONE);
- mFlyout.animate()
- .setDuration(FLYOUT_ALPHA_ANIMATION_DURATION)
- .setInterpolator(new AccelerateDecelerateInterpolator());
- mFlyout.setOnClickListener(mFlyoutClickListener);
- mFlyout.setOnTouchListener(mFlyoutTouchListener);
- addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- }
-
- void updateFlyout(float fontScale) {
- mFlyout.updateFontSize(fontScale);
- }
-
- private void updateOverflow() {
- mBubbleOverflow.update();
- mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
- mBubbleContainer.getChildCount() - 1 /* index */);
- updateOverflowVisibility();
- }
-
- void updateOverflowButtonDot() {
- for (Bubble b : mBubbleData.getOverflowBubbles()) {
- if (b.showDot()) {
- mBubbleOverflow.setShowDot(true);
- return;
- }
- }
- mBubbleOverflow.setShowDot(false);
- }
-
- /**
- * Handle theme changes.
- */
- public void onThemeChanged() {
- setUpFlyout();
- setUpManageMenu();
- updateOverflow();
- updateUserEdu();
- updateExpandedViewTheme();
- }
-
- /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
- public void onOrientationChanged(int orientation) {
- mOrientation = orientation;
-
- // Display size is based on the rotation device was in when requested, we should update it
- // We use the real size & subtract screen decorations / window insets ourselves when needed
- WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
- wm.getDefaultDisplay().getRealSize(mDisplaySize);
-
- // Some resources change depending on orientation
- Resources res = getContext().getResources();
- mStatusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
-
- mRelativeStackPositionBeforeRotation = mStackAnimationController.getRelativeStackPosition();
- addOnLayoutChangeListener(mOrientationChangedListener);
- hideFlyoutImmediate();
-
- mManageMenu.setVisibility(View.INVISIBLE);
- mShowingManage = false;
- }
-
- /** Tells the views with locale-dependent layout direction to resolve the new direction. */
- public void onLayoutDirectionChanged(int direction) {
- mManageMenu.setLayoutDirection(direction);
- mFlyout.setLayoutDirection(direction);
- if (mStackEduView != null) {
- mStackEduView.setLayoutDirection(direction);
- }
- if (mManageEduView != null) {
- mManageEduView.setLayoutDirection(direction);
- }
- updateExpandedViewDirection(direction);
- }
-
- /** Respond to the display size change by recalculating view size and location. */
- public void onDisplaySizeChanged() {
- updateOverflow();
-
- WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
- wm.getDefaultDisplay().getRealSize(mDisplaySize);
- Resources res = getContext().getResources();
- mStatusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
- for (Bubble b : mBubbleData.getBubbles()) {
- if (b.getIconView() == null) {
- Log.d(TAG, "Display size changed. Icon null: " + b);
- continue;
- }
- b.getIconView().setLayoutParams(new LayoutParams(mBubbleSize, mBubbleSize));
- }
- mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
- mStackAnimationController.updateResources(mOrientation);
- mDismissView.updateResources();
- mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
- }
-
- @Override
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-
- mTempRect.setEmpty();
- getTouchableRegion(mTempRect);
- if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null
- && mExpandedBubble.getExpandedView().getTaskView() != null) {
- inoutInfo.touchableRegion.set(mTempRect);
- mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect);
- inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE);
- } else {
- inoutInfo.touchableRegion.set(mTempRect);
- }
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- getViewTreeObserver().removeOnPreDrawListener(mViewUpdater);
- getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- if (mBubbleOverflow != null && mBubbleOverflow.getExpandedView() != null) {
- mBubbleOverflow.getExpandedView().cleanUpExpandedState();
- }
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfoInternal(info);
- setupLocalMenu(info);
- }
-
- void updateExpandedViewTheme() {
- final List<Bubble> bubbles = mBubbleData.getBubbles();
- if (bubbles.isEmpty()) {
- return;
- }
- bubbles.forEach(bubble -> {
- if (bubble.getExpandedView() != null) {
- bubble.getExpandedView().applyThemeAttrs();
- }
- });
- }
-
- void updateExpandedViewDirection(int direction) {
- final List<Bubble> bubbles = mBubbleData.getBubbles();
- if (bubbles.isEmpty()) {
- return;
- }
- bubbles.forEach(bubble -> {
- if (bubble.getExpandedView() != null) {
- bubble.getExpandedView().setLayoutDirection(direction);
- }
- });
- }
-
- void setupLocalMenu(AccessibilityNodeInfo info) {
- Resources res = mContext.getResources();
-
- // Custom local actions.
- AccessibilityAction moveTopLeft = new AccessibilityAction(R.id.action_move_top_left,
- res.getString(R.string.bubble_accessibility_action_move_top_left));
- info.addAction(moveTopLeft);
-
- AccessibilityAction moveTopRight = new AccessibilityAction(R.id.action_move_top_right,
- res.getString(R.string.bubble_accessibility_action_move_top_right));
- info.addAction(moveTopRight);
-
- AccessibilityAction moveBottomLeft = new AccessibilityAction(R.id.action_move_bottom_left,
- res.getString(R.string.bubble_accessibility_action_move_bottom_left));
- info.addAction(moveBottomLeft);
-
- AccessibilityAction moveBottomRight = new AccessibilityAction(R.id.action_move_bottom_right,
- res.getString(R.string.bubble_accessibility_action_move_bottom_right));
- info.addAction(moveBottomRight);
-
- // Default actions.
- info.addAction(AccessibilityAction.ACTION_DISMISS);
- if (mIsExpanded) {
- info.addAction(AccessibilityAction.ACTION_COLLAPSE);
- } else {
- info.addAction(AccessibilityAction.ACTION_EXPAND);
- }
- }
-
- @Override
- public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
- if (super.performAccessibilityActionInternal(action, arguments)) {
- return true;
- }
- final RectF stackBounds = mStackAnimationController.getAllowableStackPositionRegion();
-
- // R constants are not final so we cannot use switch-case here.
- if (action == AccessibilityNodeInfo.ACTION_DISMISS) {
- mBubbleData.dismissAll(BubbleController.DISMISS_ACCESSIBILITY_ACTION);
- announceForAccessibility(
- getResources().getString(R.string.accessibility_bubble_dismissed));
- return true;
- } else if (action == AccessibilityNodeInfo.ACTION_COLLAPSE) {
- mBubbleData.setExpanded(false);
- return true;
- } else if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
- mBubbleData.setExpanded(true);
- return true;
- } else if (action == R.id.action_move_top_left) {
- mStackAnimationController.springStackAfterFling(stackBounds.left, stackBounds.top);
- return true;
- } else if (action == R.id.action_move_top_right) {
- mStackAnimationController.springStackAfterFling(stackBounds.right, stackBounds.top);
- return true;
- } else if (action == R.id.action_move_bottom_left) {
- mStackAnimationController.springStackAfterFling(stackBounds.left, stackBounds.bottom);
- return true;
- } else if (action == R.id.action_move_bottom_right) {
- mStackAnimationController.springStackAfterFling(stackBounds.right, stackBounds.bottom);
- return true;
- }
- return false;
- }
-
- /**
- * Update content description for a11y TalkBack.
- */
- public void updateContentDescription() {
- if (mBubbleData.getBubbles().isEmpty()) {
- return;
- }
-
- for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
- final Bubble bubble = mBubbleData.getBubbles().get(i);
- final String appName = bubble.getAppName();
-
- String titleStr = bubble.getTitle();
- if (titleStr == null) {
- titleStr = getResources().getString(R.string.notification_bubble_title);
- }
-
- if (bubble.getIconView() != null) {
- if (mIsExpanded || i > 0) {
- bubble.getIconView().setContentDescription(getResources().getString(
- R.string.bubble_content_description_single, titleStr, appName));
- } else {
- final int moreCount = mBubbleContainer.getChildCount() - 1;
- bubble.getIconView().setContentDescription(getResources().getString(
- R.string.bubble_content_description_stack,
- titleStr, appName, moreCount));
- }
- }
- }
- }
-
- private void updateSystemGestureExcludeRects() {
- // Exclude the region occupied by the first BubbleView in the stack
- Rect excludeZone = mSystemGestureExclusionRects.get(0);
- if (getBubbleCount() > 0) {
- View firstBubble = mBubbleContainer.getChildAt(0);
- excludeZone.set(firstBubble.getLeft(), firstBubble.getTop(), firstBubble.getRight(),
- firstBubble.getBottom());
- excludeZone.offset((int) (firstBubble.getTranslationX() + 0.5f),
- (int) (firstBubble.getTranslationY() + 0.5f));
- mBubbleContainer.setSystemGestureExclusionRects(mSystemGestureExclusionRects);
- } else {
- excludeZone.setEmpty();
- mBubbleContainer.setSystemGestureExclusionRects(Collections.emptyList());
- }
- }
-
- /**
- * Sets the listener to notify when the bubble stack is expanded.
- */
- public void setExpandListener(BubbleController.BubbleExpandListener listener) {
- mExpandListener = listener;
- }
-
- /** Sets the function to call to un-bubble the given conversation. */
- public void setUnbubbleConversationCallback(
- Consumer<String> unbubbleConversationCallback) {
- mUnbubbleConversationCallback = unbubbleConversationCallback;
- }
-
- /**
- * Whether the stack of bubbles is expanded or not.
- */
- public boolean isExpanded() {
- return mIsExpanded;
- }
-
- /**
- * Whether the stack of bubbles is animating to or from expansion.
- */
- public boolean isExpansionAnimating() {
- return mIsExpansionAnimating;
- }
-
- /**
- * The {@link Bubble} that is expanded, null if one does not exist.
- */
- @Nullable
- BubbleViewProvider getExpandedBubble() {
- return mExpandedBubble;
- }
-
- // via BubbleData.Listener
- @SuppressLint("ClickableViewAccessibility")
- void addBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "addBubble: " + bubble);
- }
-
- if (getBubbleCount() == 0 && shouldShowStackEdu()) {
- // Override the default stack position if we're showing user education.
- mStackAnimationController.setStackPosition(
- mStackAnimationController.getStartPosition());
- }
-
- if (getBubbleCount() == 0) {
- mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
- }
-
- if (bubble.getIconView() == null) {
- return;
- }
-
- // Set the dot position to the opposite of the side the stack is resting on, since the stack
- // resting slightly off-screen would result in the dot also being off-screen.
- bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
-
- bubble.getIconView().setOnClickListener(mBubbleClickListener);
- bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
-
- mBubbleContainer.addView(bubble.getIconView(), 0,
- new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
- animateInFlyoutForBubble(bubble);
- requestUpdate();
- logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
- }
-
- // via BubbleData.Listener
- void removeBubble(Bubble bubble) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "removeBubble: " + bubble);
- }
- // Remove it from the views
- for (int i = 0; i < getBubbleCount(); i++) {
- View v = mBubbleContainer.getChildAt(i);
- if (v instanceof BadgedImageView
- && ((BadgedImageView) v).getKey().equals(bubble.getKey())) {
- mBubbleContainer.removeViewAt(i);
- if (mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())) {
- bubble.cleanupExpandedView();
- } else {
- bubble.cleanupViews();
- }
- updatePointerPosition();
- logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__DISMISSED);
- return;
- }
- }
- Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
- }
-
- private void updateOverflowVisibility() {
- if (mBubbleOverflow == null) {
- return;
- }
- mBubbleOverflow.setVisible(mIsExpanded ? VISIBLE : GONE);
- }
-
- // via BubbleData.Listener
- void updateBubble(Bubble bubble) {
- animateInFlyoutForBubble(bubble);
- requestUpdate();
- logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__UPDATED);
- }
-
- public void updateBubbleOrder(List<Bubble> bubbles) {
- for (int i = 0; i < bubbles.size(); i++) {
- Bubble bubble = bubbles.get(i);
- mBubbleContainer.reorderView(bubble.getIconView(), i);
- }
- updateBubbleIcons();
- updatePointerPosition();
- }
-
- /**
- * Changes the currently selected bubble. If the stack is already expanded, the newly selected
- * bubble will be shown immediately. This does not change the expanded state or change the
- * position of any bubble.
- */
- // via BubbleData.Listener
- public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setSelectedBubble: " + bubbleToSelect);
- }
-
- if (bubbleToSelect == null) {
- mBubbleData.setShowingOverflow(false);
- return;
- }
-
- // Ignore this new bubble only if it is the exact same bubble object. Otherwise, we'll want
- // to re-render it even if it has the same key (equals() returns true). If the currently
- // expanded bubble is removed and instantly re-added, we'll get back a new Bubble instance
- // with the same key (with newly inflated expanded views), and we need to render those new
- // views.
- if (mExpandedBubble == bubbleToSelect) {
- return;
- }
-
- if (bubbleToSelect.getKey().equals(BubbleOverflow.KEY)) {
- mBubbleData.setShowingOverflow(true);
- } else {
- mBubbleData.setShowingOverflow(false);
- }
-
- if (mIsExpanded && mIsExpansionAnimating) {
- // If the bubble selection changed during the expansion animation, the expanding bubble
- // probably crashed or immediately removed itself (or, we just got unlucky with a new
- // auto-expanding bubble showing up at just the right time). Cancel the animations so we
- // can start fresh.
- cancelAllExpandCollapseSwitchAnimations();
- }
-
- // If we're expanded, screenshot the currently expanded bubble (before expanding the newly
- // selected bubble) so we can animate it out.
- if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- // Before screenshotting, have the real ActivityView show on top of other surfaces
- // so that the screenshot doesn't flicker on top of it.
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
- }
-
- try {
- screenshotAnimatingOutBubbleIntoSurface((success) -> {
- mAnimatingOutSurfaceContainer.setVisibility(
- success ? View.VISIBLE : View.INVISIBLE);
- showNewlySelectedBubble(bubbleToSelect);
- });
- } catch (Exception e) {
- showNewlySelectedBubble(bubbleToSelect);
- e.printStackTrace();
- }
- } else {
- showNewlySelectedBubble(bubbleToSelect);
- }
- }
-
- private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
- final BubbleViewProvider previouslySelected = mExpandedBubble;
- mExpandedBubble = bubbleToSelect;
- updatePointerPosition();
-
- if (mIsExpanded) {
- hideCurrentInputMethod();
-
- // Make the container of the expanded view transparent before removing the expanded view
- // from it. Otherwise a punch hole created by {@link android.view.SurfaceView} in the
- // expanded view becomes visible on the screen. See b/126856255
- mExpandedViewContainer.setAlpha(0.0f);
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
- if (previouslySelected != null) {
- previouslySelected.setContentVisibility(false);
- }
-
- updateExpandedBubble();
- requestUpdate();
-
- logBubbleEvent(previouslySelected,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- logBubbleEvent(bubbleToSelect,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- notifyExpansionChanged(previouslySelected, false /* expanded */);
- notifyExpansionChanged(bubbleToSelect, true /* expanded */);
- });
- }
- }
-
- /**
- * Changes the expanded state of the stack.
- *
- * @param shouldExpand whether the bubble stack should appear expanded
- */
- // via BubbleData.Listener
- public void setExpanded(boolean shouldExpand) {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "setExpanded: " + shouldExpand);
- }
-
- if (!shouldExpand) {
- // If we're collapsing, release the animating-out surface immediately since we have no
- // need for it, and this ensures it cannot remain visible as we collapse.
- releaseAnimatingOutBubbleBuffer();
- }
-
- if (shouldExpand == mIsExpanded) {
- return;
- }
-
- hideCurrentInputMethod();
-
- mOnBubbleExpandChanged.accept(shouldExpand);
-
- if (mIsExpanded) {
- animateCollapse();
- logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
- } else {
- animateExpansion();
- // TODO: move next line to BubbleData
- logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
- logBubbleEvent(mExpandedBubble,
- FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
- }
- notifyExpansionChanged(mExpandedBubble, mIsExpanded);
- }
-
- /**
- * Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or
- * not.
- */
- void hideCurrentInputMethod() {
- mHideCurrentInputMethodCallback.run();
- }
-
- private void beforeExpandedViewAnimation() {
- mIsExpansionAnimating = true;
- hideFlyoutImmediate();
- updateExpandedBubble();
- updateExpandedView();
- }
-
- private void afterExpandedViewAnimation() {
- mIsExpansionAnimating = false;
- updateExpandedView();
- requestUpdate();
- }
-
- private void animateExpansion() {
- cancelDelayedExpandCollapseSwitchAnimations();
-
- mIsExpanded = true;
- if (mStackEduView != null) {
- mStackEduView.hide(true /* fromExpansion */);
- }
- beforeExpandedViewAnimation();
-
- mBubbleContainer.setActiveController(mExpandedAnimationController);
- updateOverflowVisibility();
- updatePointerPosition();
- mExpandedAnimationController.expandFromStack(() -> {
- if (mIsExpanded && mExpandedBubble.getExpandedView() != null) {
- maybeShowManageEdu();
- }
- } /* after */);
-
- mExpandedViewContainer.setTranslationX(0);
- mExpandedViewContainer.setTranslationY(getExpandedViewY());
- mExpandedViewContainer.setAlpha(1f);
-
- // X-value of the bubble we're expanding, once it's settled in its row.
- final float bubbleWillBeAtX =
- mExpandedAnimationController.getBubbleLeft(
- mBubbleData.getBubbles().indexOf(mExpandedBubble));
-
- // How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles
- // that are animating farther, so that the expanded view doesn't move as much.
- final float horizontalDistanceAnimated =
- Math.abs(bubbleWillBeAtX
- - mStackAnimationController.getStackPosition().x);
-
- // Wait for the path animation target to reach its end, and add a small amount of extra time
- // if the bubble is moving a lot horizontally.
- long startDelay = 0L;
-
- // Should not happen since we lay out before expanding, but just in case...
- if (getWidth() > 0) {
- startDelay = (long)
- (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION
- + (horizontalDistanceAnimated / getWidth()) * 30);
- }
-
- // Set the pivot point for the scale, so the expanded view animates out from the bubble.
- mExpandedViewContainerMatrix.setScale(
- 0f, 0f,
- bubbleWillBeAtX + mBubbleSize / 2f, getExpandedViewY());
- mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- }
-
- mDelayedAnimationHandler.postDelayed(() -> {
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .addUpdateListener((target, values) -> {
- if (mExpandedBubble == null || mExpandedBubble.getIconView() == null) {
- return;
- }
- mExpandedViewContainerMatrix.postTranslate(
- mExpandedBubble.getIconView().getTranslationX()
- - bubbleWillBeAtX,
- 0);
- mExpandedViewContainer.setAnimationMatrix(
- mExpandedViewContainerMatrix);
- })
- .withEndActions(() -> {
- afterExpandedViewAnimation();
- if (mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView()
- .setSurfaceZOrderedOnTop(false);
- }
- })
- .start();
- }, startDelay);
- }
-
- private void animateCollapse() {
- cancelDelayedExpandCollapseSwitchAnimations();
-
- // Hide the menu if it's visible.
- showManageMenu(false);
-
- mIsExpanded = false;
- mIsExpansionAnimating = true;
-
- mBubbleContainer.cancelAllAnimations();
-
- // If we were in the middle of swapping, the animating-out surface would have been scaling
- // to zero - finish it off.
- PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
- mAnimatingOutSurfaceContainer.setScaleX(0f);
- mAnimatingOutSurfaceContainer.setScaleY(0f);
-
- // Let the expanded animation controller know that it shouldn't animate child adds/reorders
- // since we're about to animate collapsed.
- mExpandedAnimationController.notifyPreparingToCollapse();
-
- final long startDelay =
- (long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
- mDelayedAnimationHandler.postDelayed(() -> mExpandedAnimationController.collapseBackToStack(
- mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
- /* collapseTo */,
- () -> mBubbleContainer.setActiveController(mStackAnimationController)), startDelay);
-
- // We want to visually collapse into this bubble during the animation.
- final View expandingFromBubble = mExpandedBubble.getIconView();
-
- // X-value the bubble is animating from (back into the stack).
- final float expandingFromBubbleAtX =
- mExpandedAnimationController.getBubbleLeft(
- mBubbleData.getBubbles().indexOf(mExpandedBubble));
-
- // Set the pivot point.
- mExpandedViewContainerMatrix.setScale(
- 1f, 1f,
- expandingFromBubbleAtX + mBubbleSize / 2f,
- getExpandedViewY());
-
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig)
- .addUpdateListener((target, values) -> {
- if (expandingFromBubble != null) {
- // Follow the bubble as it translates!
- mExpandedViewContainerMatrix.postTranslate(
- expandingFromBubble.getTranslationX()
- - expandingFromBubbleAtX, 0f);
- }
-
- mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-
- // Hide early so we don't have a tiny little expanded view still visible at the
- // end of the scale animation.
- if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) {
- mExpandedViewContainer.setVisibility(View.INVISIBLE);
- }
- })
- .withEndActions(() -> {
- final BubbleViewProvider previouslySelected = mExpandedBubble;
- beforeExpandedViewAnimation();
- if (mManageEduView != null) {
- mManageEduView.hide(false /* fromExpansion */);
- }
-
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "animateCollapse");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
- mExpandedBubble));
- }
- updateOverflowVisibility();
-
- afterExpandedViewAnimation();
- if (previouslySelected != null) {
- previouslySelected.setContentVisibility(false);
- }
- })
- .start();
- }
-
- private void animateSwitchBubbles() {
- // If we're no longer expanded, this is meaningless.
- if (!mIsExpanded) {
- return;
- }
-
- mIsBubbleSwitchAnimating = true;
-
- // The surface contains a screenshot of the animating out bubble, so we just need to animate
- // it out (and then release the GraphicBuffer).
- PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
- PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
- .spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
- .spring(DynamicAnimation.TRANSLATION_Y,
- mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2,
- mTranslateSpringConfig)
- .withEndActions(this::releaseAnimatingOutBubbleBuffer)
- .start();
-
- boolean isOverflow = mExpandedBubble != null
- && mExpandedBubble.getKey().equals(BubbleOverflow.KEY);
- float expandingFromBubbleDestinationX =
- mExpandedAnimationController.getBubbleLeft(isOverflow ? getBubbleCount()
- : mBubbleData.getBubbles().indexOf(mExpandedBubble));
-
- mExpandedViewContainer.setAlpha(1f);
- mExpandedViewContainer.setVisibility(View.VISIBLE);
-
- mExpandedViewContainerMatrix.setScale(
- 0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY());
- mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
-
- mDelayedAnimationHandler.postDelayed(() -> {
- if (!mIsExpanded) {
- mIsBubbleSwitchAnimating = false;
- return;
- }
-
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
- .spring(AnimatableScaleMatrix.SCALE_X,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .spring(AnimatableScaleMatrix.SCALE_Y,
- AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
- mScaleInSpringConfig)
- .addUpdateListener((target, values) -> {
- mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- })
- .withEndActions(() -> {
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- }
-
- mIsBubbleSwitchAnimating = false;
- })
- .start();
- }, 25);
- }
-
- /**
- * Cancels any delayed steps for expand/collapse and bubble switch animations, and resets the is
- * animating flags for those animations.
- */
- private void cancelDelayedExpandCollapseSwitchAnimations() {
- mDelayedAnimationHandler.removeCallbacksAndMessages(null);
-
- mIsExpansionAnimating = false;
- mIsBubbleSwitchAnimating = false;
- }
-
- private void cancelAllExpandCollapseSwitchAnimations() {
- cancelDelayedExpandCollapseSwitchAnimations();
-
- PhysicsAnimator.getInstance(mAnimatingOutSurfaceView).cancel();
- PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
-
- mExpandedViewContainer.setAnimationMatrix(null);
- }
-
- private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) {
- if (mExpandListener != null && bubble != null) {
- mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
- }
- }
-
- /** Moves the bubbles out of the way if they're going to be over the keyboard. */
- public void onImeVisibilityChanged(boolean visible, int height) {
- mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0);
-
- if (!mIsExpanded && getBubbleCount() > 0) {
- final float stackDestinationY =
- mStackAnimationController.animateForImeVisibility(visible);
-
- // How far the stack is animating due to IME, we'll just animate the flyout by that
- // much too.
- final float stackDy =
- stackDestinationY - mStackAnimationController.getStackPosition().y;
-
- // If the flyout is visible, translate it along with the bubble stack.
- if (mFlyout.getVisibility() == VISIBLE) {
- PhysicsAnimator.getInstance(mFlyout)
- .spring(DynamicAnimation.TRANSLATION_Y,
- mFlyout.getTranslationY() + stackDy,
- FLYOUT_IME_ANIMATION_SPRING_CONFIG)
- .start();
- }
- } else if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setImeVisible(visible);
- }
- }
-
- /**
- * This method is called by {@link android.app.ActivityView} because the BubbleStackView has a
- * higher Z-index than the ActivityView (so that dragged-out bubbles are visible over the AV).
- * ActivityView is asking BubbleStackView to subtract the stack's bounds from the provided
- * touchable region, so that the ActivityView doesn't consume events meant for the stack. Due to
- * the special nature of ActivityView, it does not respect the standard
- * {@link #dispatchTouchEvent} and {@link #onInterceptTouchEvent} methods typically used for
- * this purpose.
- *
- * BubbleStackView is MATCH_PARENT, so that bubbles can be positioned via their translation
- * properties for performance reasons. This means that the default implementation of this method
- * subtracts the entirety of the screen from the ActivityView's touchable region, resulting in
- * it not receiving any touch events. This was previously addressed by returning false in the
- * stack's {@link View#canReceivePointerEvents()} method, but this precluded the use of any
- * touch handlers in the stack or its child views.
- *
- * To support touch handlers, we're overriding this method to leave the ActivityView's touchable
- * region alone. The only touchable part of the stack that can ever overlap the AV is a
- * dragged-out bubble that is animating back into the row of bubbles. It's not worth continually
- * updating the touchable region to allow users to grab a bubble while it completes its ~50ms
- * animation back to the bubble row.
- *
- * NOTE: Any future additions to the stack that obscure the ActivityView region will need their
- * bounds subtracted here in order to receive touch events.
- */
- @Override
- public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
- // If the notification shade is expanded, or the manage menu is open, or we are showing
- // manage bubbles user education, we shouldn't let the ActivityView steal any touch events
- // from any location.
- if (!mIsExpanded
- || mShowingManage
- || (mManageEduView != null
- && mManageEduView.getVisibility() == VISIBLE)) {
- touchableRegion.setEmpty();
- }
- }
-
- /**
- * If you're here because you're not receiving touch events on a view that is a descendant of
- * BubbleStackView, and you think BSV is intercepting them - it's not! You need to subtract the
- * bounds of the view in question in {@link #subtractObscuredTouchableRegion}. The ActivityView
- * consumes all touch events within its bounds, even for views like the BubbleStackView that are
- * above it. It ignores typical view touch handling methods like this one and
- * dispatchTouchEvent.
- */
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return super.onInterceptTouchEvent(ev);
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
- // Ignore touches from additional pointer indices.
- return false;
- }
-
- if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- mPointerIndexDown = ev.getActionIndex();
- } else if (ev.getAction() == MotionEvent.ACTION_UP
- || ev.getAction() == MotionEvent.ACTION_CANCEL) {
- mPointerIndexDown = -1;
- }
-
- boolean dispatched = super.dispatchTouchEvent(ev);
-
- // If a new bubble arrives while the collapsed stack is being dragged, it will be positioned
- // at the front of the stack (under the touch position). Subsequent ACTION_MOVE events will
- // then be passed to the new bubble, which will not consume them since it hasn't received an
- // ACTION_DOWN yet. Work around this by passing MotionEvents directly to the touch handler
- // until the current gesture ends with an ACTION_UP event.
- if (!dispatched && !mIsExpanded && mIsGestureInProgress) {
- dispatched = mBubbleTouchListener.onTouch(this /* view */, ev);
- }
-
- mIsGestureInProgress =
- ev.getAction() != MotionEvent.ACTION_UP
- && ev.getAction() != MotionEvent.ACTION_CANCEL;
-
- return dispatched;
- }
-
- void setFlyoutStateForDragLength(float deltaX) {
- // This shouldn't happen, but if it does, just wait until the flyout lays out. This method
- // is continually called.
- if (mFlyout.getWidth() <= 0) {
- return;
- }
-
- final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
- mFlyoutDragDeltaX = deltaX;
-
- final float collapsePercent =
- onLeft ? -deltaX / mFlyout.getWidth() : deltaX / mFlyout.getWidth();
- mFlyout.setCollapsePercent(Math.min(1f, Math.max(0f, collapsePercent)));
-
- // Calculate how to translate the flyout if it has been dragged too far in either direction.
- float overscrollTranslation = 0f;
- if (collapsePercent < 0f || collapsePercent > 1f) {
- // Whether we are more than 100% transitioned to the dot.
- final boolean overscrollingPastDot = collapsePercent > 1f;
-
- // Whether we are overscrolling physically to the left - this can either be pulling the
- // flyout away from the stack (if the stack is on the right) or pushing it to the left
- // after it has already become the dot.
- final boolean overscrollingLeft =
- (onLeft && collapsePercent > 1f) || (!onLeft && collapsePercent < 0f);
- overscrollTranslation =
- (overscrollingPastDot ? collapsePercent - 1f : collapsePercent * -1)
- * (overscrollingLeft ? -1 : 1)
- * (mFlyout.getWidth() / (FLYOUT_OVERSCROLL_ATTENUATION_FACTOR
- // Attenuate the smaller dot less than the larger flyout.
- / (overscrollingPastDot ? 2 : 1)));
- }
-
- mFlyout.setTranslationX(mFlyout.getRestingTranslationX() + overscrollTranslation);
- }
-
- /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */
- private boolean passEventToMagnetizedObject(MotionEvent event) {
- return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event);
- }
-
- /**
- * Dismisses the magnetized object - either an individual bubble, if we're expanded, or the
- * stack, if we're collapsed.
- */
- private void dismissMagnetizedObject() {
- if (mIsExpanded) {
- final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
- dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
- } else {
- mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
- }
- }
-
- private void dismissBubbleIfExists(@Nullable Bubble bubble) {
- if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- mBubbleData.dismissBubbleWithKey(
- bubble.getKey(), BubbleController.DISMISS_USER_GESTURE);
- }
- }
-
- /** Prepares and starts the desaturate/darken animation on the bubble stack. */
- private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
- mDesaturateAndDarkenTargetView = targetView;
-
- if (mDesaturateAndDarkenTargetView == null) {
- return;
- }
-
- if (desaturateAndDarken) {
- // Use the animated paint for the bubbles.
- mDesaturateAndDarkenTargetView.setLayerType(
- View.LAYER_TYPE_HARDWARE, mDesaturateAndDarkenPaint);
- mDesaturateAndDarkenAnimator.removeAllListeners();
- mDesaturateAndDarkenAnimator.start();
- } else {
- mDesaturateAndDarkenAnimator.removeAllListeners();
- mDesaturateAndDarkenAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- // Stop using the animated paint.
- resetDesaturationAndDarken();
- }
- });
- mDesaturateAndDarkenAnimator.reverse();
- }
- }
-
- private void resetDesaturationAndDarken() {
-
- mDesaturateAndDarkenAnimator.removeAllListeners();
- mDesaturateAndDarkenAnimator.cancel();
-
- if (mDesaturateAndDarkenTargetView != null) {
- mDesaturateAndDarkenTargetView.setLayerType(View.LAYER_TYPE_NONE, null);
- mDesaturateAndDarkenTargetView = null;
- }
- }
-
- /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
- private void animateFlyoutCollapsed(boolean collapsed, float velX) {
- final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
- // If the flyout was tapped, we want a higher stiffness for the collapse animation so it's
- // faster.
- mFlyoutTransitionSpring.getSpring().setStiffness(
- (mBubbleToExpandAfterFlyoutCollapse != null)
- ? SpringForce.STIFFNESS_MEDIUM
- : SpringForce.STIFFNESS_LOW);
- mFlyoutTransitionSpring
- .setStartValue(mFlyoutDragDeltaX)
- .setStartVelocity(velX)
- .animateToFinalPosition(collapsed
- ? (onLeft ? -mFlyout.getWidth() : mFlyout.getWidth())
- : 0f);
- }
-
- /**
- * Calculates the y position of the expanded view when it is expanded.
- */
- float getExpandedViewY() {
- return getStatusBarHeight() + mBubbleSize + mBubblePaddingTop;
- }
-
- private boolean shouldShowFlyout(Bubble bubble) {
- Bubble.FlyoutMessage flyoutMessage = bubble.getFlyoutMessage();
- final BadgedImageView bubbleView = bubble.getIconView();
- if (flyoutMessage == null
- || flyoutMessage.message == null
- || !bubble.showFlyout()
- || (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE)
- || isExpanded()
- || mIsExpansionAnimating
- || mIsGestureInProgress
- || mBubbleToExpandAfterFlyoutCollapse != null
- || bubbleView == null) {
- if (bubbleView != null && mFlyout.getVisibility() != VISIBLE) {
- bubbleView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
- }
- // Skip the message if none exists, we're expanded or animating expansion, or we're
- // about to expand a bubble from the previous tapped flyout, or if bubble view is null.
- return false;
- }
- return true;
- }
-
- /**
- * Animates in the flyout for the given bubble, if available, and then hides it after some time.
- */
- @VisibleForTesting
- void animateInFlyoutForBubble(Bubble bubble) {
- if (!shouldShowFlyout(bubble)) {
- return;
- }
-
- mFlyoutDragDeltaX = 0f;
- clearFlyoutOnHide();
- mAfterFlyoutHidden = () -> {
- // Null it out to ensure it runs once.
- mAfterFlyoutHidden = null;
-
- if (mBubbleToExpandAfterFlyoutCollapse != null) {
- // User tapped on the flyout and we should expand
- mBubbleData.setSelectedBubble(mBubbleToExpandAfterFlyoutCollapse);
- mBubbleData.setExpanded(true);
- mBubbleToExpandAfterFlyoutCollapse = null;
- }
-
- // Stop suppressing the dot now that the flyout has morphed into the dot.
- bubble.getIconView().removeDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
-
- // Hide the stack after a delay, if needed.
- updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
- };
-
- // Suppress the dot when we are animating the flyout.
- bubble.getIconView().addDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
-
- // Start flyout expansion. Post in case layout isn't complete and getWidth returns 0.
- post(() -> {
- // An auto-expanding bubble could have been posted during the time it takes to
- // layout.
- if (isExpanded() || bubble.getIconView() == null) {
- return;
- }
- final Runnable expandFlyoutAfterDelay = () -> {
- mAnimateInFlyout = () -> {
- mFlyout.setVisibility(VISIBLE);
- updateTemporarilyInvisibleAnimation(false /* hideImmediately */);
- mFlyoutDragDeltaX =
- mStackAnimationController.isStackOnLeftSide()
- ? -mFlyout.getWidth()
- : mFlyout.getWidth();
- animateFlyoutCollapsed(false /* collapsed */, 0 /* velX */);
- mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- };
- mFlyout.postDelayed(mAnimateInFlyout, 200);
- };
-
-
- if (mFlyout.getVisibility() == View.VISIBLE) {
- mFlyout.animateUpdate(bubble.getFlyoutMessage(), getWidth(),
- mStackAnimationController.getStackPosition().y);
- } else {
- mFlyout.setVisibility(INVISIBLE);
- mFlyout.setupFlyoutStartingAsDot(bubble.getFlyoutMessage(),
- mStackAnimationController.getStackPosition(), getWidth(),
- mStackAnimationController.isStackOnLeftSide(),
- bubble.getIconView().getDotColor() /* dotColor */,
- expandFlyoutAfterDelay /* onLayoutComplete */,
- mAfterFlyoutHidden,
- bubble.getIconView().getDotCenter(),
- !bubble.showDot());
- }
- mFlyout.bringToFront();
- });
- mFlyout.removeCallbacks(mHideFlyout);
- mFlyout.postDelayed(mHideFlyout, FLYOUT_HIDE_AFTER);
- logBubbleEvent(bubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__FLYOUT);
- }
-
- /** Hide the flyout immediately and cancel any pending hide runnables. */
- private void hideFlyoutImmediate() {
- clearFlyoutOnHide();
- mFlyout.removeCallbacks(mAnimateInFlyout);
- mFlyout.removeCallbacks(mHideFlyout);
- mFlyout.hideFlyout();
- }
-
- private void clearFlyoutOnHide() {
- mFlyout.removeCallbacks(mAnimateInFlyout);
- if (mAfterFlyoutHidden == null) {
- return;
- }
- mAfterFlyoutHidden.run();
- mAfterFlyoutHidden = null;
- }
-
- /**
- * Fills the Rect with the touchable region of the bubbles. This will be used by WindowManager
- * to decide which touch events go to Bubbles.
- *
- * Bubbles is below the status bar/notification shade but above application windows. If you're
- * trying to get touch events from the status bar or another higher-level window layer, you'll
- * need to re-order TYPE_BUBBLES in WindowManagerPolicy so that we have the opportunity to steal
- * them.
- */
- public void getTouchableRegion(Rect outRect) {
- if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) {
- // When user education shows then capture all touches
- outRect.set(0, 0, getWidth(), getHeight());
- return;
- }
-
- if (!mIsExpanded) {
- if (getBubbleCount() > 0) {
- mBubbleContainer.getChildAt(0).getBoundsOnScreen(outRect);
- // Increase the touch target size of the bubble
- outRect.top -= mBubbleTouchPadding;
- outRect.left -= mBubbleTouchPadding;
- outRect.right += mBubbleTouchPadding;
- outRect.bottom += mBubbleTouchPadding;
- }
- } else {
- mBubbleContainer.getBoundsOnScreen(outRect);
- }
-
- if (mFlyout.getVisibility() == View.VISIBLE) {
- final Rect flyoutBounds = new Rect();
- mFlyout.getBoundsOnScreen(flyoutBounds);
- outRect.union(flyoutBounds);
- }
- }
-
- private int getStatusBarHeight() {
- if (getRootWindowInsets() != null) {
- WindowInsets insets = getRootWindowInsets();
- return Math.max(
- mStatusBarHeight,
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetTop()
- : 0);
- }
-
- return 0;
- }
-
- private void requestUpdate() {
- if (mViewUpdatedRequested || mIsExpansionAnimating) {
- return;
- }
- mViewUpdatedRequested = true;
- getViewTreeObserver().addOnPreDrawListener(mViewUpdater);
- 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.hasBubbleInStackWithKey(mExpandedBubble.getKey())) {
- final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
- mManageSettingsIcon.setImageDrawable(bubble.getAppBadge());
- mManageSettingsText.setText(getResources().getString(
- R.string.bubbles_app_settings, bubble.getAppName()));
- }
-
- mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
-
- final boolean isLtr =
- getResources().getConfiguration().getLayoutDirection() == LAYOUT_DIRECTION_LTR;
-
- // When the menu is open, it should be at these coordinates. The menu pops out to the right
- // in LTR and to the left in RTL.
- final float targetX = isLtr ? mTempRect.left : mTempRect.right - mManageMenu.getWidth();
- final float targetY = mTempRect.bottom - mManageMenu.getHeight();
-
- final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f;
- if (show) {
- mManageMenu.setScaleX(0.5f);
- mManageMenu.setScaleY(0.5f);
- mManageMenu.setTranslationX(targetX - xOffsetForAnimation);
- mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4f);
- 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)
- .withEndActions(() -> {
- View child = mManageMenu.getChildAt(0);
- child.requestAccessibilityFocus();
- // Update the AV's obscured touchable region for the new visibility state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
- })
- .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 - xOffsetForAnimation)
- .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f)
- .withEndActions(() -> {
- mManageMenu.setVisibility(View.INVISIBLE);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- // Update the AV's obscured touchable region for the new state.
- mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
- }
- })
- .start();
- }
- }
-
- private void updateExpandedBubble() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedBubble()");
- }
-
- mExpandedViewContainer.removeAllViews();
- if (mIsExpanded && mExpandedBubble != null
- && mExpandedBubble.getExpandedView() != null) {
- BubbleExpandedView bev = mExpandedBubble.getExpandedView();
- bev.setContentVisibility(false);
- mExpandedViewContainerMatrix.setScaleX(0f);
- mExpandedViewContainerMatrix.setScaleY(0f);
- mExpandedViewContainerMatrix.setTranslate(0f, 0f);
- mExpandedViewContainer.setVisibility(View.INVISIBLE);
- mExpandedViewContainer.setAlpha(0f);
- mExpandedViewContainer.addView(bev);
- bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
-
- if (!mIsExpansionAnimating) {
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
- post(this::animateSwitchBubbles);
- });
- }
- }
- }
-
- /**
- * Requests a snapshot from the currently expanded bubble's ActivityView and displays it in a
- * SurfaceView. This allows us to load a newly expanded bubble's Activity into the ActivityView,
- * while animating the (screenshot of the) previously selected bubble's content away.
- *
- * @param onComplete Callback to run once we're done here - called with 'false' if something
- * went wrong, or 'true' if the SurfaceView is now showing a screenshot of the
- * expanded bubble.
- */
- private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) {
- if (!mIsExpanded || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
- // You can't animate null.
- onComplete.accept(false);
- return;
- }
-
- final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView();
-
- // Release the previous screenshot if it hasn't been released already.
- if (mAnimatingOutBubbleBuffer != null) {
- releaseAnimatingOutBubbleBuffer();
- }
-
- try {
- mAnimatingOutBubbleBuffer = animatingOutExpandedView.snapshotActivitySurface();
- } catch (Exception e) {
- // If we fail for any reason, print the stack trace and then notify the callback of our
- // failure. This is not expected to occur, but it's not worth crashing over.
- Log.wtf(TAG, e);
- onComplete.accept(false);
- }
-
- if (mAnimatingOutBubbleBuffer == null
- || mAnimatingOutBubbleBuffer.getHardwareBuffer() == null) {
- // While no exception was thrown, we were unable to get a snapshot.
- onComplete.accept(false);
- return;
- }
-
- // Make sure the surface container's properties have been reset.
- PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
- mAnimatingOutSurfaceContainer.setScaleX(1f);
- mAnimatingOutSurfaceContainer.setScaleY(1f);
- mAnimatingOutSurfaceContainer.setTranslationX(0);
- mAnimatingOutSurfaceContainer.setTranslationY(0);
-
- final int[] activityViewLocation =
- mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen();
- final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
-
- // Translate the surface to overlap the real ActivityView.
- mAnimatingOutSurfaceContainer.setTranslationY(
- activityViewLocation[1] - surfaceViewLocation[1]);
-
- // Set the width/height of the SurfaceView to match the snapshot.
- mAnimatingOutSurfaceView.getLayoutParams().width =
- mAnimatingOutBubbleBuffer.getHardwareBuffer().getWidth();
- mAnimatingOutSurfaceView.getLayoutParams().height =
- mAnimatingOutBubbleBuffer.getHardwareBuffer().getHeight();
- mAnimatingOutSurfaceView.requestLayout();
-
- // Post to wait for layout.
- post(() -> {
- // The buffer might have been destroyed if the user is mashing on bubbles, that's okay.
- if (mAnimatingOutBubbleBuffer == null
- || mAnimatingOutBubbleBuffer.getHardwareBuffer() == null
- || mAnimatingOutBubbleBuffer.getHardwareBuffer().isClosed()) {
- onComplete.accept(false);
- return;
- }
-
- if (!mIsExpanded) {
- onComplete.accept(false);
- return;
- }
-
- // Attach the buffer! We're now displaying the snapshot.
- mAnimatingOutSurfaceView.getHolder().getSurface().attachAndQueueBufferWithColorSpace(
- mAnimatingOutBubbleBuffer.getHardwareBuffer(),
- mAnimatingOutBubbleBuffer.getColorSpace());
-
- mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true)));
- });
- }
-
- /**
- * Releases the buffer containing the screenshot of the animating-out bubble, if it exists and
- * isn't yet destroyed.
- */
- private void releaseAnimatingOutBubbleBuffer() {
- if (mAnimatingOutBubbleBuffer != null
- && !mAnimatingOutBubbleBuffer.getHardwareBuffer().isClosed()) {
- mAnimatingOutBubbleBuffer.getHardwareBuffer().close();
- }
- }
-
- private void updateExpandedView() {
- if (DEBUG_BUBBLE_STACK_VIEW) {
- Log.d(TAG, "updateExpandedView: mIsExpanded=" + mIsExpanded);
- }
-
- mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
- if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedViewContainer.setTranslationY(getExpandedViewY());
- mExpandedBubble.getExpandedView().updateView(
- mExpandedViewContainer.getLocationOnScreen());
- }
-
- mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
- updateBubbleIcons();
- }
-
- /**
- * Sets the appropriate Z-order, badge, and dot position for each bubble in the stack.
- * Animate dot and badge changes.
- */
- private void updateBubbleIcons() {
- int bubbleCount = getBubbleCount();
- for (int i = 0; i < bubbleCount; i++) {
- BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
- bv.setZ((mMaxBubbles * mBubbleElevation) - i);
-
- if (mIsExpanded) {
- bv.removeDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.BEHIND_STACK);
- bv.animateDotBadgePositions(false /* onLeft */);
- } else if (i == 0) {
- bv.removeDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.BEHIND_STACK);
- bv.animateDotBadgePositions(!mStackOnLeftOrWillBe);
- } else {
- bv.addDotSuppressionFlag(
- BadgedImageView.SuppressionFlag.BEHIND_STACK);
- bv.hideBadge();
- }
- }
- }
-
- private void updatePointerPosition() {
- if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
- return;
- }
- int index = getBubbleIndex(mExpandedBubble);
- if (index == -1) {
- return;
- }
- float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
- float halfBubble = mBubbleSize / 2f;
- float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble;
- // Padding might be adjusted for insets, so get it directly from the view
- bubbleCenter -= mExpandedViewContainer.getPaddingLeft();
- mExpandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
- }
-
- /**
- * @return the number of bubbles in the stack view.
- */
- public int getBubbleCount() {
- // Subtract 1 for the overflow button that is always in the bubble container.
- return mBubbleContainer.getChildCount() - 1;
- }
-
- /**
- * Finds the bubble index within the stack.
- *
- * @param provider the bubble view provider with the bubble to look up.
- * @return the index of the bubble view within the bubble stack. The range of the position
- * is between 0 and the bubble count minus 1.
- */
- int getBubbleIndex(@Nullable BubbleViewProvider provider) {
- if (provider == null) {
- return 0;
- }
- return mBubbleContainer.indexOfChild(provider.getIconView());
- }
-
- /**
- * @return the normalized x-axis position of the bubble stack rounded to 4 decimal places.
- */
- public float getNormalizedXPosition() {
- return new BigDecimal(getStackPosition().x / mDisplaySize.x)
- .setScale(4, RoundingMode.CEILING.HALF_UP)
- .floatValue();
- }
-
- /**
- * @return the normalized y-axis position of the bubble stack rounded to 4 decimal places.
- */
- public float getNormalizedYPosition() {
- return new BigDecimal(getStackPosition().y / mDisplaySize.y)
- .setScale(4, RoundingMode.CEILING.HALF_UP)
- .floatValue();
- }
-
- public void setStackStartPosition(RelativeStackPosition position) {
- mStackAnimationController.setStackStartPosition(position);
- }
-
- public PointF getStackPosition() {
- return mStackAnimationController.getStackPosition();
- }
-
- public RelativeStackPosition getRelativeStackPosition() {
- return mStackAnimationController.getRelativeStackPosition();
- }
-
- /**
- * Logs the bubble UI event.
- *
- * @param provider the bubble view provider that is being interacted on. Null value indicates
- * that the user interaction is not specific to one bubble.
- * @param action the user interaction enum.
- */
- private void logBubbleEvent(@Nullable BubbleViewProvider provider, int action) {
- mBubbleData.logBubbleEvent(provider,
- action,
- mContext.getApplicationInfo().packageName,
- getBubbleCount(),
- getBubbleIndex(provider),
- getNormalizedXPosition(),
- getNormalizedYPosition());
- }
-
- /** For debugging only */
- List<Bubble> getBubblesOnScreen() {
- List<Bubble> bubbles = new ArrayList<>();
- for (int i = 0; i < getBubbleCount(); i++) {
- View child = mBubbleContainer.getChildAt(i);
- if (child instanceof BadgedImageView) {
- String key = ((BadgedImageView) child).getKey();
- Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
- bubbles.add(bubble);
- }
- }
- return bubbles;
- }
-
- /**
- * Representation of stack position that uses relative properties rather than absolute
- * coordinates. This is used to maintain similar stack positions across configuration changes.
- */
- public static class RelativeStackPosition {
- /** Whether to place the stack at the leftmost allowed position. */
- private boolean mOnLeft;
-
- /**
- * How far down the vertically allowed region to place the stack. For example, if the stack
- * allowed region is between y = 100 and y = 1100 and this is 0.2f, we'll place the stack at
- * 100 + (0.2f * 1000) = 300.
- */
- private float mVerticalOffsetPercent;
-
- public RelativeStackPosition(boolean onLeft, float verticalOffsetPercent) {
- mOnLeft = onLeft;
- mVerticalOffsetPercent = clampVerticalOffsetPercent(verticalOffsetPercent);
- }
-
- /** Constructs a relative position given a region and a point in that region. */
- public RelativeStackPosition(PointF position, RectF region) {
- mOnLeft = position.x < region.width() / 2;
- mVerticalOffsetPercent =
- clampVerticalOffsetPercent((position.y - region.top) / region.height());
- }
-
- /** Ensures that the offset percent is between 0f and 1f. */
- private float clampVerticalOffsetPercent(float offsetPercent) {
- return Math.max(0f, Math.min(1f, offsetPercent));
- }
-
- /**
- * Given an allowable stack position region, returns the point within that region
- * represented by this relative position.
- */
- public PointF getAbsolutePositionInRegion(RectF region) {
- return new PointF(
- mOnLeft ? region.left : region.right,
- region.top + mVerticalOffsetPercent * region.height());
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
deleted file mode 100644
index 010a29e3560a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * 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.bubbles;
-
-import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
-import static com.android.systemui.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Matrix;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.os.AsyncTask;
-import android.util.Log;
-import android.util.PathParser;
-import android.view.LayoutInflater;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.graphics.ColorUtils;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.systemui.R;
-
-import java.lang.ref.WeakReference;
-import java.util.Objects;
-
-/**
- * Simple task to inflate views & load necessary info to display a bubble.
- */
-public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
-
-
- /**
- * Callback to find out when the bubble has been inflated & necessary data loaded.
- */
- public interface Callback {
- /**
- * Called when data has been loaded for the bubble.
- */
- void onBubbleViewsReady(Bubble bubble);
- }
-
- private Bubble mBubble;
- private WeakReference<Context> mContext;
- private WeakReference<BubbleStackView> mStackView;
- private BubbleIconFactory mIconFactory;
- private boolean mSkipInflation;
- private Callback mCallback;
-
- /**
- * Creates a task to load information for the provided {@link Bubble}. Once all info
- * is loaded, {@link Callback} is notified.
- */
- BubbleViewInfoTask(Bubble b,
- Context context,
- BubbleStackView stackView,
- BubbleIconFactory factory,
- boolean skipInflation,
- Callback c) {
- mBubble = b;
- mContext = new WeakReference<>(context);
- mStackView = new WeakReference<>(stackView);
- mIconFactory = factory;
- mSkipInflation = skipInflation;
- mCallback = c;
- }
-
- @Override
- protected BubbleViewInfo doInBackground(Void... voids) {
- return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble,
- mSkipInflation);
- }
-
- @Override
- protected void onPostExecute(BubbleViewInfo viewInfo) {
- if (isCancelled() || viewInfo == null) {
- return;
- }
- mBubble.setViewInfo(viewInfo);
- if (mCallback != null) {
- mCallback.onBubbleViewsReady(mBubble);
- }
- }
-
- /**
- * Info necessary to render a bubble.
- */
- static class BubbleViewInfo {
- BadgedImageView imageView;
- BubbleExpandedView expandedView;
- ShortcutInfo shortcutInfo;
- String appName;
- Bitmap bubbleBitmap;
- Drawable badgeDrawable;
- int dotColor;
- Path dotPath;
- Bubble.FlyoutMessage flyoutMessage;
-
- @Nullable
- static BubbleViewInfo populate(Context c, BubbleStackView stackView,
- BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) {
- BubbleViewInfo info = new BubbleViewInfo();
-
- // View inflation: only should do this once per bubble
- if (!skipInflation && !b.isInflated()) {
- LayoutInflater inflater = LayoutInflater.from(c);
- info.imageView = (BadgedImageView) inflater.inflate(
- R.layout.bubble_view, stackView, false /* attachToRoot */);
-
- info.expandedView = (BubbleExpandedView) inflater.inflate(
- R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
- info.expandedView.setStackView(stackView);
- }
-
- if (b.getShortcutInfo() != null) {
- info.shortcutInfo = b.getShortcutInfo();
- }
-
- // App name & app icon
- PackageManager pm = c.getPackageManager();
- ApplicationInfo appInfo;
- Drawable badgedIcon;
- Drawable appIcon;
- try {
- appInfo = pm.getApplicationInfo(
- b.getPackageName(),
- PackageManager.MATCH_UNINSTALLED_PACKAGES
- | PackageManager.MATCH_DISABLED_COMPONENTS
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE);
- if (appInfo != null) {
- info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
- }
- appIcon = pm.getApplicationIcon(b.getPackageName());
- badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
- } catch (PackageManager.NameNotFoundException exception) {
- // If we can't find package... don't think we should show the bubble.
- Log.w(TAG, "Unable to find package: " + b.getPackageName());
- return null;
- }
-
- // Badged bubble image
- Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
- b.getIcon());
- if (bubbleDrawable == null) {
- // Default to app icon
- bubbleDrawable = appIcon;
- }
-
- BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
- b.isImportantConversation());
- info.badgeDrawable = badgedIcon;
- info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
- null /* user */,
- true /* shrinkNonAdaptiveIcons */).icon;
-
- // Dot color & placement
- Path iconPath = PathParser.createPathFromPathData(
- c.getResources().getString(com.android.internal.R.string.config_icon_mask));
- Matrix matrix = new Matrix();
- float scale = iconFactory.getNormalizer().getScale(bubbleDrawable,
- null /* outBounds */, null /* path */, null /* outMaskShape */);
- float radius = DEFAULT_PATH_SIZE / 2f;
- matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
- radius /* pivot y */);
- iconPath.transform(matrix);
- info.dotPath = iconPath;
- info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
- Color.WHITE, WHITE_SCRIM_ALPHA);
-
- // Flyout
- info.flyoutMessage = b.getFlyoutMessage();
- if (info.flyoutMessage != null) {
- info.flyoutMessage.senderAvatar =
- loadSenderAvatar(c, info.flyoutMessage.senderIcon);
- }
- return info;
- }
- }
-
- @Nullable
- static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
- Objects.requireNonNull(context);
- if (icon == null) return null;
- if (icon.getType() == Icon.TYPE_URI || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
- context.grantUriPermission(context.getPackageName(),
- icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- return icon.loadDrawable(context);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
deleted file mode 100644
index 5cc24ce5a775..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ /dev/null
@@ -1,52 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import android.graphics.Bitmap;
-import android.graphics.Path;
-import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-/**
- * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
- */
-interface BubbleViewProvider {
- @Nullable BubbleExpandedView getExpandedView();
-
- void setContentVisibility(boolean visible);
-
- @Nullable View getIconView();
-
- String getKey();
-
- /** Bubble icon bitmap with no badge and no dot. */
- Bitmap getBubbleIcon();
-
- /** App badge drawable to draw above bubble icon. */
- @Nullable Drawable getAppBadge();
-
- /** Path of normalized bubble icon to draw dot on. */
- Path getDotPath();
-
- int getDotColor();
-
- boolean showDot();
-
- int getTaskId();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
deleted file mode 100644
index 39c750de28ac..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java
+++ /dev/null
@@ -1,133 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import android.annotation.NonNull;
-
-import androidx.annotation.MainThread;
-
-import com.android.systemui.statusbar.ScrimView;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.ScrimController;
-
-import java.util.List;
-
-/**
- * Interface to engage bubbles feature.
- */
-public interface Bubbles {
-
- /**
- * @return {@code true} if there is a bubble associated with the provided key and if its
- * notification is hidden from the shade or there is a group summary associated with the
- * provided key that is hidden from the shade because it has been dismissed but still has child
- * bubbles active.
- */
- boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry);
-
- /**
- * @return {@code true} if the current notification entry same as selected bubble
- * notification entry and the stack is currently expanded.
- */
- boolean isBubbleExpanded(NotificationEntry entry);
-
- /** @return {@code true} if stack of bubbles is expanded or not. */
- boolean isStackExpanded();
-
- /**
- * @return the {@link ScrimView} drawn behind the bubble stack. This is managed by
- * {@link ScrimController} since we want the scrim's appearance and behavior to be identical to
- * that of the notification shade scrim.
- */
- ScrimView getScrimForBubble();
-
- /** @return Bubbles for updating overflow. */
- List<Bubble> getOverflowBubbles();
-
- /** Tell the stack of bubbles to collapse. */
- void collapseStack();
-
- /**
- * Request the stack expand if needed, then select the specified Bubble as current.
- * If no bubble exists for this entry, one is created.
- *
- * @param entry the notification for the bubble to be selected
- */
- void expandStackAndSelectBubble(NotificationEntry entry);
-
- /** Promote the provided bubbles when overflow view. */
- void promoteBubbleFromOverflow(Bubble bubble);
-
- /**
- * We intercept notification entries (including group summaries) dismissed by the user when
- * there is an active bubble associated with it. We do this so that developers can still
- * cancel it (and hence the bubbles associated with it). However, these intercepted
- * notifications should then be hidden from the shade since the user has cancelled them, so we
- * {@link Bubble#setSuppressNotification}. For the case of suppressed summaries, we also add
- * {@link BubbleData#addSummaryToSuppress}.
- *
- * @return true if we want to intercept the dismissal of the entry, else false.
- */
- boolean handleDismissalInterception(NotificationEntry entry);
-
- /**
- * Removes the bubble with the given key.
- * <p>
- * Must be called from the main thread.
- */
- @MainThread
- void removeBubble(String key, int reason);
-
-
- /**
- * When a notification is marked Priority, expand the stack if needed,
- * then (maybe create and) select the given bubble.
- *
- * @param entry the notification for the bubble to show
- */
- void onUserChangedImportance(NotificationEntry entry);
-
- /**
- * Called when the status bar has become visible or invisible (either permanently or
- * temporarily).
- */
- void onStatusBarVisibilityChanged(boolean visible);
-
- /**
- * Called when a user has indicated that an active notification should be shown as a bubble.
- * <p>
- * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
- * the notification from appearing in the shade.
- *
- * @param entry the notification to change bubble state for.
- * @param shouldBubble whether the notification should show as a bubble or not.
- */
- void onUserChangedBubble(@NonNull NotificationEntry entry, boolean shouldBubble);
-
-
- /** See {@link BubbleController.NotifCallback}. */
- void addNotifCallback(BubbleController.NotifCallback callback);
-
- /** Set a listener to be notified of bubble expand events. */
- void setExpandListener(BubbleController.BubbleExpandListener listener);
-
- /** Set a listener to be notified of when overflow view update. */
- void setOverflowListener(BubbleData.Listener listener);
-
- /** The task listener for events in bubble tasks. **/
- MultiWindowTaskListener getTaskManager();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
deleted file mode 100644
index b3c552d24dcd..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/DismissView.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.android.systemui.bubbles
-
-import android.content.Context
-import android.graphics.drawable.TransitionDrawable
-import android.view.Gravity
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import androidx.dynamicanimation.animation.DynamicAnimation
-import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
-import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
-import com.android.systemui.R
-import com.android.wm.shell.common.DismissCircleView
-import com.android.wm.shell.animation.PhysicsAnimator
-
-/*
- * View that handles interactions between DismissCircleView and BubbleStackView.
- */
-class DismissView(context: Context) : FrameLayout(context) {
-
- var circle = DismissCircleView(context).apply {
- val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
- val newParams = LayoutParams(targetSize, targetSize)
- newParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
- setLayoutParams(newParams)
- setTranslationY(
- resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height).toFloat())
- }
-
- var isShowing = false
- private val animator = PhysicsAnimator.getInstance(circle)
- private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
- private val DISMISS_SCRIM_FADE_MS = 200
- init {
- setLayoutParams(LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
- Gravity.BOTTOM))
- setPadding(0, 0, 0, resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
- setClipToPadding(false)
- setClipChildren(false)
- setVisibility(View.INVISIBLE)
- setBackgroundResource(
- R.drawable.floating_dismiss_gradient_transition)
- addView(circle)
- }
-
- /**
- * Animates this view in.
- */
- fun show() {
- if (isShowing) return
- isShowing = true
- bringToFront()
- setZ(Short.MAX_VALUE - 1f)
- setVisibility(View.VISIBLE)
- (getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS)
- animator.cancel()
- animator
- .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
- .start()
- }
-
- /**
- * Animates this view out, as well as the circle that encircles the bubbles, if they
- * were dragged into the target and encircled.
- */
- fun hide() {
- if (!isShowing) return
- isShowing = false
- (getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS)
- animator
- .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
- spring)
- .withEndActions({ setVisibility(View.INVISIBLE) })
- .start()
- }
-
- fun updateResources() {
- val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
- circle.layoutParams.width = targetSize
- circle.layoutParams.height = targetSize
- circle.requestLayout()
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
deleted file mode 100644
index 3db07c227d02..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ManageEducationView.kt
+++ /dev/null
@@ -1,146 +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.
- */
-package com.android.systemui.bubbles
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.Rect
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.Button
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.internal.util.ContrastColorUtil
-import com.android.systemui.Interpolators
-import com.android.systemui.R
-
-/**
- * User education view to highlight the manage button that allows a user to configure the settings
- * for the bubble. Shown only the first time a user expands a bubble.
- */
-class ManageEducationView constructor(context: Context) : LinearLayout(context) {
-
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleManageEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
-
- private val manageView by lazy { findViewById<View>(R.id.manage_education_view) }
- private val manageButton by lazy { findViewById<Button>(R.id.manage) }
- private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
- private val titleTextView by lazy { findViewById<TextView>(R.id.user_education_title) }
- private val descTextView by lazy { findViewById<TextView>(R.id.user_education_description) }
-
- private var isHiding = false
-
- init {
- LayoutInflater.from(context).inflate(R.layout.bubbles_manage_button_education, this)
- visibility = View.GONE
- elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
-
- // BubbleStackView forces LTR by default
- // since most of Bubble UI direction depends on positioning by the user.
- // This view actually lays out differently in RTL, so we set layout LOCALE here.
- layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- }
-
- override fun setLayoutDirection(layoutDirection: Int) {
- super.setLayoutDirection(layoutDirection)
- setDrawableDirection()
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- layoutDirection = resources.configuration.layoutDirection
- setTextColor()
- }
-
- private fun setTextColor() {
- val typedArray = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent,
- android.R.attr.textColorPrimaryInverse))
- val bgColor = typedArray.getColor(0 /* index */, Color.BLACK)
- var textColor = typedArray.getColor(1 /* index */, Color.WHITE)
- typedArray.recycle()
- textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true)
- titleTextView.setTextColor(textColor)
- descTextView.setTextColor(textColor)
- }
-
- private fun setDrawableDirection() {
- manageView.setBackgroundResource(
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
- R.drawable.bubble_stack_user_education_bg_rtl
- else R.drawable.bubble_stack_user_education_bg)
- }
-
- /**
- * If necessary, toggles the user education view for the manage button. This is shown when the
- * bubble stack is expanded for the first time.
- *
- * @param show whether the user education view should show or not.
- */
- fun show(expandedView: BubbleExpandedView, rect: Rect) {
- if (visibility == VISIBLE) return
-
- alpha = 0f
- visibility = View.VISIBLE
- post {
- expandedView.getManageButtonBoundsOnScreen(rect)
-
- manageButton
- .setOnClickListener {
- expandedView.findViewById<View>(R.id.settings_button).performClick()
- hide(true /* isStackExpanding */)
- }
- gotItButton.setOnClickListener { hide(true /* isStackExpanding */) }
- setOnClickListener { hide(true /* isStackExpanding */) }
-
- with(manageView) {
- translationX = 0f
- val inset = resources.getDimensionPixelSize(
- R.dimen.bubbles_manage_education_top_inset)
- translationY = (rect.top - manageView.height + inset).toFloat()
- }
- bringToFront()
- animate()
- .setDuration(ANIMATE_DURATION)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .alpha(1f)
- }
- setShouldShow(false)
- }
-
- fun hide(isStackExpanding: Boolean) {
- if (visibility != VISIBLE || isHiding) return
-
- animate()
- .withStartAction { isHiding = true }
- .alpha(0f)
- .setDuration(if (isStackExpanding) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
- .withEndAction {
- isHiding = false
- visibility = GONE
- }
- }
-
- private fun setShouldShow(shouldShow: Boolean) {
- context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit().putBoolean(PREF_MANAGED_EDUCATION, !shouldShow).apply()
- }
-}
-
-const val PREF_MANAGED_EDUCATION: String = "HasSeenBubblesManageOnboarding" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
deleted file mode 100644
index dff8becccb86..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java
+++ /dev/null
@@ -1,177 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.os.Handler;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.SurfaceControl;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-
-/**
- * Manages tasks that are displayed in multi-window (e.g. bubbles). These are displayed in a
- * {@link TaskView}.
- *
- * This class listens on {@link TaskOrganizer} callbacks for events. Once visible, these tasks will
- * intercept back press events.
- *
- * @see android.app.WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW
- * @see TaskView
- */
-// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
-public class MultiWindowTaskListener implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = MultiWindowTaskListener.class.getSimpleName();
-
- private static final boolean DEBUG = false;
-
- //TODO(b/170153209): Have shell listener allow per task registration and remove this.
- public interface Listener {
- void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash);
- void onTaskVanished(RunningTaskInfo taskInfo);
- void onTaskInfoChanged(RunningTaskInfo taskInfo);
- void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo);
- }
-
- private static class TaskData {
- final RunningTaskInfo taskInfo;
- final Listener listener;
-
- TaskData(RunningTaskInfo info, Listener l) {
- taskInfo = info;
- listener = l;
- }
- }
-
- private final Handler mHandler;
- private final ShellTaskOrganizer mTaskOrganizer;
- private final ArrayMap<WindowContainerToken, TaskData> mTasks = new ArrayMap<>();
-
- private MultiWindowTaskListener.Listener mPendingListener;
-
- /**
- * Create a listener for tasks in multi-window mode.
- */
- public MultiWindowTaskListener(Handler handler, ShellTaskOrganizer organizer) {
- mHandler = handler;
- mTaskOrganizer = organizer;
- mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_MULTI_WINDOW);
- }
-
- /**
- * @return the task organizer that is listened to.
- */
- public TaskOrganizer getTaskOrganizer() {
- return mTaskOrganizer;
- }
-
- // TODO(b/129067201): track launches for bubbles
- // Once we have key in ActivityOptions, match listeners via that key
- public void setPendingListener(Listener listener) {
- mPendingListener = listener;
- }
-
- /**
- * Removes a task listener previously registered when starting a new activity.
- */
- public void removeListener(Listener listener) {
- if (DEBUG) {
- Log.d(TAG, "removeListener: listener=" + listener);
- }
- if (mPendingListener == listener) {
- mPendingListener = null;
- }
- for (int i = 0; i < mTasks.size(); i++) {
- if (mTasks.valueAt(i).listener == listener) {
- mTasks.removeAt(i);
- }
- }
- }
-
- @Override
- public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (DEBUG) {
- Log.d(TAG, "onTaskAppeared: taskInfo=" + taskInfo
- + " mPendingListener=" + mPendingListener);
- }
- if (mPendingListener == null) {
- // If there is no pending listener, then we are either receiving this task as a part of
- // registering the task org again (ie. after SysUI dies) or the previously started
- // task is no longer needed (ie. bubble is closed soon after), for now, just finish the
- // associated task
- try {
- ActivityTaskManager.getService().removeTask(taskInfo.taskId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to remove taskId " + taskInfo.taskId);
- }
- return;
- }
-
- mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true);
-
- final TaskData data = new TaskData(taskInfo, mPendingListener);
- mTasks.put(taskInfo.token, data);
- mHandler.post(() -> data.listener.onTaskAppeared(taskInfo, leash));
- mPendingListener = null;
- }
-
- @Override
- public void onTaskVanished(RunningTaskInfo taskInfo) {
- final TaskData data = mTasks.remove(taskInfo.token);
- if (data == null) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "onTaskVanished: taskInfo=" + taskInfo + " listener=" + data.listener);
- }
- mHandler.post(() -> data.listener.onTaskVanished(taskInfo));
- }
-
- @Override
- public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final TaskData data = mTasks.get(taskInfo.token);
- if (data == null) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
- }
- mHandler.post(() -> data.listener.onTaskInfoChanged(taskInfo));
- }
-
- @Override
- public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
- final TaskData data = mTasks.get(taskInfo.token);
- if (data == null) {
- return;
- }
-
- if (DEBUG) {
- Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener);
- }
- mHandler.post(() -> data.listener.onBackPressedOnTaskRoot(taskInfo));
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java b/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
deleted file mode 100644
index f054122eaa47..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/ObjectWrapper.java
+++ /dev/null
@@ -1,46 +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.
- */
-package com.android.systemui.bubbles;
-
-import android.os.Binder;
-import android.os.IBinder;
-
-// Copied from Launcher3
-/**
- * Utility class to pass non-parcealable objects within same process using parcealable payload.
- *
- * It wraps the object in a binder as binders are singleton within a process
- */
-public class ObjectWrapper<T> extends Binder {
-
- private T mObject;
-
- public ObjectWrapper(T object) {
- mObject = object;
- }
-
- public T get() {
- return mObject;
- }
-
- public void clear() {
- mObject = null;
- }
-
- public static IBinder wrap(Object obj) {
- return new ObjectWrapper<>(obj);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
deleted file mode 100644
index b1291a507b57..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/RelativeTouchListener.kt
+++ /dev/null
@@ -1,164 +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.
- */
-
-package com.android.systemui.bubbles
-
-import android.graphics.PointF
-import android.os.Handler
-import android.view.MotionEvent
-import android.view.VelocityTracker
-import android.view.View
-import android.view.ViewConfiguration
-import kotlin.math.hypot
-
-/**
- * Listener which receives [onDown], [onMove], and [onUp] events, with relevant information about
- * the coordinates of the touch and the view relative to the initial ACTION_DOWN event and the
- * view's initial position.
- */
-abstract class RelativeTouchListener : View.OnTouchListener {
-
- /**
- * Called when an ACTION_DOWN event is received for the given view.
- *
- * @return False if the object is not interested in MotionEvents at this time, or true if we
- * should consume this event and subsequent events, and begin calling [onMove].
- */
- abstract fun onDown(v: View, ev: MotionEvent): Boolean
-
- /**
- * Called when an ACTION_MOVE event is received for the given view. This signals that the view
- * is being dragged.
- *
- * @param viewInitialX The view's translationX value when this touch gesture started.
- * @param viewInitialY The view's translationY value when this touch gesture started.
- * @param dx Horizontal distance covered since the initial ACTION_DOWN event, in pixels.
- * @param dy Vertical distance covered since the initial ACTION_DOWN event, in pixels.
- */
- abstract fun onMove(
- v: View,
- ev: MotionEvent,
- viewInitialX: Float,
- viewInitialY: Float,
- dx: Float,
- dy: Float
- )
-
- /**
- * Called when an ACTION_UP event is received for the given view. This signals that a drag or
- * fling gesture has completed.
- *
- * @param viewInitialX The view's translationX value when this touch gesture started.
- * @param viewInitialY The view's translationY value when this touch gesture started.
- * @param dx Horizontal distance covered, in pixels.
- * @param dy Vertical distance covered, in pixels.
- * @param velX The final horizontal velocity of the gesture, in pixels/second.
- * @param velY The final vertical velocity of the gesture, in pixels/second.
- */
- abstract fun onUp(
- v: View,
- ev: MotionEvent,
- viewInitialX: Float,
- viewInitialY: Float,
- dx: Float,
- dy: Float,
- velX: Float,
- velY: Float
- )
-
- /** The raw coordinates of the last ACTION_DOWN event. */
- private val touchDown = PointF()
-
- /** The coordinates of the view, at the time of the last ACTION_DOWN event. */
- private val viewPositionOnTouchDown = PointF()
-
- private val velocityTracker = VelocityTracker.obtain()
-
- private var touchSlop: Int = -1
- private var movedEnough = false
-
- private val handler = Handler()
- private var performedLongClick = false
-
- @Suppress("UNCHECKED_CAST")
- override fun onTouch(v: View, ev: MotionEvent): Boolean {
- addMovement(ev)
-
- val dx = ev.rawX - touchDown.x
- val dy = ev.rawY - touchDown.y
-
- when (ev.action) {
- MotionEvent.ACTION_DOWN -> {
- if (!onDown(v, ev)) {
- return false
- }
-
- // Grab the touch slop, it might have changed if the config changed since the
- // last gesture.
- touchSlop = ViewConfiguration.get(v.context).scaledTouchSlop
-
- touchDown.set(ev.rawX, ev.rawY)
- viewPositionOnTouchDown.set(v.translationX, v.translationY)
-
- performedLongClick = false
- handler.postDelayed({
- if (v.isLongClickable) {
- performedLongClick = v.performLongClick()
- }
- }, ViewConfiguration.getLongPressTimeout().toLong())
- }
-
- MotionEvent.ACTION_MOVE -> {
- if (!movedEnough && hypot(dx, dy) > touchSlop && !performedLongClick) {
- movedEnough = true
- handler.removeCallbacksAndMessages(null)
- }
-
- if (movedEnough) {
- onMove(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy)
- }
- }
-
- MotionEvent.ACTION_UP -> {
- if (movedEnough) {
- velocityTracker.computeCurrentVelocity(1000 /* units */)
- onUp(v, ev, viewPositionOnTouchDown.x, viewPositionOnTouchDown.y, dx, dy,
- velocityTracker.xVelocity, velocityTracker.yVelocity)
- } else if (!performedLongClick) {
- v.performClick()
- } else {
- handler.removeCallbacksAndMessages(null)
- }
-
- velocityTracker.clear()
- movedEnough = false
- }
- }
-
- return true
- }
-
- /**
- * Adds a movement to the velocity tracker using raw screen coordinates.
- */
- private fun addMovement(event: MotionEvent) {
- val deltaX = event.rawX - event.x
- val deltaY = event.rawY - event.y
- event.offsetLocation(deltaX, deltaY)
- velocityTracker.addMovement(event)
- event.offsetLocation(-deltaX, -deltaY)
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt b/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
deleted file mode 100644
index 216df2e1f402..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/StackEducationView.kt
+++ /dev/null
@@ -1,135 +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.
- */
-package com.android.systemui.bubbles
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.PointF
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.LinearLayout
-import android.widget.TextView
-import com.android.internal.util.ContrastColorUtil
-import com.android.systemui.Interpolators
-import com.android.systemui.R
-
-/**
- * User education view to highlight the collapsed stack of bubbles.
- * Shown only the first time a user taps the stack.
- */
-class StackEducationView constructor(context: Context) : LinearLayout(context) {
-
- private val TAG = if (BubbleDebugConfig.TAG_WITH_CLASS_NAME) "BubbleStackEducationView"
- else BubbleDebugConfig.TAG_BUBBLES
-
- private val ANIMATE_DURATION: Long = 200
- private val ANIMATE_DURATION_SHORT: Long = 40
-
- private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
- private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
- private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
-
- private var isHiding = false
-
- init {
- LayoutInflater.from(context).inflate(R.layout.bubble_stack_user_education, this)
-
- visibility = View.GONE
- elevation = resources.getDimensionPixelSize(R.dimen.bubble_elevation).toFloat()
-
- // BubbleStackView forces LTR by default
- // since most of Bubble UI direction depends on positioning by the user.
- // This view actually lays out differently in RTL, so we set layout LOCALE here.
- layoutDirection = View.LAYOUT_DIRECTION_LOCALE
- }
-
- override fun setLayoutDirection(layoutDirection: Int) {
- super.setLayoutDirection(layoutDirection)
- setDrawableDirection()
- }
-
- override fun onFinishInflate() {
- super.onFinishInflate()
- layoutDirection = resources.configuration.layoutDirection
- setTextColor()
- }
-
- private fun setTextColor() {
- val ta = mContext.obtainStyledAttributes(intArrayOf(android.R.attr.colorAccent,
- android.R.attr.textColorPrimaryInverse))
- val bgColor = ta.getColor(0 /* index */, Color.BLACK)
- var textColor = ta.getColor(1 /* index */, Color.WHITE)
- ta.recycle()
- textColor = ContrastColorUtil.ensureTextContrast(textColor, bgColor, true)
- titleTextView.setTextColor(textColor)
- descTextView.setTextColor(textColor)
- }
-
- private fun setDrawableDirection() {
- view.setBackgroundResource(
- if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR)
- R.drawable.bubble_stack_user_education_bg
- else R.drawable.bubble_stack_user_education_bg_rtl)
- }
-
- /**
- * If necessary, shows the user education view for the bubble stack. This appears the first
- * time a user taps on a bubble.
- *
- * @return true if user education was shown, false otherwise.
- */
- fun show(stackPosition: PointF): Boolean {
- if (visibility == VISIBLE) return false
-
- setAlpha(0f)
- setVisibility(View.VISIBLE)
- post {
- with(view) {
- val bubbleSize = context.resources.getDimensionPixelSize(
- R.dimen.individual_bubble_size)
- translationY = stackPosition.y + bubbleSize / 2 - getHeight() / 2
- }
- animate()
- .setDuration(ANIMATE_DURATION)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .alpha(1f)
- }
- setShouldShow(false)
- return true
- }
-
- /**
- * If necessary, hides the stack education view.
- *
- * @param fromExpansion if true this indicates the hide is happening due to the bubble being
- * expanded, false if due to a touch outside of the bubble stack.
- */
- fun hide(fromExpansion: Boolean) {
- if (visibility != VISIBLE || isHiding) return
-
- animate()
- .alpha(0f)
- .setDuration(if (fromExpansion) ANIMATE_DURATION_SHORT else ANIMATE_DURATION)
- .withEndAction { visibility = GONE }
- }
-
- private fun setShouldShow(shouldShow: Boolean) {
- context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
- .edit().putBoolean(PREF_STACK_EDUCATION, !shouldShow).apply()
- }
-}
-
-const val PREF_STACK_EDUCATION: String = "HasSeenBubblesOnboarding" \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
deleted file mode 100644
index 524fa42af7d5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
+++ /dev/null
@@ -1,308 +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.
- */
-
-package com.android.systemui.bubbles;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import dalvik.system.CloseGuard;
-
-/**
- * View that can display a task.
- */
-// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration.
-public class TaskView extends SurfaceView implements SurfaceHolder.Callback,
- MultiWindowTaskListener.Listener {
-
- public interface Listener {
- /** Called when the container is ready for launching activities. */
- default void onInitialized() {}
-
- /** Called when the container can no longer launch activities. */
- default void onReleased() {}
-
- /** Called when a task is created inside the container. */
- default void onTaskCreated(int taskId, ComponentName name) {}
-
- /** Called when a task visibility changes. */
- default void onTaskVisibilityChanged(int taskId, boolean visible) {}
-
- /** Called when a task is about to be removed from the stack inside the container. */
- default void onTaskRemovalStarted(int taskId) {}
-
- /** Called when a task is created inside the container. */
- default void onBackPressedOnTaskRoot(int taskId) {}
- }
-
- private final CloseGuard mGuard = CloseGuard.get();
-
- private final MultiWindowTaskListener mMultiWindowTaskListener;
-
- private ActivityManager.RunningTaskInfo mTaskInfo;
- private WindowContainerToken mTaskToken;
- private SurfaceControl mTaskLeash;
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
- private boolean mSurfaceCreated;
- private boolean mIsInitialized;
- private Listener mListener;
-
- private final Rect mTmpRect = new Rect();
- private final Rect mTmpRootRect = new Rect();
-
- public TaskView(Context context, MultiWindowTaskListener taskListener) {
- super(context, null, 0, 0, true /* disableBackgroundLayer */);
-
- mMultiWindowTaskListener = taskListener;
- setUseAlpha();
- getHolder().addCallback(this);
- mGuard.open("release");
- }
-
- /**
- * Only one listener may be set on the view, throws an exception otherwise.
- */
- public void setListener(Listener listener) {
- if (mListener != null) {
- throw new IllegalStateException(
- "Trying to set a listener when one has already been set");
- }
- mListener = listener;
- }
-
- /**
- * Launch an activity represented by {@link ShortcutInfo}.
- * <p>The owner of this container must be allowed to access the shortcut information,
- * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method.
- *
- * @param shortcut the shortcut used to launch the activity.
- * @param options options for the activity.
- * @param sourceBounds the rect containing the source bounds of the clicked icon to open
- * this shortcut.
- */
- public void startShortcutActivity(@NonNull ShortcutInfo shortcut,
- @NonNull ActivityOptions options, @Nullable Rect sourceBounds) {
- mMultiWindowTaskListener.setPendingListener(this);
- prepareActivityOptions(options);
- LauncherApps service = mContext.getSystemService(LauncherApps.class);
- try {
- service.startShortcut(shortcut, sourceBounds, options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Launch a new activity.
- *
- * @param pendingIntent Intent used to launch an activity.
- * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}
- * @param options options for the activity.
- */
- public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
- @NonNull ActivityOptions options) {
- mMultiWindowTaskListener.setPendingListener(this);
- prepareActivityOptions(options);
- try {
- pendingIntent.send(mContext, 0 /* code */, fillInIntent,
- null /* onFinished */, null /* handler */, null /* requiredPermission */,
- options.toBundle());
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- private void prepareActivityOptions(ActivityOptions options) {
- options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
- options.setTaskAlwaysOnTop(true);
- }
-
- /**
- * Call when view position or size has changed. Do not call when animating.
- */
- public void onLocationChanged() {
- if (mTaskToken == null) {
- return;
- }
- // Update based on the screen bounds
- getBoundsOnScreen(mTmpRect);
- getRootView().getBoundsOnScreen(mTmpRootRect);
- if (!mTmpRootRect.contains(mTmpRect)) {
- mTmpRect.offsetTo(0, 0);
- }
-
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setBounds(mTaskToken, mTmpRect);
- // TODO(b/151449487): Enable synchronization
- mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
- }
-
- /**
- * Release this container if it is initialized.
- */
- public void release() {
- performRelease();
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mGuard != null) {
- mGuard.warnIfOpen();
- performRelease();
- }
- } finally {
- super.finalize();
- }
- }
-
- private void performRelease() {
- getHolder().removeCallback(this);
- mMultiWindowTaskListener.removeListener(this);
- resetTaskInfo();
- mGuard.close();
- if (mListener != null && mIsInitialized) {
- mListener.onReleased();
- mIsInitialized = false;
- }
- }
-
- private void resetTaskInfo() {
- mTaskInfo = null;
- mTaskToken = null;
- mTaskLeash = null;
- }
-
- private void updateTaskVisibility() {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */);
- mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct);
- // TODO(b/151449487): Only call callback once we enable synchronization
- if (mListener != null) {
- mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated);
- }
- }
-
- @Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl leash) {
- mTaskInfo = taskInfo;
- mTaskToken = taskInfo.token;
- mTaskLeash = leash;
-
- if (mSurfaceCreated) {
- // Surface is ready, so just reparent the task to this surface control
- mTransaction.reparent(mTaskLeash, getSurfaceControl())
- .show(mTaskLeash)
- .apply();
- } else {
- // The surface has already been destroyed before the task has appeared, so go ahead and
- // hide the task entirely
- updateTaskVisibility();
- }
-
- // TODO: Synchronize show with the resize
- onLocationChanged();
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
-
- if (mListener != null) {
- mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity);
- }
- }
-
- @Override
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
- if (mListener != null) {
- mListener.onTaskRemovalStarted(taskInfo.taskId);
- }
-
- // Unparent the task when this surface is destroyed
- mTransaction.reparent(mTaskLeash, null).apply();
- resetTaskInfo();
- }
- }
-
- @Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- mTaskInfo.taskDescription = taskInfo.taskDescription;
- setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor());
- }
-
- @Override
- public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
- if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) {
- if (mListener != null) {
- mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
- }
- }
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- mSurfaceCreated = true;
- if (mListener != null && !mIsInitialized) {
- mIsInitialized = true;
- mListener.onInitialized();
- }
- if (mTaskToken == null) {
- // Nothing to update, task is not yet available
- return;
- }
- // Reparent the task when this surface is created
- mTransaction.reparent(mTaskLeash, getSurfaceControl())
- .show(mTaskLeash)
- .apply();
- updateTaskVisibility();
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- if (mTaskToken == null) {
- return;
- }
- onLocationChanged();
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- mSurfaceCreated = false;
- if (mTaskToken == null) {
- // Nothing to update, task is not yet available
- return;
- }
-
- // Unparent the task when this surface is destroyed
- mTransaction.reparent(mTaskLeash, null).apply();
- updateTaskVisibility();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
deleted file mode 100644
index 07acb710c6d7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/AnimatableScaleMatrix.java
+++ /dev/null
@@ -1,144 +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.
- */
-package com.android.systemui.bubbles.animation;
-
-import android.graphics.Matrix;
-
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-
-/**
- * Matrix whose scale properties can be animated using physics animations, via the {@link #SCALE_X}
- * and {@link #SCALE_Y} FloatProperties.
- *
- * This is useful when you need to perform a scale animation with a pivot point, since pivot points
- * are not supported by standard View scale operations but are supported by matrices.
- *
- * NOTE: DynamicAnimation assumes that all custom properties are denominated in pixels, and thus
- * considers 1 to be the smallest user-visible change for custom properties. This means that if you
- * animate {@link #SCALE_X} and {@link #SCALE_Y} to 3f, for example, the animation would have only
- * three frames.
- *
- * To work around this, whenever animating to a desired scale value, animate to the value returned
- * by {@link #getAnimatableValueForScaleFactor} instead. The SCALE_X and SCALE_Y properties will
- * convert that (larger) value into the appropriate scale factor when scaling the matrix.
- */
-public class AnimatableScaleMatrix extends Matrix {
-
- /**
- * The X value of the scale.
- *
- * NOTE: This must be set or animated to the value returned by
- * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
- */
- public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_X =
- new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleX") {
- @Override
- public float getValue(AnimatableScaleMatrix object) {
- return getAnimatableValueForScaleFactor(object.mScaleX);
- }
-
- @Override
- public void setValue(AnimatableScaleMatrix object, float value) {
- object.setScaleX(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
- }
- };
-
- /**
- * The Y value of the scale.
- *
- * NOTE: This must be set or animated to the value returned by
- * {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
- */
- public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_Y =
- new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleY") {
- @Override
- public float getValue(AnimatableScaleMatrix object) {
- return getAnimatableValueForScaleFactor(object.mScaleY);
- }
-
- @Override
- public void setValue(AnimatableScaleMatrix object, float value) {
- object.setScaleY(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
- }
- };
-
- private float mScaleX = 1f;
- private float mScaleY = 1f;
-
- private float mPivotX = 0f;
- private float mPivotY = 0f;
-
- /**
- * Return the value to animate SCALE_X or SCALE_Y to in order to achieve the desired scale
- * factor.
- */
- public static float getAnimatableValueForScaleFactor(float scale) {
- return scale * (1f / DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
- }
-
- @Override
- public void setScale(float sx, float sy, float px, float py) {
- mScaleX = sx;
- mScaleY = sy;
- mPivotX = px;
- mPivotY = py;
- super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
- }
-
- public void setScaleX(float scaleX) {
- mScaleX = scaleX;
- super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
- }
-
- public void setScaleY(float scaleY) {
- mScaleY = scaleY;
- super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
- }
-
- public void setPivotX(float pivotX) {
- mPivotX = pivotX;
- super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
- }
-
- public void setPivotY(float pivotY) {
- mPivotY = pivotY;
- super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
- }
-
- public float getScaleX() {
- return mScaleX;
- }
-
- public float getScaleY() {
- return mScaleY;
- }
-
- public float getPivotX() {
- return mPivotX;
- }
-
- public float getPivotY() {
- return mPivotY;
- }
-
- @Override
- public boolean equals(Object obj) {
- // Use object equality to allow this matrix to be used as a map key (which is required for
- // PhysicsAnimator's animator caching).
- return obj == this;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
deleted file mode 100644
index 7fdc01961aa5..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ /dev/null
@@ -1,668 +0,0 @@
-/*
- * 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.bubbles.animation;
-
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Path;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.view.DisplayCutout;
-import android.view.View;
-import android.view.WindowInsets;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.wm.shell.animation.PhysicsAnimator;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-
-import com.google.android.collect.Sets;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Set;
-
-/**
- * Animation controller for bubbles when they're in their expanded state, or animating to/from the
- * expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be
- * dismissed.
- */
-public class ExpandedAnimationController
- extends PhysicsAnimationLayout.PhysicsAnimationController {
-
- /**
- * How much to translate the bubbles when they're animating in/out. This value is multiplied by
- * the bubble size.
- */
- private static final int ANIMATE_TRANSLATION_FACTOR = 4;
-
- /** Duration of the expand/collapse target path animation. */
- public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
-
- /** Damping ratio for expand/collapse spring. */
- private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
-
- /** Stiffness for the expand/collapse path-following animation. */
- private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
-
- /** What percentage of the screen to use when centering the bubbles in landscape. */
- private static final float CENTER_BUBBLES_LANDSCAPE_PERCENT = 0.66f;
-
- /**
- * Velocity required to dismiss an individual bubble without dragging it into the dismiss
- * target.
- */
- private static final float FLING_TO_DISMISS_MIN_VELOCITY = 6000f;
-
- private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
- new PhysicsAnimator.SpringConfig(
- EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
-
- /** Horizontal offset between bubbles, which we need to know to re-stack them. */
- private float mStackOffsetPx;
- /** Space between status bar and bubbles in the expanded state. */
- private float mBubblePaddingTop;
- /** Size of each bubble. */
- private float mBubbleSizePx;
- /** Space between bubbles in row above expanded view. */
- private float mSpaceBetweenBubbles;
- /** Height of the status bar. */
- private float mStatusBarHeight;
- /** Size of display. */
- private Point mDisplaySize;
- /** Max number of bubbles shown in row above expanded view. */
- private int mBubblesMaxRendered;
- /** What the current screen orientation is. */
- private int mScreenOrientation;
-
- private boolean mAnimatingExpand = false;
-
- /**
- * Whether we are animating other Bubbles UI elements out in preparation for a call to
- * {@link #collapseBackToStack}. If true, we won't animate bubbles in response to adds or
- * reorders.
- */
- private boolean mPreparingToCollapse = false;
-
- private boolean mAnimatingCollapse = false;
- private @Nullable Runnable mAfterExpand;
- private Runnable mAfterCollapse;
- private PointF mCollapsePoint;
-
- /**
- * Whether the dragged out bubble is springing towards the touch point, rather than using the
- * default behavior of moving directly to the touch point.
- *
- * This happens when the user's finger exits the dismiss area while the bubble is magnetized to
- * the center. Since the touch point differs from the bubble location, we need to animate the
- * bubble back to the touch point to avoid a jarring instant location change from the center of
- * the target to the touch point just outside the target bounds.
- */
- private boolean mSpringingBubbleToTouch = false;
-
- /**
- * Whether to spring the bubble to the next touch event coordinates. This is used to animate the
- * bubble out of the magnetic dismiss target to the touch location.
- *
- * Once it 'catches up' and the animation ends, we'll revert to moving it directly.
- */
- private boolean mSpringToTouchOnNextMotionEvent = false;
-
- /** The bubble currently being dragged out of the row (to potentially be dismissed). */
- private MagnetizedObject<View> mMagnetizedBubbleDraggingOut;
-
- private int mExpandedViewPadding;
-
- /**
- * Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
- * end of this animation means we have no bubbles left, and notify the BubbleController.
- */
- private Runnable mOnBubbleAnimatedOutAction;
-
- public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
- int orientation, Runnable onBubbleAnimatedOutAction) {
- updateResources(orientation, displaySize);
- mExpandedViewPadding = expandedViewPadding;
- mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
- }
-
- /**
- * Whether the individual bubble has been dragged out of the row of bubbles far enough to cause
- * the rest of the bubbles to animate to fill the gap.
- */
- private boolean mBubbleDraggedOutEnough = false;
-
- /** End action to run when the lead bubble's expansion animation completes. */
- @Nullable private Runnable mLeadBubbleEndAction;
-
- /**
- * Animates expanding the bubbles into a row along the top of the screen, optionally running an
- * end action when the entire animation completes, and an end action when the lead bubble's
- * animation ends.
- */
- public void expandFromStack(
- @Nullable Runnable after, @Nullable Runnable leadBubbleEndAction) {
- mPreparingToCollapse = false;
- mAnimatingCollapse = false;
- mAnimatingExpand = true;
- mAfterExpand = after;
- mLeadBubbleEndAction = leadBubbleEndAction;
-
- startOrUpdatePathAnimation(true /* expanding */);
- }
-
- /**
- * Animates expanding the bubbles into a row along the top of the screen.
- */
- public void expandFromStack(@Nullable Runnable after) {
- expandFromStack(after, null /* leadBubbleEndAction */);
- }
-
- /**
- * Sets that we're animating the stack collapsed, but haven't yet called
- * {@link #collapseBackToStack}. This will temporarily suspend animations for bubbles that are
- * added or re-ordered, since the upcoming collapse animation will handle positioning those
- * bubbles in the collapsed stack.
- */
- public void notifyPreparingToCollapse() {
- mPreparingToCollapse = true;
- }
-
- /** Animate collapsing the bubbles back to their stacked position. */
- public void collapseBackToStack(PointF collapsePoint, Runnable after) {
- mAnimatingExpand = false;
- mPreparingToCollapse = false;
- mAnimatingCollapse = true;
- mAfterCollapse = after;
- mCollapsePoint = collapsePoint;
-
- startOrUpdatePathAnimation(false /* expanding */);
- }
-
- /**
- * Update effective screen width based on current orientation.
- * @param orientation Landscape or portrait.
- * @param displaySize Updated display size.
- */
- public void updateResources(int orientation, Point displaySize) {
- mScreenOrientation = orientation;
- mDisplaySize = displaySize;
- if (mLayout == null) {
- return;
- }
- Resources res = mLayout.getContext().getResources();
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mStatusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
-
- // Includes overflow button.
- float totalGapWidth = getWidthForDisplayingBubbles() - (mExpandedViewPadding * 2)
- - (mBubblesMaxRendered + 1) * mBubbleSizePx;
- mSpaceBetweenBubbles = totalGapWidth / mBubblesMaxRendered;
- }
-
- /**
- * Animates the bubbles along a curved path, either to expand them along the top or collapse
- * them back into a stack.
- */
- private void startOrUpdatePathAnimation(boolean expanding) {
- Runnable after;
-
- if (expanding) {
- after = () -> {
- mAnimatingExpand = false;
-
- if (mAfterExpand != null) {
- mAfterExpand.run();
- }
-
- mAfterExpand = null;
-
- // Update bubble positions in case any bubbles were added or removed during the
- // expansion animation.
- updateBubblePositions();
- };
- } else {
- after = () -> {
- mAnimatingCollapse = false;
-
- if (mAfterCollapse != null) {
- mAfterCollapse.run();
- }
-
- mAfterCollapse = null;
- };
- }
-
- // Animate each bubble individually, since each path will end in a different spot.
- animationsForChildrenFromIndex(0, (index, animation) -> {
- final View bubble = mLayout.getChildAt(index);
-
- // Start a path at the bubble's current position.
- final Path path = new Path();
- path.moveTo(bubble.getTranslationX(), bubble.getTranslationY());
-
- final float expandedY = getExpandedY();
- if (expanding) {
- // If we're expanding, first draw a line from the bubble's current position to the
- // top of the screen.
- path.lineTo(bubble.getTranslationX(), expandedY);
-
- // Then, draw a line across the screen to the bubble's resting position.
- path.lineTo(getBubbleLeft(index), expandedY);
- } else {
- final float stackedX = mCollapsePoint.x;
-
- // If we're collapsing, draw a line from the bubble's current position to the side
- // of the screen where the bubble will be stacked.
- path.lineTo(stackedX, expandedY);
-
- // Then, draw a line down to the stack position.
- path.lineTo(stackedX, mCollapsePoint.y + index * mStackOffsetPx);
- }
-
- // The lead bubble should be the bubble with the longest distance to travel when we're
- // expanding, and the bubble with the shortest distance to travel when we're collapsing.
- // During expansion from the left side, the last bubble has to travel to the far right
- // side, so we have it lead and 'pull' the rest of the bubbles into place. From the
- // right side, the first bubble is traveling to the top left, so it leads. During
- // collapse to the left, the first bubble has the shortest travel time back to the stack
- // position, so it leads (and vice versa).
- final boolean firstBubbleLeads =
- (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
- || (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
- final int startDelay = firstBubbleLeads
- ? (index * 10)
- : ((mLayout.getChildCount() - index) * 10);
-
- final boolean isLeadBubble =
- (firstBubbleLeads && index == 0)
- || (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
-
- animation
- .followAnimatedTargetAlongPath(
- path,
- EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
- Interpolators.LINEAR /* targetAnimInterpolator */,
- isLeadBubble ? mLeadBubbleEndAction : null /* endAction */,
- () -> mLeadBubbleEndAction = null /* endAction */)
- .withStartDelay(startDelay)
- .withStiffness(EXPAND_COLLAPSE_ANIM_STIFFNESS);
- }).startAll(after);
- }
-
- /** Notifies the controller that the dragged-out bubble was unstuck from the magnetic target. */
- public void onUnstuckFromTarget() {
- mSpringToTouchOnNextMotionEvent = true;
- }
-
- /**
- * Prepares the given bubble view to be dragged out, using the provided magnetic target and
- * listener.
- */
- public void prepareForBubbleDrag(
- View bubble,
- MagnetizedObject.MagneticTarget target,
- MagnetizedObject.MagnetListener listener) {
- mLayout.cancelAnimationsOnView(bubble);
-
- bubble.setTranslationZ(Short.MAX_VALUE);
- mMagnetizedBubbleDraggingOut = new MagnetizedObject<View>(
- mLayout.getContext(), bubble,
- DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y) {
- @Override
- public float getWidth(@NonNull View underlyingObject) {
- return mBubbleSizePx;
- }
-
- @Override
- public float getHeight(@NonNull View underlyingObject) {
- return mBubbleSizePx;
- }
-
- @Override
- public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) {
- loc[0] = (int) bubble.getTranslationX();
- loc[1] = (int) bubble.getTranslationY();
- }
- };
- mMagnetizedBubbleDraggingOut.addTarget(target);
- mMagnetizedBubbleDraggingOut.setMagnetListener(listener);
- mMagnetizedBubbleDraggingOut.setHapticsEnabled(true);
- mMagnetizedBubbleDraggingOut.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
- }
-
- private void springBubbleTo(View bubble, float x, float y) {
- animationForChild(bubble)
- .translationX(x)
- .translationY(y)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .start();
- }
-
- /**
- * Drags an individual bubble to the given coordinates. Bubbles to the right will animate to
- * take its place once it's dragged out of the row of bubbles, and animate out of the way if the
- * bubble is dragged back into the row.
- */
- public void dragBubbleOut(View bubbleView, float x, float y) {
- if (mSpringToTouchOnNextMotionEvent) {
- springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
- mSpringToTouchOnNextMotionEvent = false;
- mSpringingBubbleToTouch = true;
- } else if (mSpringingBubbleToTouch) {
- if (mLayout.arePropertiesAnimatingOnView(
- bubbleView, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)) {
- springBubbleTo(mMagnetizedBubbleDraggingOut.getUnderlyingObject(), x, y);
- } else {
- mSpringingBubbleToTouch = false;
- }
- }
-
- if (!mSpringingBubbleToTouch && !mMagnetizedBubbleDraggingOut.getObjectStuckToTarget()) {
- bubbleView.setTranslationX(x);
- bubbleView.setTranslationY(y);
- }
-
- final boolean draggedOutEnough =
- y > getExpandedY() + mBubbleSizePx || y < getExpandedY() - mBubbleSizePx;
- if (draggedOutEnough != mBubbleDraggedOutEnough) {
- updateBubblePositions();
- mBubbleDraggedOutEnough = draggedOutEnough;
- }
- }
-
- /** Plays a dismiss animation on the dragged out bubble. */
- public void dismissDraggedOutBubble(View bubble, float translationYBy, Runnable after) {
- if (bubble == null) {
- return;
- }
- animationForChild(bubble)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .scaleX(0f)
- .scaleY(0f)
- .translationY(bubble.getTranslationY() + translationYBy)
- .alpha(0f, after)
- .start();
-
- updateBubblePositions();
- }
-
- @Nullable public View getDraggedOutBubble() {
- return mMagnetizedBubbleDraggingOut == null
- ? null
- : mMagnetizedBubbleDraggingOut.getUnderlyingObject();
- }
-
- /** Returns the MagnetizedObject instance for the dragging-out bubble. */
- public MagnetizedObject<View> getMagnetizedBubbleDraggingOut() {
- return mMagnetizedBubbleDraggingOut;
- }
-
- /**
- * Snaps a bubble back to its position within the bubble row, and animates the rest of the
- * bubbles to accommodate it if it was previously dragged out past the threshold.
- */
- public void snapBubbleBack(View bubbleView, float velX, float velY) {
- final int index = mLayout.indexOfChild(bubbleView);
-
- animationForChildAtIndex(index)
- .position(getBubbleLeft(index), getExpandedY())
- .withPositionStartVelocities(velX, velY)
- .start(() -> bubbleView.setTranslationZ(0f) /* after */);
-
- mMagnetizedBubbleDraggingOut = null;
-
- updateBubblePositions();
- }
-
- /** Resets bubble drag out gesture flags. */
- public void onGestureFinished() {
- mBubbleDraggedOutEnough = false;
- mMagnetizedBubbleDraggingOut = null;
- updateBubblePositions();
- }
-
- /**
- * Animates the bubbles to {@link #getExpandedY()} position. Used in response to IME showing.
- */
- public void updateYPosition(Runnable after) {
- if (mLayout == null) return;
- animationsForChildrenFromIndex(
- 0, (i, anim) -> anim.translationY(getExpandedY())).startAll(after);
- }
-
- /** The Y value of the row of expanded bubbles. */
- public float getExpandedY() {
- if (mLayout == null || mLayout.getRootWindowInsets() == null) {
- return 0;
- }
- final WindowInsets insets = mLayout.getRootWindowInsets();
- return mBubblePaddingTop + Math.max(
- mStatusBarHeight,
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetTop()
- : 0);
- }
-
- /** Description of current animation controller state. */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("ExpandedAnimationController state:");
- pw.print(" isActive: "); pw.println(isActiveController());
- pw.print(" animatingExpand: "); pw.println(mAnimatingExpand);
- pw.print(" animatingCollapse: "); pw.println(mAnimatingCollapse);
- pw.print(" springingBubble: "); pw.println(mSpringingBubbleToTouch);
- }
-
- @Override
- void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
- updateResources(mScreenOrientation, mDisplaySize);
-
- // Ensure that all child views are at 1x scale, and visible, in case they were animating
- // in.
- mLayout.setVisibility(View.VISIBLE);
- animationsForChildrenFromIndex(0 /* startIndex */, (index, animation) ->
- animation.scaleX(1f).scaleY(1f).alpha(1f)).startAll();
- }
-
- @Override
- Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
- return Sets.newHashSet(
- DynamicAnimation.TRANSLATION_X,
- DynamicAnimation.TRANSLATION_Y,
- DynamicAnimation.SCALE_X,
- DynamicAnimation.SCALE_Y,
- DynamicAnimation.ALPHA);
- }
-
- @Override
- int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
- return NONE;
- }
-
- @Override
- float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
- return 0;
- }
-
- @Override
- SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
- return new SpringForce()
- .setDampingRatio(DAMPING_RATIO_MEDIUM_LOW_BOUNCY)
- .setStiffness(SpringForce.STIFFNESS_LOW);
- }
-
- @Override
- void onChildAdded(View child, int index) {
- // If a bubble is added while the expand/collapse animations are playing, update the
- // animation to include the new bubble.
- if (mAnimatingExpand) {
- startOrUpdatePathAnimation(true /* expanding */);
- } else if (mAnimatingCollapse) {
- startOrUpdatePathAnimation(false /* expanding */);
- } else {
- child.setTranslationX(getBubbleLeft(index));
-
- // If we're preparing to collapse, don't start animations since the collapse animation
- // will take over and animate the new bubble into the correct (stacked) position.
- if (!mPreparingToCollapse) {
- animationForChild(child)
- .translationY(
- getExpandedY()
- - mBubbleSizePx * ANIMATE_TRANSLATION_FACTOR, /* from */
- getExpandedY() /* to */)
- .start();
- updateBubblePositions();
- }
- }
- }
-
- @Override
- void onChildRemoved(View child, int index, Runnable finishRemoval) {
- // If we're removing the dragged-out bubble, that means it got dismissed.
- if (child.equals(getDraggedOutBubble())) {
- mMagnetizedBubbleDraggingOut = null;
- finishRemoval.run();
- mOnBubbleAnimatedOutAction.run();
- } else {
- PhysicsAnimator.getInstance(child)
- .spring(DynamicAnimation.ALPHA, 0f)
- .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
- .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
- .start();
- }
-
- // Animate all the other bubbles to their new positions sans this bubble.
- updateBubblePositions();
- }
-
- @Override
- void onChildReordered(View child, int oldIndex, int newIndex) {
- if (mPreparingToCollapse) {
- // If a re-order is received while we're preparing to collapse, ignore it. Once started,
- // the collapse animation will animate all of the bubbles to their correct (stacked)
- // position.
- return;
- }
-
- if (mAnimatingCollapse) {
- // If a re-order is received during collapse, update the animation so that the bubbles
- // end up in the correct (stacked) position.
- startOrUpdatePathAnimation(false /* expanding */);
- } else {
- // Otherwise, animate the bubbles around to reflect their new order.
- updateBubblePositions();
- }
- }
-
- private void updateBubblePositions() {
- if (mAnimatingExpand || mAnimatingCollapse) {
- return;
- }
-
- for (int i = 0; i < mLayout.getChildCount(); i++) {
- final View bubble = mLayout.getChildAt(i);
-
- // Don't animate the dragging out bubble, or it'll jump around while being dragged. It
- // will be snapped to the correct X value after the drag (if it's not dismissed).
- if (bubble.equals(getDraggedOutBubble())) {
- return;
- }
-
- animationForChild(bubble)
- .translationX(getBubbleLeft(i))
- .start();
- }
- }
-
- /**
- * @param index Bubble index in row.
- * @return Bubble left x from left edge of screen.
- */
- public float getBubbleLeft(int index) {
- final float bubbleFromRowLeft = index * (mBubbleSizePx + mSpaceBetweenBubbles);
- return getRowLeft() + bubbleFromRowLeft;
- }
-
- /**
- * When expanded, the bubbles are centered in the screen. In portrait, all available space is
- * used. In landscape we have too much space so the value is restricted. This method accounts
- * for window decorations (nav bar, cutouts).
- *
- * @return the desired width to display the expanded bubbles in.
- */
- public float getWidthForDisplayingBubbles() {
- final float availableWidth = getAvailableScreenWidth(true /* includeStableInsets */);
- if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
- // display size y in landscape will be the smaller dimension of the screen
- return Math.max(mDisplaySize.y, availableWidth * CENTER_BUBBLES_LANDSCAPE_PERCENT);
- } else {
- return availableWidth;
- }
- }
-
- /**
- * Determines the available screen width without the cutout.
- *
- * @param subtractStableInsets Whether or not stable insets should also be removed from the
- * returned width.
- * @return the total screen width available accounting for cutouts and insets,
- * iff {@param includeStableInsets} is true.
- */
- private float getAvailableScreenWidth(boolean subtractStableInsets) {
- float availableSize = mDisplaySize.x;
- WindowInsets insets = mLayout != null ? mLayout.getRootWindowInsets() : null;
- if (insets != null) {
- int cutoutLeft = 0;
- int cutoutRight = 0;
- DisplayCutout cutout = insets.getDisplayCutout();
- if (cutout != null) {
- cutoutLeft = cutout.getSafeInsetLeft();
- cutoutRight = cutout.getSafeInsetRight();
- }
- final int stableLeft = subtractStableInsets ? insets.getStableInsetLeft() : 0;
- final int stableRight = subtractStableInsets ? insets.getStableInsetRight() : 0;
- availableSize -= Math.max(stableLeft, cutoutLeft);
- availableSize -= Math.max(stableRight, cutoutRight);
- }
- return availableSize;
- }
-
- private float getRowLeft() {
- if (mLayout == null) {
- return 0;
- }
- float rowWidth = (mLayout.getChildCount() * mBubbleSizePx)
- + ((mLayout.getChildCount() - 1) * mSpaceBetweenBubbles);
-
- // This display size we're using includes the size of the insets, we want the true
- // center of the display minus the notch here, which means we should include the
- // stable insets (e.g. status bar, nav bar) in this calculation.
- final float trueCenter = getAvailableScreenWidth(false /* subtractStableInsets */) / 2f;
- return trueCenter - (rowWidth / 2f);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
deleted file mode 100644
index 4e0abc8009b4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/OneTimeEndListener.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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.bubbles.animation;
-
-import androidx.dynamicanimation.animation.DynamicAnimation;
-
-/**
- * End listener that removes itself from its animation when called for the first time. Useful since
- * anonymous OnAnimationEndListener instances can't pass themselves to
- * {@link DynamicAnimation#removeEndListener}, but can call through to this superclass
- * implementation.
- */
-public class OneTimeEndListener implements DynamicAnimation.OnAnimationEndListener {
-
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
- float velocity) {
- animation.removeEndListener(this);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
deleted file mode 100644
index 6e6f82b714ff..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ /dev/null
@@ -1,1147 +0,0 @@
-/*
- * 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.bubbles.animation;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.content.Context;
-import android.graphics.Path;
-import android.graphics.PointF;
-import android.util.FloatProperty;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-
-import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.systemui.R;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Layout that constructs physics-based animations for each of its children, which behave according
- * to settings provided by a {@link PhysicsAnimationController} instance.
- *
- * See physics-animation-layout.md.
- */
-public class PhysicsAnimationLayout extends FrameLayout {
- private static final String TAG = "Bubbs.PAL";
-
- /**
- * Controls the construction, configuration, and use of the physics animations supplied by this
- * layout.
- */
- abstract static class PhysicsAnimationController {
-
- /** Configures a given {@link PhysicsPropertyAnimator} for a view at the given index. */
- interface ChildAnimationConfigurator {
-
- /**
- * Called to configure the animator for the view at the given index.
- *
- * This method should make use of methods such as
- * {@link PhysicsPropertyAnimator#translationX} and
- * {@link PhysicsPropertyAnimator#withStartDelay} to configure the animation.
- *
- * Implementations should not call {@link PhysicsPropertyAnimator#start}, this will
- * happen elsewhere after configuration is complete.
- */
- void configureAnimationForChildAtIndex(int index, PhysicsPropertyAnimator animation);
- }
-
- /**
- * Returned by {@link #animationsForChildrenFromIndex} to allow starting multiple animations
- * on multiple child views at the same time.
- */
- interface MultiAnimationStarter {
-
- /**
- * Start all animations and call the given end actions once all animations have
- * completed.
- */
- void startAll(Runnable... endActions);
- }
-
- /**
- * Constant to return from {@link #getNextAnimationInChain} if the animation should not be
- * chained at all.
- */
- protected static final int NONE = -1;
-
- /** Set of properties for which the layout should construct physics animations. */
- abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties();
-
- /**
- * Returns the index of the next animation after the given index in the animation chain, or
- * {@link #NONE} if it should not be chained, or if the chain should end at the given index.
- *
- * If a next index is returned, an update listener will be added to the animation at the
- * given index that dispatches value updates to the animation at the next index. This
- * creates a 'following' effect.
- *
- * Typical implementations of this method will return either index + 1, or index - 1, to
- * create forward or backward chains between adjacent child views, but this is not required.
- */
- abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index);
-
- /**
- * Offsets to be added to the value that chained animations of the given property dispatch
- * to subsequent child animations.
- *
- * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles
- * stack off to the left or right side slightly.
- */
- abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property);
-
- /**
- * Returns the SpringForce to be used for the given child view's property animation. Despite
- * these usually being similar or identical across properties and views, {@link SpringForce}
- * also contains the SpringAnimation's final position, so we have to construct a new one for
- * each animation rather than using a constant.
- */
- abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view);
-
- /**
- * Called when a new child is added at the specified index. Controllers can use this
- * opportunity to animate in the new view.
- */
- abstract void onChildAdded(View child, int index);
-
- /**
- * Called with a child view that has been removed from the layout, from the given index. The
- * passed view has been removed from the layout and added back as a transient view, which
- * renders normally, but is not part of the normal view hierarchy and will not be considered
- * by getChildAt() and getChildCount().
- *
- * The controller can perform animations on the child (either manually, or by using
- * {@link #animationForChild(View)}), and then call finishRemoval when complete.
- *
- * finishRemoval must be called by implementations of this method, or transient views will
- * never be removed.
- */
- abstract void onChildRemoved(View child, int index, Runnable finishRemoval);
-
- /** Called when a child view has been reordered in the view hierachy. */
- abstract void onChildReordered(View child, int oldIndex, int newIndex);
-
- /**
- * Called when the controller is set as the active animation controller for the given
- * layout. Once active, the controller can start animations using the animator instances
- * returned by {@link #animationForChild}.
- *
- * While all animations started by the previous controller will be cancelled, the new
- * controller should not make any assumptions about the state of the layout or its children.
- * Their translation, alpha, scale, etc. values may have been changed by the previous
- * controller and should be reset here if relevant.
- */
- abstract void onActiveControllerForLayout(PhysicsAnimationLayout layout);
-
- protected PhysicsAnimationLayout mLayout;
-
- PhysicsAnimationController() { }
-
- /** Whether this controller is the currently active controller for its associated layout. */
- protected boolean isActiveController() {
- return mLayout != null && this == mLayout.mController;
- }
-
- protected void setLayout(PhysicsAnimationLayout layout) {
- this.mLayout = layout;
- onActiveControllerForLayout(layout);
- }
-
- protected PhysicsAnimationLayout getLayout() {
- return mLayout;
- }
-
- /**
- * Returns a {@link PhysicsPropertyAnimator} instance for the given child view.
- */
- protected PhysicsPropertyAnimator animationForChild(View child) {
- PhysicsPropertyAnimator animator =
- (PhysicsPropertyAnimator) child.getTag(R.id.physics_animator_tag);
-
- if (animator == null) {
- animator = mLayout.new PhysicsPropertyAnimator(child);
- child.setTag(R.id.physics_animator_tag, animator);
- }
-
- animator.clearAnimator();
- animator.setAssociatedController(this);
-
- return animator;
- }
-
- /** Returns a {@link PhysicsPropertyAnimator} instance for child at the given index. */
- protected PhysicsPropertyAnimator animationForChildAtIndex(int index) {
- return animationForChild(mLayout.getChildAt(index));
- }
-
- /**
- * Returns a {@link MultiAnimationStarter} whose startAll method will start the physics
- * animations for all children from startIndex onward. The provided configurator will be
- * called with each child's {@link PhysicsPropertyAnimator}, where it can set up each
- * animation appropriately.
- */
- protected MultiAnimationStarter animationsForChildrenFromIndex(
- int startIndex, ChildAnimationConfigurator configurator) {
- final Set<DynamicAnimation.ViewProperty> allAnimatedProperties = new HashSet<>();
- final List<PhysicsPropertyAnimator> allChildAnims = new ArrayList<>();
-
- // Retrieve the animator for each child, ask the configurator to configure it, then save
- // it and the properties it chose to animate.
- for (int i = startIndex; i < mLayout.getChildCount(); i++) {
- final PhysicsPropertyAnimator anim = animationForChildAtIndex(i);
- configurator.configureAnimationForChildAtIndex(i, anim);
- allAnimatedProperties.addAll(anim.getAnimatedProperties());
- allChildAnims.add(anim);
- }
-
- // Return a MultiAnimationStarter that will start all of the child animations, and also
- // add a multiple property end listener to the layout that will call the end action
- // provided to startAll() once all animations on the animated properties complete.
- return (endActions) -> {
- final Runnable runAllEndActions = () -> {
- for (Runnable action : endActions) {
- action.run();
- }
- };
-
- // If there aren't any children to animate, just run the end actions.
- if (mLayout.getChildCount() == 0) {
- runAllEndActions.run();
- return;
- }
-
- if (endActions != null) {
- setEndActionForMultipleProperties(
- runAllEndActions,
- allAnimatedProperties.toArray(
- new DynamicAnimation.ViewProperty[0]));
- }
-
- for (PhysicsPropertyAnimator childAnim : allChildAnims) {
- childAnim.start();
- }
- };
- }
-
- /**
- * Sets an end action that will be run when all child animations for a given property have
- * stopped running.
- */
- protected void setEndActionForProperty(
- Runnable action, DynamicAnimation.ViewProperty property) {
- mLayout.mEndActionForProperty.put(property, action);
- }
-
- /**
- * Sets an end action that will be run when all child animations for all of the given
- * properties have stopped running.
- */
- protected void setEndActionForMultipleProperties(
- Runnable action, DynamicAnimation.ViewProperty... properties) {
- final Runnable checkIfAllFinished = () -> {
- if (!mLayout.arePropertiesAnimating(properties)) {
- action.run();
-
- for (DynamicAnimation.ViewProperty property : properties) {
- removeEndActionForProperty(property);
- }
- }
- };
-
- for (DynamicAnimation.ViewProperty property : properties) {
- setEndActionForProperty(checkIfAllFinished, property);
- }
- }
-
- /**
- * Removes the end listener that would have been called when all child animations for a
- * given property stopped running.
- */
- protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
- mLayout.mEndActionForProperty.remove(property);
- }
- }
-
- /**
- * End actions that are called when every child's animation of the given property has finished.
- */
- protected final HashMap<DynamicAnimation.ViewProperty, Runnable> mEndActionForProperty =
- new HashMap<>();
-
- /** The currently active animation controller. */
- @Nullable protected PhysicsAnimationController mController;
-
- public PhysicsAnimationLayout(Context context) {
- super(context);
- }
-
- /**
- * Sets the animation controller and constructs or reconfigures the layout's physics animations
- * to meet the controller's specifications.
- */
- public void setActiveController(PhysicsAnimationController controller) {
- cancelAllAnimations();
- mEndActionForProperty.clear();
-
- this.mController = controller;
- mController.setLayout(this);
-
- // Set up animations for this controller's animated properties.
- for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
- setUpAnimationsForProperty(property);
- }
- }
-
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- addViewInternal(child, index, params, false /* isReorder */);
- }
-
- @Override
- public void removeView(View view) {
- if (mController != null) {
- final int index = indexOfChild(view);
-
- // Remove the view and add it back as a transient view so we can animate it out.
- super.removeView(view);
- addTransientView(view, index);
-
- // Tell the controller to animate this view out, and call the callback when it's
- // finished.
- mController.onChildRemoved(view, index, () -> {
- // The controller says it's done with the transient view, cancel animations in case
- // any are still running and then remove it.
- cancelAnimationsOnView(view);
- removeTransientView(view);
- });
- } else {
- // Without a controller, nobody will animate this view out, so it gets an unceremonious
- // departure.
- super.removeView(view);
- }
- }
-
- @Override
- public void removeViewAt(int index) {
- removeView(getChildAt(index));
- }
-
- /** Immediately re-orders the view to the given index. */
- public void reorderView(View view, int index) {
- if (view == null) {
- return;
- }
- final int oldIndex = indexOfChild(view);
-
- super.removeView(view);
- addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */);
-
- if (mController != null) {
- mController.onChildReordered(view, oldIndex, index);
- }
- }
-
- /** Checks whether any animations of the given properties are still running. */
- public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) {
- for (int i = 0; i < getChildCount(); i++) {
- if (arePropertiesAnimatingOnView(getChildAt(i), properties)) {
- return true;
- }
- }
-
- return false;
- }
-
- /** Checks whether any animations of the given properties are running on the given view. */
- public boolean arePropertiesAnimatingOnView(
- View view, DynamicAnimation.ViewProperty... properties) {
- final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
- for (DynamicAnimation.ViewProperty property : properties) {
- final SpringAnimation animation = getAnimationFromView(property, view);
- if (animation != null && animation.isRunning()) {
- return true;
- }
-
- // If the target animator is running, its update listener will trigger the translation
- // physics animations at some point. We should consider the translation properties to be
- // be animating in this case, even if the physics animations haven't been started yet.
- final boolean isTranslation =
- property.equals(DynamicAnimation.TRANSLATION_X)
- || property.equals(DynamicAnimation.TRANSLATION_Y);
- if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) {
- return true;
- }
- }
-
- return false;
- }
-
- /** Cancels all animations that are running on all child views, for all properties. */
- public void cancelAllAnimations() {
- if (mController == null) {
- return;
- }
-
- cancelAllAnimationsOfProperties(
- mController.getAnimatedProperties().toArray(new DynamicAnimation.ViewProperty[]{}));
- }
-
- /** Cancels all animations that are running on all child views, for the given properties. */
- public void cancelAllAnimationsOfProperties(DynamicAnimation.ViewProperty... properties) {
- if (mController == null) {
- return;
- }
-
- for (int i = 0; i < getChildCount(); i++) {
- for (DynamicAnimation.ViewProperty property : properties) {
- final DynamicAnimation anim = getAnimationAtIndex(property, i);
- if (anim != null) {
- anim.cancel();
- }
- }
- }
- }
-
- /** Cancels all of the physics animations running on the given view. */
- public void cancelAnimationsOnView(View view) {
- // If present, cancel the target animator so it doesn't restart the translation physics
- // animations.
- final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
- if (targetAnimator != null) {
- targetAnimator.cancel();
- }
-
- // Cancel physics animations on the view.
- for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
- final DynamicAnimation animationFromView = getAnimationFromView(property, view);
- if (animationFromView != null) {
- animationFromView.cancel();
- }
- }
- }
-
- protected boolean isActiveController(PhysicsAnimationController controller) {
- return mController == controller;
- }
-
- /** Whether the first child would be left of center if translated to the given x value. */
- protected boolean isFirstChildXLeftOfCenter(float x) {
- if (getChildCount() > 0) {
- return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2;
- } else {
- return false; // If there's no first child, really anything is correct, right?
- }
- }
-
- /** ViewProperty's toString is useless, this returns a readable name for debug logging. */
- protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) {
- if (property.equals(DynamicAnimation.TRANSLATION_X)) {
- return "TRANSLATION_X";
- } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
- return "TRANSLATION_Y";
- } else if (property.equals(DynamicAnimation.SCALE_X)) {
- return "SCALE_X";
- } else if (property.equals(DynamicAnimation.SCALE_Y)) {
- return "SCALE_Y";
- } else if (property.equals(DynamicAnimation.ALPHA)) {
- return "ALPHA";
- } else {
- return "Unknown animation property.";
- }
- }
-
- /**
- * Adds a view to the layout. If this addition is not the result of a call to
- * {@link #reorderView}, this will also notify the controller via
- * {@link PhysicsAnimationController#onChildAdded} and set up animations for the view.
- */
- private void addViewInternal(
- View child, int index, ViewGroup.LayoutParams params, boolean isReorder) {
- super.addView(child, index, params);
-
- // Set up animations for the new view, if the controller is set. If it isn't set, we'll be
- // setting up animations for all children when setActiveController is called.
- if (mController != null && !isReorder) {
- for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
- setUpAnimationForChild(property, child, index);
- }
-
- mController.onChildAdded(child, index);
- }
- }
-
- /**
- * Retrieves the animation of the given property from the view at the given index via the view
- * tag system.
- */
- @Nullable private SpringAnimation getAnimationAtIndex(
- DynamicAnimation.ViewProperty property, int index) {
- return getAnimationFromView(property, getChildAt(index));
- }
-
- /** Retrieves the animation of the given property from the view via the view tag system. */
- @Nullable private SpringAnimation getAnimationFromView(
- DynamicAnimation.ViewProperty property, View view) {
- return (SpringAnimation) view.getTag(getTagIdForProperty(property));
- }
-
- /** Retrieves the target animator from the view via the view tag system. */
- @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) {
- return (ObjectAnimator) view.getTag(R.id.target_animator_tag);
- }
-
- /** Sets up SpringAnimations of the given property for each child view in the layout. */
- private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
- for (int i = 0; i < getChildCount(); i++) {
- setUpAnimationForChild(property, getChildAt(i), i);
- }
- }
-
- /** Constructs a SpringAnimation of the given property for a child view. */
- private void setUpAnimationForChild(
- DynamicAnimation.ViewProperty property, View child, int index) {
- SpringAnimation newAnim = new SpringAnimation(child, property);
- newAnim.addUpdateListener((animation, value, velocity) -> {
- final int indexOfChild = indexOfChild(child);
- final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild);
-
- if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) {
- return;
- }
-
- final float offset = mController.getOffsetForChainedPropertyAnimation(property);
- if (nextAnimInChain < getChildCount()) {
- final SpringAnimation nextAnim = getAnimationAtIndex(property, nextAnimInChain);
- if (nextAnim != null) {
- nextAnim.animateToFinalPosition(value + offset);
- }
- }
- });
-
- newAnim.setSpring(mController.getSpringForce(property, child));
- newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property));
- child.setTag(getTagIdForProperty(property), newAnim);
- }
-
- /** Return a stable ID to use as a tag key for the given property's animations. */
- private int getTagIdForProperty(DynamicAnimation.ViewProperty property) {
- if (property.equals(DynamicAnimation.TRANSLATION_X)) {
- return R.id.translation_x_dynamicanimation_tag;
- } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
- return R.id.translation_y_dynamicanimation_tag;
- } else if (property.equals(DynamicAnimation.SCALE_X)) {
- return R.id.scale_x_dynamicanimation_tag;
- } else if (property.equals(DynamicAnimation.SCALE_Y)) {
- return R.id.scale_y_dynamicanimation_tag;
- } else if (property.equals(DynamicAnimation.ALPHA)) {
- return R.id.alpha_dynamicanimation_tag;
- }
-
- return -1;
- }
-
- /**
- * End listener that is added to each individual DynamicAnimation, which dispatches to a single
- * listener when every other animation of the given property is no longer running.
- *
- * This is required since chained DynamicAnimations can stop and start again due to changes in
- * upstream animations. This means that adding an end listener to just the last animation is not
- * sufficient. By firing only when every other animation on the property has stopped running, we
- * ensure that no animation will be restarted after the single end listener is called.
- */
- protected class AllAnimationsForPropertyFinishedEndListener
- implements DynamicAnimation.OnAnimationEndListener {
- private DynamicAnimation.ViewProperty mProperty;
-
- AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) {
- this.mProperty = property;
- }
-
- @Override
- public void onAnimationEnd(
- DynamicAnimation anim, boolean canceled, float value, float velocity) {
- if (!arePropertiesAnimating(mProperty)) {
- if (mEndActionForProperty.containsKey(mProperty)) {
- final Runnable callback = mEndActionForProperty.get(mProperty);
-
- if (callback != null) {
- callback.run();
- }
- }
- }
- }
- }
-
- /**
- * Animator class returned by {@link PhysicsAnimationController#animationForChild}, to allow
- * controllers to animate child views using physics animations.
- *
- * See docs/physics-animation-layout.md for documentation and examples.
- */
- protected class PhysicsPropertyAnimator {
- /** The view whose properties this animator animates. */
- private View mView;
-
- /** Start velocity to use for all property animations. */
- private float mDefaultStartVelocity = -Float.MAX_VALUE;
-
- /** Start delay to use when start is called. */
- private long mStartDelay = 0;
-
- /** Damping ratio to use for the animations. */
- private float mDampingRatio = -1;
-
- /** Stiffness to use for the animations. */
- private float mStiffness = -1;
-
- /** End actions to call when animations for the given property complete. */
- private Map<DynamicAnimation.ViewProperty, Runnable[]> mEndActionsForProperty =
- new HashMap<>();
-
- /**
- * Start velocities to use for TRANSLATION_X and TRANSLATION_Y, since these are often
- * provided by VelocityTrackers and differ from each other.
- */
- private Map<DynamicAnimation.ViewProperty, Float> mPositionStartVelocities =
- new HashMap<>();
-
- /**
- * End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed,
- * if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously.
- */
- @Nullable private Runnable[] mPositionEndActions;
-
- /**
- * All of the properties that have been set and will animate when {@link #start} is called.
- */
- private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>();
-
- /**
- * All of the initial property values that have been set. These values will be instantly set
- * when {@link #start} is called, just before the animation begins.
- */
- private Map<DynamicAnimation.ViewProperty, Float> mInitialPropertyValues = new HashMap<>();
-
- /** The animation controller that last retrieved this animator instance. */
- private PhysicsAnimationController mAssociatedController;
-
- /**
- * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As
- * the path is traversed, the view's translation spring animation final positions are
- * updated such that the view 'follows' the current position on the path.
- */
- @Nullable private ObjectAnimator mPathAnimator;
-
- /** Current position on the path. This is animated by {@link #mPathAnimator}. */
- private PointF mCurrentPointOnPath = new PointF();
-
- /**
- * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value
- * of {@link #mCurrentPointOnPath}.
- */
- private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty =
- new FloatProperty<PhysicsPropertyAnimator>("PathX") {
- @Override
- public void setValue(PhysicsPropertyAnimator object, float value) {
- mCurrentPointOnPath.x = value;
- }
-
- @Override
- public Float get(PhysicsPropertyAnimator object) {
- return mCurrentPointOnPath.x;
- }
- };
-
- private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty =
- new FloatProperty<PhysicsPropertyAnimator>("PathY") {
- @Override
- public void setValue(PhysicsPropertyAnimator object, float value) {
- mCurrentPointOnPath.y = value;
- }
-
- @Override
- public Float get(PhysicsPropertyAnimator object) {
- return mCurrentPointOnPath.y;
- }
- };
-
- protected PhysicsPropertyAnimator(View view) {
- this.mView = view;
- }
-
- /** Animate a property to the given value, then call the optional end actions. */
- public PhysicsPropertyAnimator property(
- DynamicAnimation.ViewProperty property, float value, Runnable... endActions) {
- mAnimatedProperties.put(property, value);
- mEndActionsForProperty.put(property, endActions);
- return this;
- }
-
- /** Animate the view's alpha value to the provided value. */
- public PhysicsPropertyAnimator alpha(float alpha, Runnable... endActions) {
- return property(DynamicAnimation.ALPHA, alpha, endActions);
- }
-
- /** Set the view's alpha value to 'from', then animate it to the given value. */
- public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) {
- mInitialPropertyValues.put(DynamicAnimation.ALPHA, from);
- return alpha(to, endActions);
- }
-
- /** Animate the view's translationX value to the provided value. */
- public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) {
- mPathAnimator = null; // We aren't using the path anymore if we're translating.
- return property(DynamicAnimation.TRANSLATION_X, translationX, endActions);
- }
-
- /** Set the view's translationX value to 'from', then animate it to the given value. */
- public PhysicsPropertyAnimator translationX(
- float from, float to, Runnable... endActions) {
- mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_X, from);
- return translationX(to, endActions);
- }
-
- /** Animate the view's translationY value to the provided value. */
- public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) {
- mPathAnimator = null; // We aren't using the path anymore if we're translating.
- return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions);
- }
-
- /** Set the view's translationY value to 'from', then animate it to the given value. */
- public PhysicsPropertyAnimator translationY(
- float from, float to, Runnable... endActions) {
- mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_Y, from);
- return translationY(to, endActions);
- }
-
- /**
- * Animate the view's translationX and translationY values, and call the end actions only
- * once both TRANSLATION_X and TRANSLATION_Y animations have completed.
- */
- public PhysicsPropertyAnimator position(
- float translationX, float translationY, Runnable... endActions) {
- mPositionEndActions = endActions;
- translationX(translationX);
- return translationY(translationY);
- }
-
- /**
- * Animates a 'target' point that moves along the given path, using the provided duration
- * and interpolator to animate the target. The view itself is animated using physics-based
- * animations, whose final positions are updated to the target position as it animates. This
- * results in the view 'following' the target in a realistic way.
- *
- * This method will override earlier calls to {@link #translationX}, {@link #translationY},
- * or {@link #position}, ultimately animating the view's position to the final point on the
- * given path.
- *
- * @param pathAnimEndActions End actions to run after the animator that moves the target
- * along the path ends. The views following the target may still
- * be moving.
- */
- public PhysicsPropertyAnimator followAnimatedTargetAlongPath(
- Path path,
- int targetAnimDuration,
- TimeInterpolator targetAnimInterpolator,
- Runnable... pathAnimEndActions) {
- if (mPathAnimator != null) {
- mPathAnimator.cancel();
- }
-
- mPathAnimator = ObjectAnimator.ofFloat(
- this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
-
- if (pathAnimEndActions != null) {
- mPathAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- for (Runnable action : pathAnimEndActions) {
- if (action != null) {
- action.run();
- }
- }
- }
- });
- }
-
- mPathAnimator.setDuration(targetAnimDuration);
- mPathAnimator.setInterpolator(targetAnimInterpolator);
-
- // Remove translation related values since we're going to ignore them and follow the
- // path instead.
- clearTranslationValues();
- return this;
- }
-
- private void clearTranslationValues() {
- mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X);
- mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y);
- mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X);
- mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y);
- mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X);
- mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y);
- }
-
- /** Animate the view's scaleX value to the provided value. */
- public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) {
- return property(DynamicAnimation.SCALE_X, scaleX, endActions);
- }
-
- /** Set the view's scaleX value to 'from', then animate it to the given value. */
- public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) {
- mInitialPropertyValues.put(DynamicAnimation.SCALE_X, from);
- return scaleX(to, endActions);
- }
-
- /** Animate the view's scaleY value to the provided value. */
- public PhysicsPropertyAnimator scaleY(float scaleY, Runnable... endActions) {
- return property(DynamicAnimation.SCALE_Y, scaleY, endActions);
- }
-
- /** Set the view's scaleY value to 'from', then animate it to the given value. */
- public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) {
- mInitialPropertyValues.put(DynamicAnimation.SCALE_Y, from);
- return scaleY(to, endActions);
- }
-
- /** Set the start velocity to use for all property animations. */
- public PhysicsPropertyAnimator withStartVelocity(float startVel) {
- mDefaultStartVelocity = startVel;
- return this;
- }
-
- /**
- * Set the damping ratio to use for this animation. If not supplied, will default to the
- * value from {@link PhysicsAnimationController#getSpringForce}.
- */
- public PhysicsPropertyAnimator withDampingRatio(float dampingRatio) {
- mDampingRatio = dampingRatio;
- return this;
- }
-
- /**
- * Set the stiffness to use for this animation. If not supplied, will default to the
- * value from {@link PhysicsAnimationController#getSpringForce}.
- */
- public PhysicsPropertyAnimator withStiffness(float stiffness) {
- mStiffness = stiffness;
- return this;
- }
-
- /**
- * Set the start velocities to use for TRANSLATION_X and TRANSLATION_Y animations. This
- * overrides any value set via {@link #withStartVelocity(float)} for those properties.
- */
- public PhysicsPropertyAnimator withPositionStartVelocities(float velX, float velY) {
- mPositionStartVelocities.put(DynamicAnimation.TRANSLATION_X, velX);
- mPositionStartVelocities.put(DynamicAnimation.TRANSLATION_Y, velY);
- return this;
- }
-
- /** Set a delay, in milliseconds, before kicking off the animations. */
- public PhysicsPropertyAnimator withStartDelay(long startDelay) {
- mStartDelay = startDelay;
- return this;
- }
-
- /**
- * Start the animations, and call the optional end actions once all animations for every
- * animated property on every child (including chained animations) have ended.
- */
- public void start(Runnable... after) {
- if (!isActiveController(mAssociatedController)) {
- Log.w(TAG, "Only the active animation controller is allowed to start animations. "
- + "Use PhysicsAnimationLayout#setActiveController to set the active "
- + "animation controller.");
- return;
- }
-
- final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties();
-
- // If there are end actions, set an end listener on the layout for all the properties
- // we're about to animate.
- if (after != null && after.length > 0) {
- final DynamicAnimation.ViewProperty[] propertiesArray =
- properties.toArray(new DynamicAnimation.ViewProperty[0]);
- mAssociatedController.setEndActionForMultipleProperties(() -> {
- for (Runnable callback : after) {
- callback.run();
- }
- }, propertiesArray);
- }
-
- // If we used position-specific end actions, we'll need to listen for both TRANSLATION_X
- // and TRANSLATION_Y animations ending, and call them once both have finished.
- if (mPositionEndActions != null) {
- final SpringAnimation translationXAnim =
- getAnimationFromView(DynamicAnimation.TRANSLATION_X, mView);
- final SpringAnimation translationYAnim =
- getAnimationFromView(DynamicAnimation.TRANSLATION_Y, mView);
- final Runnable waitForBothXAndY = () -> {
- if (!translationXAnim.isRunning() && !translationYAnim.isRunning()) {
- if (mPositionEndActions != null) {
- for (Runnable callback : mPositionEndActions) {
- callback.run();
- }
- }
-
- mPositionEndActions = null;
- }
- };
-
- mEndActionsForProperty.put(DynamicAnimation.TRANSLATION_X,
- new Runnable[]{waitForBothXAndY});
- mEndActionsForProperty.put(DynamicAnimation.TRANSLATION_Y,
- new Runnable[]{waitForBothXAndY});
- }
-
- if (mPathAnimator != null) {
- startPathAnimation();
- }
-
- // Actually start the animations.
- for (DynamicAnimation.ViewProperty property : properties) {
- // Don't start translation animations if we're using a path animator, the update
- // listeners added to that animator will take care of that.
- if (mPathAnimator != null
- && (property.equals(DynamicAnimation.TRANSLATION_X)
- || property.equals(DynamicAnimation.TRANSLATION_Y))) {
- return;
- }
-
- if (mInitialPropertyValues.containsKey(property)) {
- property.setValue(mView, mInitialPropertyValues.get(property));
- }
-
- final SpringForce defaultSpringForce = mController.getSpringForce(property, mView);
- animateValueForChild(
- property,
- mView,
- mAnimatedProperties.get(property),
- mPositionStartVelocities.getOrDefault(property, mDefaultStartVelocity),
- mStartDelay,
- mStiffness >= 0 ? mStiffness : defaultSpringForce.getStiffness(),
- mDampingRatio >= 0 ? mDampingRatio : defaultSpringForce.getDampingRatio(),
- mEndActionsForProperty.get(property));
- }
-
- clearAnimator();
- }
-
- /** Returns the set of properties that will animate once {@link #start} is called. */
- protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
- final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>(
- mAnimatedProperties.keySet());
-
- // If we're using a path animator, it'll kick off translation animations.
- if (mPathAnimator != null) {
- animatedProperties.add(DynamicAnimation.TRANSLATION_X);
- animatedProperties.add(DynamicAnimation.TRANSLATION_Y);
- }
-
- return animatedProperties;
- }
-
- /**
- * Animates the property of the given child view, then runs the callback provided when the
- * animation ends.
- */
- protected void animateValueForChild(
- DynamicAnimation.ViewProperty property,
- View view,
- float value,
- float startVel,
- long startDelay,
- float stiffness,
- float dampingRatio,
- Runnable... afterCallbacks) {
- if (view != null) {
- final SpringAnimation animation =
- (SpringAnimation) view.getTag(getTagIdForProperty(property));
-
- // If the animation is null, the view was probably removed from the layout before
- // the animation started.
- if (animation == null) {
- return;
- }
-
- if (afterCallbacks != null) {
- animation.addEndListener(new OneTimeEndListener() {
- @Override
- public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
- float value, float velocity) {
- super.onAnimationEnd(animation, canceled, value, velocity);
- for (Runnable runnable : afterCallbacks) {
- runnable.run();
- }
- }
- });
- }
-
- final SpringForce animationSpring = animation.getSpring();
-
- if (animationSpring == null) {
- return;
- }
-
- final Runnable configureAndStartAnimation = () -> {
- animationSpring.setStiffness(stiffness);
- animationSpring.setDampingRatio(dampingRatio);
-
- if (startVel > -Float.MAX_VALUE) {
- animation.setStartVelocity(startVel);
- }
-
- animationSpring.setFinalPosition(value);
- animation.start();
- };
-
- if (startDelay > 0) {
- postDelayed(configureAndStartAnimation, startDelay);
- } else {
- configureAndStartAnimation.run();
- }
- }
- }
-
- /**
- * Updates the final position of a view's animation, without changing any of the animation's
- * other settings. Calling this before an initial call to {@link #animateValueForChild} will
- * work, but result in unknown values for stiffness, etc. and is not recommended.
- */
- private void updateValueForChild(
- DynamicAnimation.ViewProperty property, View view, float position) {
- if (view != null) {
- final SpringAnimation animation =
- (SpringAnimation) view.getTag(getTagIdForProperty(property));
-
- if (animation == null) {
- return;
- }
-
- final SpringForce animationSpring = animation.getSpring();
-
- if (animationSpring == null) {
- return;
- }
-
- animationSpring.setFinalPosition(position);
- animation.start();
- }
- }
-
- /**
- * Configures the path animator to respect the settings passed into the animation builder
- * and adds update listeners that update the translation physics animations. Then, starts
- * the path animation.
- */
- protected void startPathAnimation() {
- final SpringForce defaultSpringForceX = mController.getSpringForce(
- DynamicAnimation.TRANSLATION_X, mView);
- final SpringForce defaultSpringForceY = mController.getSpringForce(
- DynamicAnimation.TRANSLATION_Y, mView);
-
- if (mStartDelay > 0) {
- mPathAnimator.setStartDelay(mStartDelay);
- }
-
- final Runnable updatePhysicsAnims = () -> {
- updateValueForChild(
- DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x);
- updateValueForChild(
- DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y);
- };
-
- mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run());
- mPathAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- animateValueForChild(
- DynamicAnimation.TRANSLATION_X,
- mView,
- mCurrentPointOnPath.x,
- mDefaultStartVelocity,
- 0 /* startDelay */,
- mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(),
- mDampingRatio >= 0
- ? mDampingRatio
- : defaultSpringForceX.getDampingRatio());
-
- animateValueForChild(
- DynamicAnimation.TRANSLATION_Y,
- mView,
- mCurrentPointOnPath.y,
- mDefaultStartVelocity,
- 0 /* startDelay */,
- mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(),
- mDampingRatio >= 0
- ? mDampingRatio
- : defaultSpringForceY.getDampingRatio());
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- updatePhysicsAnims.run();
- }
- });
-
- // If there's a target animator saved for the view, make sure it's not running.
- final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView);
- if (targetAnimator != null) {
- targetAnimator.cancel();
- }
-
- mView.setTag(R.id.target_animator_tag, mPathAnimator);
- mPathAnimator.start();
- }
-
- private void clearAnimator() {
- mInitialPropertyValues.clear();
- mAnimatedProperties.clear();
- mPositionStartVelocities.clear();
- mDefaultStartVelocity = -Float.MAX_VALUE;
- mStartDelay = 0;
- mStiffness = -1;
- mDampingRatio = -1;
- mEndActionsForProperty.clear();
- mPathAnimator = null;
- mPositionEndActions = null;
- }
-
- /**
- * Sets the controller that last retrieved this animator instance, so that we can prevent
- * {@link #start} from actually starting animations if called by a non-active controller.
- */
- private void setAssociatedController(PhysicsAnimationController controller) {
- mAssociatedController = controller;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
deleted file mode 100644
index 12051241f049..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ /dev/null
@@ -1,1106 +0,0 @@
-/*
- * 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.bubbles.animation;
-
-import android.content.ContentResolver;
-import android.content.res.Resources;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.provider.Settings;
-import android.util.Log;
-import android.view.View;
-import android.view.WindowInsets;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.dynamicanimation.animation.DynamicAnimation;
-import androidx.dynamicanimation.animation.FlingAnimation;
-import androidx.dynamicanimation.animation.FloatPropertyCompat;
-import androidx.dynamicanimation.animation.SpringAnimation;
-import androidx.dynamicanimation.animation.SpringForce;
-
-import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleStackView;
-import com.android.wm.shell.animation.PhysicsAnimator;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-
-import com.google.android.collect.Sets;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.function.IntSupplier;
-
-/**
- * Animation controller for bubbles when they're in their stacked state. Stacked bubbles sit atop
- * each other with a slight offset to the left or right (depending on which side of the screen they
- * are on). Bubbles 'follow' each other when dragged, and can be flung to the left or right sides of
- * the screen.
- */
-public class StackAnimationController extends
- PhysicsAnimationLayout.PhysicsAnimationController {
-
- private static final String TAG = "Bubbs.StackCtrl";
-
- /** Scale factor to use initially for new bubbles being animated in. */
- private static final float ANIMATE_IN_STARTING_SCALE = 1.15f;
-
- /** Translation factor (multiplied by stack offset) to use for bubbles being animated in/out. */
- private static final int ANIMATE_TRANSLATION_FACTOR = 4;
-
- /** Values to use for animating bubbles in. */
- private static final float ANIMATE_IN_STIFFNESS = 1000f;
- private static final int ANIMATE_IN_START_DELAY = 25;
-
- /**
- * Values to use for the default {@link SpringForce} provided to the physics animation layout.
- */
- public static final int SPRING_TO_TOUCH_STIFFNESS = 12000;
- public static final float IME_ANIMATION_STIFFNESS = SpringForce.STIFFNESS_LOW;
- private static final int CHAIN_STIFFNESS = 600;
- public static final float DEFAULT_BOUNCINESS = 0.9f;
-
- private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfig =
- new PhysicsAnimator.SpringConfig(
- ANIMATE_IN_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
-
- /**
- * Friction applied to fling animations. Since the stack must land on one of the sides of the
- * screen, we want less friction horizontally so that the stack has a better chance of making it
- * to the side without needing a spring.
- */
- private static final float FLING_FRICTION = 2.2f;
-
- /**
- * Values to use for the stack spring animation used to spring the stack to its final position
- * after a fling.
- */
- private static final int SPRING_AFTER_FLING_STIFFNESS = 750;
- private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
-
- /** Sentinel value for unset position value. */
- private static final float UNSET = -Float.MIN_VALUE;
-
- /**
- * Minimum fling velocity required to trigger moving the stack from one side of the screen to
- * the other.
- */
- private static final float ESCAPE_VELOCITY = 750f;
-
- /** Velocity required to dismiss the stack without dragging it into the dismiss target. */
- private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f;
-
- /**
- * The canonical position of the stack. This is typically the position of the first bubble, but
- * we need to keep track of it separately from the first bubble's translation in case there are
- * no bubbles, or the first bubble was just added and being animated to its new position.
- */
- private PointF mStackPosition = new PointF(-1, -1);
-
- /**
- * MagnetizedObject instance for the stack, which is used by the touch handler for the magnetic
- * dismiss target.
- */
- private MagnetizedObject<StackAnimationController> mMagnetizedStack;
-
- /**
- * The area that Bubbles will occupy after all animations end. This is used to move other
- * floating content out of the way proactively.
- */
- private Rect mAnimatingToBounds = new Rect();
-
- /** Initial starting location for the stack. */
- @Nullable private BubbleStackView.RelativeStackPosition mStackStartPosition;
-
- /** Whether or not the stack's start position has been set. */
- private boolean mStackMovedToStartPosition = false;
-
- /**
- * The stack's most recent position along the edge of the screen. This is saved when the last
- * bubble is removed, so that the stack can be restored in its previous position.
- */
- private PointF mRestingStackPosition;
-
- /** The height of the most recently visible IME. */
- private float mImeHeight = 0f;
-
- /**
- * The Y position of the stack before the IME became visible, or {@link Float#MIN_VALUE} if the
- * IME is not visible or the user moved the stack since the IME became visible.
- */
- private float mPreImeY = UNSET;
-
- /**
- * Animations on the stack position itself, which would have been started in
- * {@link #flingThenSpringFirstBubbleWithStackFollowing}. These animations dispatch to
- * {@link #moveFirstBubbleWithStackFollowing} to move the entire stack (with 'following' effect)
- * to a legal position on the side of the screen.
- */
- private HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mStackPositionAnimations =
- new HashMap<>();
-
- /**
- * Whether the current motion of the stack is due to a fling animation (vs. being dragged
- * manually).
- */
- private boolean mIsMovingFromFlinging = false;
-
- /**
- * Whether the first bubble is springing towards the touch point, rather than using the default
- * behavior of moving directly to the touch point with the rest of the stack following it.
- *
- * This happens when the user's finger exits the dismiss area while the stack is magnetized to
- * the center. Since the touch point differs from the stack location, we need to animate the
- * stack back to the touch point to avoid a jarring instant location change from the center of
- * the target to the touch point just outside the target bounds.
- *
- * This is reset once the spring animations end, since that means the first bubble has
- * successfully 'caught up' to the touch.
- */
- private boolean mFirstBubbleSpringingToTouch = false;
-
- /**
- * Whether to spring the stack to the next touch event coordinates. This is used to animate the
- * stack (including the first bubble) out of the magnetic dismiss target to the touch location.
- * Once it 'catches up' and the animation ends, we'll revert to moving the first bubble directly
- * and only animating the following bubbles.
- */
- private boolean mSpringToTouchOnNextMotionEvent = false;
-
- /** Horizontal offset of bubbles in the stack. */
- private float mStackOffset;
- /** Diameter of the bubble icon. */
- private int mBubbleBitmapSize;
- /** Width of the bubble (icon and padding). */
- private int mBubbleSize;
- /**
- * The amount of space to add between the bubbles and certain UI elements, such as the top of
- * the screen or the IME. This does not apply to the left/right sides of the screen since the
- * stack goes offscreen intentionally.
- */
- private int mBubblePaddingTop;
- /** How far offscreen the stack rests. */
- private int mBubbleOffscreen;
- /** How far down the screen the stack starts, when there is no pre-existing location. */
- private int mStackStartingVerticalOffset;
- /** Height of the status bar. */
- private float mStatusBarHeight;
-
- /** FloatingContentCoordinator instance for resolving floating content conflicts. */
- private FloatingContentCoordinator mFloatingContentCoordinator;
-
- /**
- * FloatingContent instance that returns the stack's location on the screen, and moves it when
- * requested.
- */
- private final FloatingContentCoordinator.FloatingContent mStackFloatingContent =
- new FloatingContentCoordinator.FloatingContent() {
-
- private final Rect mFloatingBoundsOnScreen = new Rect();
-
- @Override
- public void moveToBounds(@NonNull Rect bounds) {
- springStack(bounds.left, bounds.top, SpringForce.STIFFNESS_LOW);
- }
-
- @NonNull
- @Override
- public Rect getAllowedFloatingBoundsRegion() {
- final Rect floatingBounds = getFloatingBoundsOnScreen();
- final Rect allowableStackArea = new Rect();
- getAllowableStackPositionRegion().roundOut(allowableStackArea);
- allowableStackArea.right += floatingBounds.width();
- allowableStackArea.bottom += floatingBounds.height();
- return allowableStackArea;
- }
-
- @NonNull
- @Override
- public Rect getFloatingBoundsOnScreen() {
- if (!mAnimatingToBounds.isEmpty()) {
- return mAnimatingToBounds;
- }
-
- if (mLayout.getChildCount() > 0) {
- // Calculate the bounds using stack position + bubble size so that we don't need to
- // wait for the bubble views to lay out.
- mFloatingBoundsOnScreen.set(
- (int) mStackPosition.x,
- (int) mStackPosition.y,
- (int) mStackPosition.x + mBubbleSize,
- (int) mStackPosition.y + mBubbleSize + mBubblePaddingTop);
- } else {
- mFloatingBoundsOnScreen.setEmpty();
- }
-
- return mFloatingBoundsOnScreen;
- }
- };
-
- /** Returns the number of 'real' bubbles (excluding the overflow bubble). */
- private IntSupplier mBubbleCountSupplier;
-
- /**
- * Callback to run whenever any bubble is animated out. The BubbleStackView will check if the
- * end of this animation means we have no bubbles left, and notify the BubbleController.
- */
- private Runnable mOnBubbleAnimatedOutAction;
-
- public StackAnimationController(
- FloatingContentCoordinator floatingContentCoordinator,
- IntSupplier bubbleCountSupplier,
- Runnable onBubbleAnimatedOutAction) {
- mFloatingContentCoordinator = floatingContentCoordinator;
- mBubbleCountSupplier = bubbleCountSupplier;
- mOnBubbleAnimatedOutAction = onBubbleAnimatedOutAction;
- }
-
- /**
- * Instantly move the first bubble to the given point, and animate the rest of the stack behind
- * it with the 'following' effect.
- */
- public void moveFirstBubbleWithStackFollowing(float x, float y) {
- // If we're moving the bubble around, we're not animating to any bounds.
- mAnimatingToBounds.setEmpty();
-
- // If we manually move the bubbles with the IME open, clear the return point since we don't
- // want the stack to snap away from the new position.
- mPreImeY = UNSET;
-
- moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X, x);
- moveFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y, y);
-
- // This method is called when the stack is being dragged manually, so we're clearly no
- // longer flinging.
- mIsMovingFromFlinging = false;
- }
-
- /**
- * The position of the stack - typically the position of the first bubble; if no bubbles have
- * been added yet, it will be where the first bubble will go when added.
- */
- public PointF getStackPosition() {
- return mStackPosition;
- }
-
- /** Whether the stack is on the left side of the screen. */
- public boolean isStackOnLeftSide() {
- if (mLayout == null || !isStackPositionSet()) {
- return true; // Default to left, which is where it starts by default.
- }
-
- float stackCenter = mStackPosition.x + mBubbleBitmapSize / 2;
- float screenCenter = mLayout.getWidth() / 2;
- return stackCenter < screenCenter;
- }
-
- /**
- * Fling stack to given corner, within allowable screen bounds.
- * Note that we need new SpringForce instances per animation despite identical configs because
- * SpringAnimation uses SpringForce's internal (changing) velocity while the animation runs.
- */
- public void springStack(
- float destinationX, float destinationY, float stiffness) {
- notifyFloatingCoordinatorStackAnimatingTo(destinationX, destinationY);
-
- springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_X,
- new SpringForce()
- .setStiffness(stiffness)
- .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
- 0 /* startXVelocity */,
- destinationX);
-
- springFirstBubbleWithStackFollowing(DynamicAnimation.TRANSLATION_Y,
- new SpringForce()
- .setStiffness(stiffness)
- .setDampingRatio(SPRING_AFTER_FLING_DAMPING_RATIO),
- 0 /* startYVelocity */,
- destinationY);
- }
-
- /**
- * Springs the stack to the specified x/y coordinates, with the stiffness used for springs after
- * flings.
- */
- public void springStackAfterFling(float destinationX, float destinationY) {
- springStack(destinationX, destinationY, SPRING_AFTER_FLING_STIFFNESS);
- }
-
- /**
- * Flings the stack starting with the given velocities, springing it to the nearest edge
- * afterward.
- *
- * @return The X value that the stack will end up at after the fling/spring.
- */
- public float flingStackThenSpringToEdge(float x, float velX, float velY) {
- final boolean stackOnLeftSide = x - mBubbleBitmapSize / 2 < mLayout.getWidth() / 2;
-
- final boolean stackShouldFlingLeft = stackOnLeftSide
- ? velX < ESCAPE_VELOCITY
- : velX < -ESCAPE_VELOCITY;
-
- final RectF stackBounds = getAllowableStackPositionRegion();
-
- // Target X translation (either the left or right side of the screen).
- final float destinationRelativeX = stackShouldFlingLeft
- ? stackBounds.left : stackBounds.right;
-
- // If all bubbles were removed during a drag event, just return the X we would have animated
- // to if there were still bubbles.
- if (mLayout == null || mLayout.getChildCount() == 0) {
- return destinationRelativeX;
- }
-
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float stiffness = Settings.Secure.getFloat(contentResolver, "bubble_stiffness",
- SPRING_AFTER_FLING_STIFFNESS /* default */);
- final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
- SPRING_AFTER_FLING_DAMPING_RATIO);
- final float friction = Settings.Secure.getFloat(contentResolver, "bubble_friction",
- FLING_FRICTION);
-
- // Minimum velocity required for the stack to make it to the targeted side of the screen,
- // taking friction into account (4.2f is the number that friction scalars are multiplied by
- // in DynamicAnimation.DragForce). This is an estimate - it could possibly be slightly off,
- // but the SpringAnimation at the end will ensure that it reaches the destination X
- // regardless.
- final float minimumVelocityToReachEdge =
- (destinationRelativeX - x) * (friction * 4.2f);
-
- final float estimatedY = PhysicsAnimator.estimateFlingEndValue(
- mStackPosition.y, velY,
- new PhysicsAnimator.FlingConfig(
- friction, stackBounds.top, stackBounds.bottom));
-
- notifyFloatingCoordinatorStackAnimatingTo(destinationRelativeX, estimatedY);
-
- // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity so
- // that it'll make it all the way to the side of the screen.
- final float startXVelocity = stackShouldFlingLeft
- ? Math.min(minimumVelocityToReachEdge, velX)
- : Math.max(minimumVelocityToReachEdge, velX);
-
-
-
- flingThenSpringFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_X,
- startXVelocity,
- friction,
- new SpringForce()
- .setStiffness(stiffness)
- .setDampingRatio(dampingRatio),
- destinationRelativeX);
-
- flingThenSpringFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_Y,
- velY,
- friction,
- new SpringForce()
- .setStiffness(stiffness)
- .setDampingRatio(dampingRatio),
- /* destination */ null);
-
- // If we're flinging now, there's no more touch event to catch up to.
- mFirstBubbleSpringingToTouch = false;
- mIsMovingFromFlinging = true;
- return destinationRelativeX;
- }
-
- /**
- * Where the stack would be if it were snapped to the nearest horizontal edge (left or right).
- */
- public PointF getStackPositionAlongNearestHorizontalEdge() {
- final PointF stackPos = getStackPosition();
- final boolean onLeft = mLayout.isFirstChildXLeftOfCenter(stackPos.x);
- final RectF bounds = getAllowableStackPositionRegion();
-
- stackPos.x = onLeft ? bounds.left : bounds.right;
- return stackPos;
- }
-
- /** Description of current animation controller state. */
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("StackAnimationController state:");
- pw.print(" isActive: "); pw.println(isActiveController());
- pw.print(" restingStackPos: ");
- pw.println(mRestingStackPosition != null ? mRestingStackPosition.toString() : "null");
- pw.print(" currentStackPos: "); pw.println(mStackPosition.toString());
- pw.print(" isMovingFromFlinging: "); pw.println(mIsMovingFromFlinging);
- pw.print(" withinDismiss: "); pw.println(isStackStuckToTarget());
- pw.print(" firstBubbleSpringing: "); pw.println(mFirstBubbleSpringingToTouch);
- }
-
- /**
- * Flings the first bubble along the given property's axis, using the provided configuration
- * values. When the animation ends - either by hitting the min/max, or by friction sufficiently
- * reducing momentum - a SpringAnimation takes over to snap the bubble to the given final
- * position.
- */
- protected void flingThenSpringFirstBubbleWithStackFollowing(
- DynamicAnimation.ViewProperty property,
- float vel,
- float friction,
- SpringForce spring,
- Float finalPosition) {
- if (!isActiveController()) {
- return;
- }
-
- Log.d(TAG, String.format("Flinging %s.",
- PhysicsAnimationLayout.getReadablePropertyName(property)));
-
- StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
- final float currentValue = firstBubbleProperty.getValue(this);
- final RectF bounds = getAllowableStackPositionRegion();
- final float min =
- property.equals(DynamicAnimation.TRANSLATION_X)
- ? bounds.left
- : bounds.top;
- final float max =
- property.equals(DynamicAnimation.TRANSLATION_X)
- ? bounds.right
- : bounds.bottom;
-
- FlingAnimation flingAnimation = new FlingAnimation(this, firstBubbleProperty);
- flingAnimation.setFriction(friction)
- .setStartVelocity(vel)
-
- // If the bubble's property value starts beyond the desired min/max, use that value
- // instead so that the animation won't immediately end. If, for example, the user
- // drags the bubbles into the navigation bar, but then flings them upward, we want
- // the fling to occur despite temporarily having a value outside of the min/max. If
- // the bubbles are out of bounds and flung even farther out of bounds, the fling
- // animation will halt immediately and the SpringAnimation will take over, springing
- // it in reverse to the (legal) final position.
- .setMinValue(Math.min(currentValue, min))
- .setMaxValue(Math.max(currentValue, max))
-
- .addEndListener((animation, canceled, endValue, endVelocity) -> {
- if (!canceled) {
- mRestingStackPosition.set(mStackPosition);
-
- springFirstBubbleWithStackFollowing(property, spring, endVelocity,
- finalPosition != null
- ? finalPosition
- : Math.max(min, Math.min(max, endValue)));
- }
- });
-
- cancelStackPositionAnimation(property);
- mStackPositionAnimations.put(property, flingAnimation);
- flingAnimation.start();
- }
-
- /**
- * Cancel any stack position animations that were started by calling
- * @link #flingThenSpringFirstBubbleWithStackFollowing}, and remove any corresponding end
- * listeners.
- */
- public void cancelStackPositionAnimations() {
- cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_X);
- cancelStackPositionAnimation(DynamicAnimation.TRANSLATION_Y);
-
- removeEndActionForProperty(DynamicAnimation.TRANSLATION_X);
- removeEndActionForProperty(DynamicAnimation.TRANSLATION_Y);
- }
-
- /** Save the current IME height so that we know where the stack bounds should be. */
- public void setImeHeight(int imeHeight) {
- mImeHeight = imeHeight;
- }
-
- /**
- * Animates the stack either away from the newly visible IME, or back to its original position
- * due to the IME going away.
- *
- * @return The destination Y value of the stack due to the IME movement (or the current position
- * of the stack if it's not moving).
- */
- public float animateForImeVisibility(boolean imeVisible) {
- final float maxBubbleY = getAllowableStackPositionRegion().bottom;
- float destinationY = UNSET;
-
- if (imeVisible) {
- // Stack is lower than it should be and overlaps the now-visible IME.
- if (mStackPosition.y > maxBubbleY && mPreImeY == UNSET) {
- mPreImeY = mStackPosition.y;
- destinationY = maxBubbleY;
- }
- } else {
- if (mPreImeY != UNSET) {
- destinationY = mPreImeY;
- mPreImeY = UNSET;
- }
- }
-
- if (destinationY != UNSET) {
- springFirstBubbleWithStackFollowing(
- DynamicAnimation.TRANSLATION_Y,
- getSpringForce(DynamicAnimation.TRANSLATION_Y, /* view */ null)
- .setStiffness(IME_ANIMATION_STIFFNESS),
- /* startVel */ 0f,
- destinationY);
-
- notifyFloatingCoordinatorStackAnimatingTo(mStackPosition.x, destinationY);
- }
-
- return destinationY != UNSET ? destinationY : mStackPosition.y;
- }
-
- /**
- * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so
- * we return these bounds from
- * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}.
- */
- private void notifyFloatingCoordinatorStackAnimatingTo(float x, float y) {
- final Rect floatingBounds = mStackFloatingContent.getFloatingBoundsOnScreen();
- floatingBounds.offsetTo((int) x, (int) y);
- mAnimatingToBounds = floatingBounds;
- mFloatingContentCoordinator.onContentMoved(mStackFloatingContent);
- }
-
- /**
- * Returns the region that the stack position must stay within. This goes slightly off the left
- * and right sides of the screen, below the status bar/cutout and above the navigation bar.
- * While the stack position is not allowed to rest outside of these bounds, it can temporarily
- * be animated or dragged beyond them.
- */
- public RectF getAllowableStackPositionRegion() {
- final WindowInsets insets = mLayout.getRootWindowInsets();
- final RectF allowableRegion = new RectF();
- if (insets != null) {
- allowableRegion.left =
- -mBubbleOffscreen
- + Math.max(
- insets.getSystemWindowInsetLeft(),
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetLeft()
- : 0);
- allowableRegion.right =
- mLayout.getWidth()
- - mBubbleSize
- + mBubbleOffscreen
- - Math.max(
- insets.getSystemWindowInsetRight(),
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetRight()
- : 0);
-
- allowableRegion.top =
- mBubblePaddingTop
- + Math.max(
- mStatusBarHeight,
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetTop()
- : 0);
- allowableRegion.bottom =
- mLayout.getHeight()
- - mBubbleSize
- - mBubblePaddingTop
- - (mImeHeight != UNSET ? mImeHeight + mBubblePaddingTop : 0f)
- - Math.max(
- insets.getStableInsetBottom(),
- insets.getDisplayCutout() != null
- ? insets.getDisplayCutout().getSafeInsetBottom()
- : 0);
- }
-
- return allowableRegion;
- }
-
- /** Moves the stack in response to a touch event. */
- public void moveStackFromTouch(float x, float y) {
- // Begin the spring-to-touch catch up animation if needed.
- if (mSpringToTouchOnNextMotionEvent) {
- springStack(x, y, SPRING_TO_TOUCH_STIFFNESS);
- mSpringToTouchOnNextMotionEvent = false;
- mFirstBubbleSpringingToTouch = true;
- } else if (mFirstBubbleSpringingToTouch) {
- final SpringAnimation springToTouchX =
- (SpringAnimation) mStackPositionAnimations.get(
- DynamicAnimation.TRANSLATION_X);
- final SpringAnimation springToTouchY =
- (SpringAnimation) mStackPositionAnimations.get(
- DynamicAnimation.TRANSLATION_Y);
-
- // If either animation is still running, we haven't caught up. Update the animations.
- if (springToTouchX.isRunning() || springToTouchY.isRunning()) {
- springToTouchX.animateToFinalPosition(x);
- springToTouchY.animateToFinalPosition(y);
- } else {
- // If the animations have finished, the stack is now at the touch point. We can
- // resume moving the bubble directly.
- mFirstBubbleSpringingToTouch = false;
- }
- }
-
- if (!mFirstBubbleSpringingToTouch && !isStackStuckToTarget()) {
- moveFirstBubbleWithStackFollowing(x, y);
- }
- }
-
- /** Notify the controller that the stack has been unstuck from the dismiss target. */
- public void onUnstuckFromTarget() {
- mSpringToTouchOnNextMotionEvent = true;
- }
-
- /**
- * 'Implode' the stack by shrinking the bubbles, fading them out, and translating them down.
- */
- public void animateStackDismissal(float translationYBy, Runnable after) {
- animationsForChildrenFromIndex(0, (index, animation) ->
- animation
- .scaleX(0f)
- .scaleY(0f)
- .alpha(0f)
- .translationY(
- mLayout.getChildAt(index).getTranslationY() + translationYBy)
- .withStiffness(SpringForce.STIFFNESS_HIGH))
- .startAll(after);
- }
-
- /**
- * Springs the first bubble to the given final position, with the rest of the stack 'following'.
- */
- protected void springFirstBubbleWithStackFollowing(
- DynamicAnimation.ViewProperty property, SpringForce spring,
- float vel, float finalPosition, @Nullable Runnable... after) {
-
- if (mLayout.getChildCount() == 0 || !isActiveController()) {
- return;
- }
-
- Log.d(TAG, String.format("Springing %s to final position %f.",
- PhysicsAnimationLayout.getReadablePropertyName(property),
- finalPosition));
-
- // Whether we're springing towards the touch location, rather than to a position on the
- // sides of the screen.
- final boolean isSpringingTowardsTouch = mSpringToTouchOnNextMotionEvent;
-
- StackPositionProperty firstBubbleProperty = new StackPositionProperty(property);
- SpringAnimation springAnimation =
- new SpringAnimation(this, firstBubbleProperty)
- .setSpring(spring)
- .addEndListener((dynamicAnimation, b, v, v1) -> {
- if (!isSpringingTowardsTouch) {
- // If we're springing towards the touch position, don't save the
- // resting position - the touch location is not a valid resting
- // position. We'll set this when the stack springs to the left or
- // right side of the screen after the touch gesture ends.
- mRestingStackPosition.set(mStackPosition);
- }
-
- if (after != null) {
- for (Runnable callback : after) {
- callback.run();
- }
- }
- })
- .setStartVelocity(vel);
-
- cancelStackPositionAnimation(property);
- mStackPositionAnimations.put(property, springAnimation);
- springAnimation.animateToFinalPosition(finalPosition);
- }
-
- @Override
- Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
- return Sets.newHashSet(
- DynamicAnimation.TRANSLATION_X, // For positioning.
- DynamicAnimation.TRANSLATION_Y,
- DynamicAnimation.ALPHA, // For fading in new bubbles.
- DynamicAnimation.SCALE_X, // For 'popping in' new bubbles.
- DynamicAnimation.SCALE_Y);
- }
-
- @Override
- int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
- if (property.equals(DynamicAnimation.TRANSLATION_X)
- || property.equals(DynamicAnimation.TRANSLATION_Y)) {
- return index + 1;
- } else {
- return NONE;
- }
- }
-
-
- @Override
- float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
- if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
- // If we're in the dismiss target, have the bubbles pile on top of each other with no
- // offset.
- if (isStackStuckToTarget()) {
- return 0f;
- } else {
- return mStackOffset;
- }
- } else {
- return 0f;
- }
- }
-
- @Override
- SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float dampingRatio = Settings.Secure.getFloat(contentResolver, "bubble_damping",
- DEFAULT_BOUNCINESS);
-
- return new SpringForce()
- .setDampingRatio(dampingRatio)
- .setStiffness(CHAIN_STIFFNESS);
- }
-
- @Override
- void onChildAdded(View child, int index) {
- // Don't animate additions within the dismiss target.
- if (isStackStuckToTarget()) {
- return;
- }
-
- if (getBubbleCount() == 1) {
- // If this is the first child added, position the stack in its starting position.
- moveStackToStartPosition();
- } else if (isStackPositionSet() && mLayout.indexOfChild(child) == 0) {
- // Otherwise, animate the bubble in if it's the newest bubble. If we're adding a bubble
- // to the back of the stack, it'll be largely invisible so don't bother animating it in.
- animateInBubble(child, index);
- }
- }
-
- @Override
- void onChildRemoved(View child, int index, Runnable finishRemoval) {
- PhysicsAnimator.getInstance(child)
- .spring(DynamicAnimation.ALPHA, 0f)
- .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
- .withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
- .start();
-
- // If there are other bubbles, pull them into the correct position.
- if (getBubbleCount() > 0) {
- animationForChildAtIndex(0).translationX(mStackPosition.x).start();
- } else {
- // When all children are removed ensure stack position is sane
- setStackPosition(mRestingStackPosition == null
- ? getStartPosition()
- : mRestingStackPosition);
-
- // Remove the stack from the coordinator since we don't have any bubbles and aren't
- // visible.
- mFloatingContentCoordinator.onContentRemoved(mStackFloatingContent);
- }
- }
-
- @Override
- void onChildReordered(View child, int oldIndex, int newIndex) {
- if (isStackPositionSet()) {
- setStackPosition(mStackPosition);
- }
- }
-
- @Override
- void onActiveControllerForLayout(PhysicsAnimationLayout layout) {
- Resources res = layout.getResources();
- mStackOffset = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
- mBubbleSize = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
- mBubbleBitmapSize = res.getDimensionPixelSize(R.dimen.bubble_bitmap_size);
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mBubbleOffscreen = res.getDimensionPixelSize(R.dimen.bubble_stack_offscreen);
- mStackStartingVerticalOffset =
- res.getDimensionPixelSize(R.dimen.bubble_stack_starting_offset_y);
- mStatusBarHeight =
- res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
- }
-
- /**
- * Update effective screen width based on current orientation.
- * @param orientation Landscape or portrait.
- */
- public void updateResources(int orientation) {
- if (mLayout != null) {
- Resources res = mLayout.getContext().getResources();
- mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
- mStatusBarHeight = res.getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- }
- }
-
- private boolean isStackStuckToTarget() {
- return mMagnetizedStack != null && mMagnetizedStack.getObjectStuckToTarget();
- }
-
- /** Moves the stack, without any animation, to the starting position. */
- private void moveStackToStartPosition() {
- // Post to ensure that the layout's width and height have been calculated.
- mLayout.setVisibility(View.INVISIBLE);
- mLayout.post(() -> {
- setStackPosition(mRestingStackPosition == null
- ? getStartPosition()
- : mRestingStackPosition);
- mStackMovedToStartPosition = true;
- mLayout.setVisibility(View.VISIBLE);
-
- // Animate in the top bubble now that we're visible.
- if (mLayout.getChildCount() > 0) {
- // Add the stack to the floating content coordinator now that we have a bubble and
- // are visible.
- mFloatingContentCoordinator.onContentAdded(mStackFloatingContent);
-
- animateInBubble(mLayout.getChildAt(0), 0 /* index */);
- }
- });
- }
-
- /**
- * Moves the first bubble instantly to the given X or Y translation, and instructs subsequent
- * bubbles to animate 'following' to the new location.
- */
- private void moveFirstBubbleWithStackFollowing(
- DynamicAnimation.ViewProperty property, float value) {
-
- // Update the canonical stack position.
- if (property.equals(DynamicAnimation.TRANSLATION_X)) {
- mStackPosition.x = value;
- } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
- mStackPosition.y = value;
- }
-
- if (mLayout.getChildCount() > 0) {
- property.setValue(mLayout.getChildAt(0), value);
- if (mLayout.getChildCount() > 1) {
- animationForChildAtIndex(1)
- .property(property, value + getOffsetForChainedPropertyAnimation(property))
- .start();
- }
- }
- }
-
- /** Moves the stack to a position instantly, with no animation. */
- public void setStackPosition(PointF pos) {
- Log.d(TAG, String.format("Setting position to (%f, %f).", pos.x, pos.y));
- mStackPosition.set(pos.x, pos.y);
-
- if (mRestingStackPosition == null) {
- mRestingStackPosition = new PointF();
- }
-
- mRestingStackPosition.set(mStackPosition);
-
- // If we're not the active controller, we don't want to physically move the bubble views.
- if (isActiveController()) {
- // Cancel animations that could be moving the views.
- mLayout.cancelAllAnimationsOfProperties(
- DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y);
- cancelStackPositionAnimations();
-
- // Since we're not using the chained animations, apply the offsets manually.
- final float xOffset = getOffsetForChainedPropertyAnimation(
- DynamicAnimation.TRANSLATION_X);
- final float yOffset = getOffsetForChainedPropertyAnimation(
- DynamicAnimation.TRANSLATION_Y);
- for (int i = 0; i < mLayout.getChildCount(); i++) {
- mLayout.getChildAt(i).setTranslationX(pos.x + (i * xOffset));
- mLayout.getChildAt(i).setTranslationY(pos.y + (i * yOffset));
- }
- }
- }
-
- public void setStackPosition(BubbleStackView.RelativeStackPosition position) {
- setStackPosition(position.getAbsolutePositionInRegion(getAllowableStackPositionRegion()));
- }
-
- public BubbleStackView.RelativeStackPosition getRelativeStackPosition() {
- return new BubbleStackView.RelativeStackPosition(
- mStackPosition, getAllowableStackPositionRegion());
- }
-
- /**
- * Sets the starting position for the stack, where it will be located when the first bubble is
- * added.
- */
- public void setStackStartPosition(BubbleStackView.RelativeStackPosition position) {
- mStackStartPosition = position;
- }
-
- /**
- * Returns the starting stack position. If {@link #setStackStartPosition} was called, this will
- * return that position - otherwise, a reasonable default will be returned.
- */
- @Nullable public PointF getStartPosition() {
- if (mLayout == null) {
- return null;
- }
-
- if (mStackStartPosition == null) {
- // Start on the left if we're in LTR, right otherwise.
- final boolean startOnLeft =
- mLayout.getResources().getConfiguration().getLayoutDirection()
- != View.LAYOUT_DIRECTION_RTL;
-
- final float startingVerticalOffset = mLayout.getResources().getDimensionPixelOffset(
- R.dimen.bubble_stack_starting_offset_y);
-
- mStackStartPosition = new BubbleStackView.RelativeStackPosition(
- startOnLeft,
- startingVerticalOffset / getAllowableStackPositionRegion().height());
- }
-
- return mStackStartPosition.getAbsolutePositionInRegion(getAllowableStackPositionRegion());
- }
-
- private boolean isStackPositionSet() {
- return mStackMovedToStartPosition;
- }
-
- /** Animates in the given bubble. */
- private void animateInBubble(View child, int index) {
- if (!isActiveController()) {
- return;
- }
-
- final float xOffset =
- getOffsetForChainedPropertyAnimation(DynamicAnimation.TRANSLATION_X);
-
- // Position the new bubble in the correct position, scaled down completely.
- child.setTranslationX(mStackPosition.x + xOffset * index);
- child.setTranslationY(mStackPosition.y);
- child.setScaleX(0f);
- child.setScaleY(0f);
-
- // Push the subsequent views out of the way, if there are subsequent views.
- if (index + 1 < mLayout.getChildCount()) {
- animationForChildAtIndex(index + 1)
- .translationX(mStackPosition.x + xOffset * (index + 1))
- .withStiffness(SpringForce.STIFFNESS_LOW)
- .start();
- }
-
- // Scale in the new bubble, slightly delayed.
- animationForChild(child)
- .scaleX(1f)
- .scaleY(1f)
- .withStiffness(ANIMATE_IN_STIFFNESS)
- .withStartDelay(mLayout.getChildCount() > 1 ? ANIMATE_IN_START_DELAY : 0)
- .start();
- }
-
- /**
- * Cancels any outstanding first bubble property animations that are running. This does not
- * affect the SpringAnimations controlling the individual bubbles' 'following' effect - it only
- * cancels animations started from {@link #springFirstBubbleWithStackFollowing} and
- * {@link #flingThenSpringFirstBubbleWithStackFollowing}.
- */
- private void cancelStackPositionAnimation(DynamicAnimation.ViewProperty property) {
- if (mStackPositionAnimations.containsKey(property)) {
- mStackPositionAnimations.get(property).cancel();
- }
- }
-
- /**
- * Returns the {@link MagnetizedObject} instance for the bubble stack, with the provided
- * {@link MagnetizedObject.MagneticTarget} added as a target.
- */
- public MagnetizedObject<StackAnimationController> getMagnetizedStack(
- MagnetizedObject.MagneticTarget target) {
- if (mMagnetizedStack == null) {
- mMagnetizedStack = new MagnetizedObject<StackAnimationController>(
- mLayout.getContext(),
- this,
- new StackPositionProperty(DynamicAnimation.TRANSLATION_X),
- new StackPositionProperty(DynamicAnimation.TRANSLATION_Y)
- ) {
- @Override
- public float getWidth(@NonNull StackAnimationController underlyingObject) {
- return mBubbleSize;
- }
-
- @Override
- public float getHeight(@NonNull StackAnimationController underlyingObject) {
- return mBubbleSize;
- }
-
- @Override
- public void getLocationOnScreen(@NonNull StackAnimationController underlyingObject,
- @NonNull int[] loc) {
- loc[0] = (int) mStackPosition.x;
- loc[1] = (int) mStackPosition.y;
- }
- };
- mMagnetizedStack.addTarget(target);
- mMagnetizedStack.setHapticsEnabled(true);
- mMagnetizedStack.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY);
- }
-
- final ContentResolver contentResolver = mLayout.getContext().getContentResolver();
- final float minVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_fling_min_velocity",
- mMagnetizedStack.getFlingToTargetMinVelocity() /* default */);
- final float maxVelocity = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_stick_max_velocity",
- mMagnetizedStack.getStickToTargetMaxXVelocity() /* default */);
- final float targetWidth = Settings.Secure.getFloat(contentResolver,
- "bubble_dismiss_target_width_percent",
- mMagnetizedStack.getFlingToTargetWidthPercent() /* default */);
-
- mMagnetizedStack.setFlingToTargetMinVelocity(minVelocity);
- mMagnetizedStack.setStickToTargetMaxXVelocity(maxVelocity);
- mMagnetizedStack.setFlingToTargetWidthPercent(targetWidth);
-
- return mMagnetizedStack;
- }
-
- /** Returns the number of 'real' bubbles (excluding overflow). */
- private int getBubbleCount() {
- return mBubbleCountSupplier.getAsInt();
- }
-
- /**
- * FloatProperty that uses {@link #moveFirstBubbleWithStackFollowing} to set the first bubble's
- * translation and animate the rest of the stack with it. A DynamicAnimation can animate this
- * property directly to move the first bubble and cause the stack to 'follow' to the new
- * location.
- *
- * This could also be achieved by simply animating the first bubble view and adding an update
- * listener to dispatch movement to the rest of the stack. However, this would require
- * duplication of logic in that update handler - it's simpler to keep all logic contained in the
- * {@link #moveFirstBubbleWithStackFollowing} method.
- */
- private class StackPositionProperty
- extends FloatPropertyCompat<StackAnimationController> {
- private final DynamicAnimation.ViewProperty mProperty;
-
- private StackPositionProperty(DynamicAnimation.ViewProperty property) {
- super(property.toString());
- mProperty = property;
- }
-
- @Override
- public float getValue(StackAnimationController controller) {
- return mLayout.getChildCount() > 0 ? mProperty.getValue(mLayout.getChildAt(0)) : 0;
- }
-
- @Override
- public void setValue(StackAnimationController controller, float value) {
- moveFirstBubbleWithStackFollowing(mProperty, value);
- }
- }
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
deleted file mode 100644
index 6b5f237ac76f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ /dev/null
@@ -1,109 +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.
- */
-
-package com.android.systemui.bubbles.dagger;
-
-import android.app.INotificationManager;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.os.Handler;
-import android.view.WindowManager;
-
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.Bubbles;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.FeatureFlags;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-
-import dagger.Module;
-import dagger.Provides;
-
-/** */
-@Module
-public interface BubbleModule {
-
- /**
- */
- @SysUISingleton
- @Provides
- static Bubbles newBubbleController(
- Context context,
- NotificationShadeWindowController notificationShadeWindowController,
- StatusBarStateController statusBarStateController,
- ShadeController shadeController,
- ConfigurationController configurationController,
- NotificationInterruptStateProvider interruptionStateProvider,
- ZenModeController zenModeController,
- NotificationLockscreenUserManager notifUserManager,
- NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- FeatureFlags featureFlags,
- DumpManager dumpManager,
- FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState,
- INotificationManager notifManager,
- IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- LauncherApps launcherApps,
- UiEventLogger uiEventLogger,
- @Main Handler mainHandler,
- ShellTaskOrganizer organizer) {
- return BubbleController.create(
- context,
- notificationShadeWindowController,
- statusBarStateController,
- shadeController,
- null /* synchronizer */,
- configurationController,
- interruptionStateProvider,
- zenModeController,
- notifUserManager,
- groupManager,
- entryManager,
- notifPipeline,
- featureFlags,
- dumpManager,
- floatingContentCoordinator,
- sysUiState,
- notifManager,
- statusBarService,
- windowManager,
- windowManagerShellWrapper,
- launcherApps,
- uiEventLogger,
- mainHandler,
- organizer);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
deleted file mode 100644
index ce0786d86460..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubblePersistentRepository.kt
+++ /dev/null
@@ -1,62 +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.
- */
-package com.android.systemui.bubbles.storage
-
-import android.content.Context
-import android.util.AtomicFile
-import android.util.Log
-import java.io.File
-import java.io.FileOutputStream
-import java.io.IOException
-
-class BubblePersistentRepository(context: Context) {
-
- private val bubbleFile: AtomicFile = AtomicFile(File(context.filesDir,
- "overflow_bubbles.xml"), "overflow-bubbles")
-
- fun persistsToDisk(bubbles: List<BubbleEntity>): Boolean {
- if (DEBUG) Log.d(TAG, "persisting ${bubbles.size} bubbles")
- synchronized(bubbleFile) {
- val stream: FileOutputStream = try { bubbleFile.startWrite() } catch (e: IOException) {
- Log.e(TAG, "Failed to save bubble file", e)
- return false
- }
- try {
- writeXml(stream, bubbles)
- bubbleFile.finishWrite(stream)
- if (DEBUG) Log.d(TAG, "persisted ${bubbles.size} bubbles")
- return true
- } catch (e: Exception) {
- Log.e(TAG, "Failed to save bubble file, restoring backup", e)
- bubbleFile.failWrite(stream)
- }
- }
- return false
- }
-
- fun readFromDisk(): List<BubbleEntity> {
- synchronized(bubbleFile) {
- if (!bubbleFile.exists()) return emptyList()
- try { return bubbleFile.openRead().use(::readXml) } catch (e: Throwable) {
- Log.e(TAG, "Failed to open bubble file", e)
- }
- return emptyList()
- }
- }
-}
-
-private const val TAG = "BubblePersistentRepository"
-private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
deleted file mode 100644
index e0a7c7879f43..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleVolatileRepository.kt
+++ /dev/null
@@ -1,88 +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.
- */
-package com.android.systemui.bubbles.storage
-
-import android.content.pm.LauncherApps
-import android.os.UserHandle
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.bubbles.ShortcutKey
-
-private const val CAPACITY = 16
-
-/**
- * BubbleVolatileRepository holds the most updated snapshot of list of bubbles for in-memory
- * manipulation.
- */
-class BubbleVolatileRepository(private val launcherApps: LauncherApps) {
- /**
- * An ordered set of bubbles based on their natural ordering.
- */
- private var entities = mutableSetOf<BubbleEntity>()
-
- /**
- * The capacity of the cache.
- */
- @VisibleForTesting
- var capacity = CAPACITY
-
- /**
- * Returns a snapshot of all the bubbles.
- */
- val bubbles: List<BubbleEntity>
- @Synchronized
- get() = entities.toList()
-
- /**
- * Add the bubbles to memory and perform a de-duplication. In case a bubble already exists,
- * it will be moved to the last.
- */
- @Synchronized
- fun addBubbles(bubbles: List<BubbleEntity>) {
- if (bubbles.isEmpty()) return
- // Verify the size of given bubbles is within capacity, otherwise trim down to capacity
- val bubblesInRange = bubbles.takeLast(capacity)
- // To ensure natural ordering of the bubbles, removes bubbles which already exist
- val uniqueBubbles = bubblesInRange.filterNot { b: BubbleEntity ->
- entities.removeIf { e: BubbleEntity -> b.key == e.key } }
- val overflowCount = entities.size + bubblesInRange.size - capacity
- if (overflowCount > 0) {
- // Uncache ShortcutInfo of bubbles that will be removed due to capacity
- uncache(entities.take(overflowCount))
- entities = entities.drop(overflowCount).toMutableSet()
- }
- entities.addAll(bubblesInRange)
- cache(uniqueBubbles)
- }
-
- @Synchronized
- fun removeBubbles(bubbles: List<BubbleEntity>) =
- uncache(bubbles.filter { b: BubbleEntity ->
- entities.removeIf { e: BubbleEntity -> b.key == e.key } })
-
- private fun cache(bubbles: List<BubbleEntity>) {
- bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) ->
- launcherApps.cacheShortcuts(key.pkg, bubbles.map { it.shortcutId },
- UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)
- }
- }
-
- private fun uncache(bubbles: List<BubbleEntity>) {
- bubbles.groupBy { ShortcutKey(it.userId, it.packageName) }.forEach { (key, bubbles) ->
- launcherApps.uncacheShortcuts(key.pkg, bubbles.map { it.shortcutId },
- UserHandle.of(key.userId), LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
deleted file mode 100644
index bf163a230aff..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
+++ /dev/null
@@ -1,115 +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.
- */
-package com.android.systemui.bubbles.storage
-
-import com.android.internal.util.FastXmlSerializer
-import org.xmlpull.v1.XmlSerializer
-import java.io.IOException
-import android.util.Xml
-import com.android.internal.util.XmlUtils
-import org.xmlpull.v1.XmlPullParser
-import java.io.InputStream
-import java.io.OutputStream
-import java.nio.charset.StandardCharsets
-
-// TODO: handle version changes gracefully
-private const val CURRENT_VERSION = 1
-
-private const val TAG_BUBBLES = "bs"
-private const val ATTR_VERSION = "v"
-private const val TAG_BUBBLE = "bb"
-private const val ATTR_USER_ID = "uid"
-private const val ATTR_PACKAGE = "pkg"
-private const val ATTR_SHORTCUT_ID = "sid"
-private const val ATTR_KEY = "key"
-private const val ATTR_DESIRED_HEIGHT = "h"
-private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid"
-private const val ATTR_TITLE = "t"
-
-/**
- * Writes the bubbles in xml format into given output stream.
- */
-@Throws(IOException::class)
-fun writeXml(stream: OutputStream, bubbles: List<BubbleEntity>) {
- val serializer: XmlSerializer = FastXmlSerializer()
- serializer.setOutput(stream, StandardCharsets.UTF_8.name())
- serializer.startDocument(null, true)
- serializer.startTag(null, TAG_BUBBLES)
- serializer.attribute(null, ATTR_VERSION, CURRENT_VERSION.toString())
- bubbles.forEach { b -> writeXmlEntry(serializer, b) }
- serializer.endTag(null, TAG_BUBBLES)
- serializer.endDocument()
-}
-
-/**
- * Creates a xml entry for given bubble in following format:
- * ```
- * <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" key="my-key" />
- * ```
- */
-private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
- try {
- serializer.startTag(null, TAG_BUBBLE)
- serializer.attribute(null, ATTR_USER_ID, bubble.userId.toString())
- serializer.attribute(null, ATTR_PACKAGE, bubble.packageName)
- serializer.attribute(null, ATTR_SHORTCUT_ID, bubble.shortcutId)
- serializer.attribute(null, ATTR_KEY, bubble.key)
- serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString())
- serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString())
- bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) }
- serializer.endTag(null, TAG_BUBBLE)
- } catch (e: IOException) {
- throw RuntimeException(e)
- }
-}
-
-/**
- * Reads the bubbles from xml file.
- */
-fun readXml(stream: InputStream): List<BubbleEntity> {
- val bubbles = mutableListOf<BubbleEntity>()
- val parser: XmlPullParser = Xml.newPullParser()
- parser.setInput(stream, StandardCharsets.UTF_8.name())
- XmlUtils.beginDocument(parser, TAG_BUBBLES)
- val version = parser.getAttributeWithName(ATTR_VERSION)?.toInt()
- if (version != null && version == CURRENT_VERSION) {
- val outerDepth = parser.depth
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- bubbles.add(readXmlEntry(parser) ?: continue)
- }
- }
- return bubbles
-}
-
-private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? {
- while (parser.eventType != XmlPullParser.START_TAG) { parser.next() }
- return BubbleEntity(
- parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null,
- parser.getAttributeWithName(ATTR_PACKAGE) ?: return null,
- parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null,
- parser.getAttributeWithName(ATTR_KEY) ?: return null,
- parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null,
- parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null,
- parser.getAttributeWithName(ATTR_TITLE)
- )
-}
-
-private fun XmlPullParser.getAttributeWithName(name: String): String? {
- for (i in 0 until attributeCount) {
- if (getAttributeName(i) == name) return getAttributeValue(i)
- }
- return null
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index cb90b6114396..3aa462657637 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -38,7 +38,6 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.ViewMediatorCallback;
@@ -340,13 +339,6 @@ public class DependencyProvider {
return Choreographer.getInstance();
}
- /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
- @Provides
- @SysUISingleton
- static UiEventLogger provideUiEventLogger() {
- return new UiEventLoggerImpl();
- }
-
/** */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index c5dc8cccfdf4..53383d65e379 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -16,9 +16,18 @@
package com.android.systemui.dagger;
+import android.content.Context;
+import android.util.DisplayMetrics;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
+import com.android.wm.shell.animation.FlingAnimationUtils;
+
+import javax.inject.Singleton;
import dagger.Module;
+import dagger.Provides;
/**
* Supplies globally scoped instances that should be available in all versions of SystemUI
@@ -39,4 +48,22 @@ import dagger.Module;
FrameworkServicesModule.class,
GlobalConcurrencyModule.class})
public class GlobalModule {
+
+ // TODO(b/162923491): This should not be a singleton at all, the display metrics can change and
+ // callers should be creating a new builder on demand
+ @Singleton
+ @Provides
+ static FlingAnimationUtils.Builder provideFlingAnimationUtilsBuilder(
+ Context context) {
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ context.getDisplay().getMetrics(displayMetrics);
+ return new FlingAnimationUtils.Builder(displayMetrics);
+ }
+
+ /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
+ @Provides
+ @Singleton
+ static UiEventLogger provideUiEventLogger() {
+ return new UiEventLoggerImpl();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 00fdf55b28e0..d648c949ffc5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -52,7 +52,7 @@ public interface GlobalRootComponent {
WMComponent.Builder getWMComponentBuilder();
/**
- * Builder for a SysuiComponent.
+ * Builder for a SysUIComponent.
*/
SysUIComponent.Builder getSysUIComponent();
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 4bea0674e8bf..b94a68b2441d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -22,8 +22,17 @@ import com.android.systemui.InitController;
import com.android.systemui.SystemUIAppComponentFactory;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
+import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.InjectionInflationController;
+import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.onehanded.OneHanded;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -43,15 +52,41 @@ public interface SysUIComponent {
/**
* Builder for a SysUIComponent.
*/
+ @SysUISingleton
@Subcomponent.Builder
interface Builder {
@BindsInstance
- Builder setStubAPIClass(WMComponent.StubAPIClass stubAPIClass);
+ Builder setPip(Optional<Pip> p);
+
+ @BindsInstance
+ Builder setSplitScreen(Optional<SplitScreen> s);
+
+ @BindsInstance
+ Builder setOneHanded(Optional<OneHanded> o);
+
+ @BindsInstance
+ Builder setBubbles(Optional<Bubbles> b);
+
+ @BindsInstance
+ Builder setInputConsumerController(InputConsumerController i);
+
+ @BindsInstance
+ Builder setShellTaskOrganizer(ShellTaskOrganizer s);
+
+ @BindsInstance
+ Builder setShellDump(Optional<ShellDump> shellDump);
SysUIComponent build();
}
/**
+ * Initializes all the SysUI components.
+ */
+ default void init() {
+ // Do nothing
+ }
+
+ /**
* Provides a BootCompleteCache.
*/
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 1f6288a94ad4..c0013d8cb981 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -24,7 +24,6 @@ import com.android.systemui.SystemUI;
import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.accessibility.WindowMagnification;
import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.bubbles.dagger.BubbleModule;
import com.android.systemui.globalactions.GlobalActionsComponent;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -51,8 +50,7 @@ import dagger.multibindings.IntoMap;
/**
* SystemUI objects that are injectable should go here.
*/
-@Module(includes = {RecentsModule.class, StatusBarModule.class, BubbleModule.class,
- KeyguardModule.class})
+@Module(includes = {RecentsModule.class, StatusBarModule.class, KeyguardModule.class})
public abstract class SystemUIBinder {
/** Inject into AuthController. */
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 2c0b04fed810..7ca8e63bfae1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -33,6 +33,7 @@ import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -61,7 +62,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.wmshell.WMShellModule;
import javax.inject.Named;
@@ -74,9 +74,9 @@ import dagger.Provides;
* overridden by the System UI implementation.
*/
@Module(includes = {
- QSModule.class,
- WMShellModule.class
- })
+ MediaModule.class,
+ QSModule.class
+})
public abstract class SystemUIDefaultModule {
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 63d9a831b33f..780bb5b01103 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -16,6 +16,12 @@
package com.android.systemui.dagger;
+import android.app.INotificationManager;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
@@ -24,23 +30,35 @@ import com.android.systemui.assist.AssistModule;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.demomode.dagger.DemoModeModule;
import com.android.systemui.doze.dagger.DozeComponent;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.dagger.PowerModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
import com.android.systemui.settings.dagger.SettingsModule;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
+import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.tuner.dagger.TunerModule;
@@ -52,6 +70,10 @@ import com.android.systemui.util.settings.SettingsUtilModule;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.util.time.SystemClockImpl;
import com.android.systemui.volume.dagger.VolumeModule;
+import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.util.Optional;
import dagger.Binds;
import dagger.BindsOptionalOf;
@@ -128,4 +150,25 @@ public abstract class SystemUIModule {
@SysUISingleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
+
+ /** Provides Optional of BubbleManager */
+ @SysUISingleton
+ @Provides
+ static Optional<BubblesManager> provideBubblesManager(Context context,
+ Optional<Bubbles> bubblesOptional,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController, ShadeController shadeController,
+ ConfigurationController configurationController,
+ @Nullable IStatusBarService statusBarService, INotificationManager notificationManager,
+ NotificationInterruptStateProvider interruptionStateProvider,
+ ZenModeController zenModeController, NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManagerLegacy groupManager, NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline, SysUiState sysUiState, FeatureFlags featureFlags,
+ DumpManager dumpManager) {
+ return Optional.ofNullable(BubblesManager.create(context, bubblesOptional,
+ notificationShadeWindowController, statusBarStateController, shadeController,
+ configurationController, statusBarService, notificationManager,
+ interruptionStateProvider, zenModeController, notifUserManager,
+ groupManager, entryManager, notifPipeline, sysUiState, featureFlags, dumpManager));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index ad90eff3c969..8f3d8eaac2d3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -16,7 +16,17 @@
package com.android.systemui.dagger;
-import javax.inject.Inject;
+import com.android.systemui.shared.system.InputConsumerController;
+import com.android.systemui.wmshell.WMShellModule;
+import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.ShellInit;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.bubbles.Bubbles;
+import com.android.wm.shell.onehanded.OneHanded;
+import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.splitscreen.SplitScreen;
+
+import java.util.Optional;
import dagger.Subcomponent;
@@ -24,7 +34,7 @@ import dagger.Subcomponent;
* Dagger Subcomponent for WindowManager.
*/
@WMSingleton
-@Subcomponent(modules = {})
+@Subcomponent(modules = {WMShellModule.class})
public interface WMComponent {
/**
@@ -35,18 +45,40 @@ public interface WMComponent {
WMComponent build();
}
-
/**
- * Example class used for passing an API to SysUI from WMShell.
- *
- * TODO: Remove this once real WM classes are ready to go.
- **/
- @WMSingleton
- class StubAPIClass {
- @Inject
- StubAPIClass() {}
+ * Initializes all the WMShell components before starting any of the SystemUI components.
+ */
+ default void init() {
+ getShellInit().init();
}
- /** Create a StubAPIClass. */
- StubAPIClass createStubAPIClass();
+ // Gets the Shell init instance
+ @WMSingleton
+ ShellInit getShellInit();
+
+ // Gets the Shell dump instance
+ @WMSingleton
+ Optional<ShellDump> getShellDump();
+
+ // TODO(b/162923491): Refactor this out so Pip doesn't need to inject this
+ @WMSingleton
+ InputConsumerController getInputConsumerController();
+
+ // TODO(b/162923491): To be removed once Bubbles migrates over to the Shell
+ @WMSingleton
+ ShellTaskOrganizer getShellTaskOrganizer();
+
+ // TODO(b/162923491): We currently pass the instances through to SysUI, but that may change
+ // depending on the threading mechanism we go with
+ @WMSingleton
+ Optional<OneHanded> getOneHanded();
+
+ @WMSingleton
+ Optional<Pip> getPip();
+
+ @WMSingleton
+ Optional<SplitScreen> getSplitScreen();
+
+ @WMSingleton
+ Optional<Bubbles> getBubbles();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/RootView.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.java
index 5ebff097604b..e6c46c07fff8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/RootView.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/RootView.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.keyguard.dagger;
+package com.android.systemui.dagger.qualifiers;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 028870f3815e..ebfce661c9af 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -44,6 +44,8 @@ import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.plugins.SensorManagerPlugin;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -98,7 +100,8 @@ public class DozeSensors {
DozeSensors(Context context, AsyncSensorManager sensorManager,
DozeParameters dozeParameters, AmbientDisplayConfiguration config, WakeLock wakeLock,
Callback callback, Consumer<Boolean> proxCallback, DozeLog dozeLog,
- ProximitySensor proximitySensor, SecureSettings secureSettings) {
+ ProximitySensor proximitySensor, SecureSettings secureSettings,
+ AuthController authController) {
mContext = context;
mSensorManager = sensorManager;
mConfig = config;
@@ -152,9 +155,9 @@ public class DozeSensors {
dozeLog),
new TriggerSensor(
findSensorWithType(config.udfpsLongPressSensorType()),
- Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
- false /* settingDef */,
- true /* configured */,
+ "doze_pulse_on_auth",
+ true /* settingDef */,
+ authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()),
DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
true /* reports touch coordinates */,
true /* touchscreen */,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 45e5c614ea58..58e49f896931 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -131,7 +131,10 @@ public class DozeTriggers implements DozeMachine.Part {
DOZING_UPDATE_SENSOR_WAKE_LOCKSCREEN(440),
@UiEvent(doc = "Dozing updated because sensor was tapped.")
- DOZING_UPDATE_SENSOR_TAP(441);
+ DOZING_UPDATE_SENSOR_TAP(441),
+
+ @UiEvent(doc = "Dozing updated because on display auth was triggered from AOD.")
+ DOZING_UPDATE_AUTH_TRIGGERED(442);
private final int mId;
@@ -155,6 +158,7 @@ public class DozeTriggers implements DozeMachine.Part {
case 7: return DOZING_UPDATE_SENSOR_WAKEUP;
case 8: return DOZING_UPDATE_SENSOR_WAKE_LOCKSCREEN;
case 9: return DOZING_UPDATE_SENSOR_TAP;
+ case 10: return DOZING_UPDATE_AUTH_TRIGGERED;
default: return null;
}
}
@@ -177,7 +181,7 @@ public class DozeTriggers implements DozeMachine.Part {
mAllowPulseTriggers = true;
mDozeSensors = new DozeSensors(context, mSensorManager, dozeParameters,
config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
- secureSettings);
+ secureSettings, authController);
mUiModeManager = mContext.getSystemService(UiModeManager.class);
mDockManager = dockManager;
mProxCheck = proxCheck;
@@ -286,10 +290,11 @@ public class DozeTriggers implements DozeMachine.Part {
} else if (isPickup) {
gentleWakeUp(pulseReason);
} else if (isUdfpsLongPress) {
- gentleWakeUp(pulseReason);
+ requestPulse(DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS, true, null);
// Since the gesture won't be received by the UDFPS view, manually inject an
// event.
- mAuthController.onAodInterrupt((int) screenX, (int) screenY);
+ mAuthController.onAodInterrupt((int) screenX, (int) screenY,
+ rawValues[2] /* major */, rawValues[3] /* minor */);
} else {
mDozeHost.extendPulse(pulseReason);
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index b55b29a80410..a330be6449e2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -140,8 +140,14 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
d.setContentView(R.layout.shutdown_dialog);
d.setCancelable(false);
- int color = Utils.getColorAttrDefaultColor(mContext,
- com.android.systemui.R.attr.wallpaperTextColor);
+ int color;
+ if (mBlurUtils.supportsBlursOnWindows()) {
+ color = Utils.getColorAttrDefaultColor(mContext,
+ com.android.systemui.R.attr.wallpaperTextColor);
+ } else {
+ color = mContext.getResources().getColor(
+ com.android.systemui.R.color.global_actions_shutdown_ui_text);
+ }
ProgressBar bar = d.findViewById(R.id.progress);
bar.getIndeterminateDrawable().setTint(color);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 5f726cd1e1f9..37bcb163d6f3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -316,7 +316,8 @@ public class KeyguardSliceProvider extends SliceProvider implements
}
mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
mPendingIntent = PendingIntent.getActivity(getContext(), 0,
- new Intent(getContext(), KeyguardSliceProvider.class), 0);
+ new Intent(getContext(), KeyguardSliceProvider.class),
+ PendingIntent.FLAG_IMMUTABLE);
try {
//TODO(b/168778439): Remove this whole try catch. This is for debugging in dogfood.
mMediaManager.addCallback(this);
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 e3ee2a10821b..fff185b99a1e 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,18 @@ public class LogModule {
return buffer;
}
+ /** Provides a logging buffer for all logs related to privacy indicators in SystemUI. */
+ @Provides
+ @SysUISingleton
+ @PrivacyLog
+ public static LogBuffer providePrivacyLogBuffer(
+ LogcatEchoTracker bufferFilter,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer(("PrivacyLog"), 100, 10, bufferFilter);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Allows logging buffers to be tweaked via adb on debug builds but not on prod builds. */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
new file mode 100644
index 000000000000..e96e532f94bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PrivacyLog.java
@@ -0,0 +1,33 @@
+/*
+ * 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.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 privacy indicator-related messages. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface PrivacyLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
index 094ece27fc8e..6fb86504ec32 100644
--- a/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/KeyguardMediaController.kt
@@ -18,6 +18,7 @@ package com.android.systemui.media
import android.view.View
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.media.dagger.MediaModule.KEYGUARD
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.StatusBarState
@@ -25,6 +26,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.MediaHeaderView
import com.android.systemui.statusbar.phone.KeyguardBypassController
import javax.inject.Inject
+import javax.inject.Named
/**
* A class that controls the media notifications on the lock screen, handles its visibility and
@@ -32,7 +34,7 @@ import javax.inject.Inject
*/
@SysUISingleton
class KeyguardMediaController @Inject constructor(
- private val mediaHost: MediaHost,
+ @param:Named(KEYGUARD) private val mediaHost: MediaHost,
private val bypassController: KeyguardBypassController,
private val statusBarStateController: SysuiStatusBarStateController,
private val notifLockscreenUserManager: NotificationLockscreenUserManager
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index 1beb875af70c..5eb6687f0b81 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -143,6 +143,11 @@ class MediaCarouselController @Inject constructor(
if (newConfig == null) return
isRtl = newConfig.layoutDirection == View.LAYOUT_DIRECTION_RTL
}
+
+ override fun onUiModeChanged() {
+ // Only settings button needs to update for dark theme
+ inflateSettingsButton()
+ }
}
init {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
index d80aafb714d3..cb14f31abd16 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselScrollHandler.kt
@@ -282,10 +282,12 @@ class MediaCarouselScrollHandler(
scrollXAmount = -1 * relativePos
}
if (scrollXAmount != 0) {
+ val dx = if (isRtl) -scrollXAmount else scrollXAmount
+ val newScrollX = scrollView.relativeScrollX + dx
// Delay the scrolling since scrollView calls springback which cancels
// the animation again..
mainExecutor.execute {
- scrollView.smoothScrollBy(if (isRtl) -scrollXAmount else scrollXAmount, 0)
+ scrollView.smoothScrollTo(newScrollX, scrollView.scrollY)
}
}
val currentTranslation = scrollView.getContentTranslation()
@@ -553,4 +555,4 @@ class MediaCarouselScrollHandler(
}
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 5b096ea363b6..c18a6a45e286 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -354,7 +354,15 @@ public class MediaControlPanel {
final MediaController controller = getController();
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
+ // Guts label
+ boolean isDismissible = data.isClearable();
+ mViewHolder.getSettingsText().setText(isDismissible
+ ? R.string.controls_media_close_session
+ : R.string.controls_media_active_session);
+
// Dismiss
+ mViewHolder.getDismissLabel().setAlpha(isDismissible ? 1 : DISABLED_ALPHA);
+ mViewHolder.getDismiss().setEnabled(isDismissible);
mViewHolder.getDismiss().setOnClickListener(v -> {
if (mKey != null) {
closeGuts();
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 6f6ee4c8091d..c6ed9c096544 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -641,7 +641,8 @@ class MediaDataManager(
// Move to resume key (aka package name) if that key doesn't already exist.
val resumeAction = getResumeMediaAction(removed.resumeAction!!)
val updated = removed.copy(token = null, actions = listOf(resumeAction),
- actionsToShowInCompact = listOf(0), active = false, resumption = true)
+ actionsToShowInCompact = listOf(0), active = false, resumption = true,
+ isClearable = true)
val pkg = removed.packageName
val migrate = mediaEntries.put(pkg, updated) == null
// Notify listeners of "new" controls when migrating or removed and update when not
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
index ce184aa23a57..857c50fc8d32 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHost.kt
@@ -11,7 +11,7 @@ import com.android.systemui.util.animation.UniqueObjectHostView
import java.util.Objects
import javax.inject.Inject
-class MediaHost @Inject constructor(
+class MediaHost constructor(
private val state: MediaHostStateHolder,
private val mediaHierarchyManager: MediaHierarchyManager,
private val mediaDataManager: MediaDataManager,
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
index 51dbfa733541..ce72991d01c0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaTimeoutListener.kt
@@ -46,7 +46,7 @@ class MediaTimeoutListener @Inject constructor(
/**
* Callback representing that a media object is now expired:
* @param token Media session unique identifier
- * @param pauseTimeuot True when expired for {@code PAUSED_MEDIA_TIMEOUT}
+ * @param pauseTimeout True when expired for {@code PAUSED_MEDIA_TIMEOUT}
*/
lateinit var timeoutCallback: (String, Boolean) -> Unit
@@ -57,11 +57,10 @@ class MediaTimeoutListener @Inject constructor(
// Having an old key means that we're migrating from/to resumption. We should update
// the old listener to make sure that events will be dispatched to the new location.
val migrating = oldKey != null && key != oldKey
- var wasPlaying = false
if (migrating) {
val reusedListener = mediaListeners.remove(oldKey)
if (reusedListener != null) {
- wasPlaying = reusedListener.playing ?: false
+ val wasPlaying = reusedListener.playing ?: false
if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
reusedListener.mediaData = data
reusedListener.key = key
@@ -159,9 +158,8 @@ class MediaTimeoutListener @Inject constructor(
Log.v(TAG, "Execute timeout for $key")
}
timedOut = true
- if (dispatchEvents) {
- timeoutCallback(key, timedOut)
- }
+ // this event is async, so it's safe even when `dispatchEvents` is false
+ timeoutCallback(key, timedOut)
}, PAUSED_MEDIA_TIMEOUT)
} else {
expireMediaTimeout(key, "playback started - $state, $key")
diff --git a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
index 666a6038a8b6..16327bd9064a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/PlayerViewHolder.kt
@@ -60,8 +60,10 @@ class PlayerViewHolder private constructor(itemView: View) {
val action4 = itemView.requireViewById<ImageButton>(R.id.action4)
// Settings screen
+ val settingsText = itemView.requireViewById<TextView>(R.id.remove_text)
val cancel = itemView.requireViewById<View>(R.id.cancel)
- val dismiss = itemView.requireViewById<View>(R.id.dismiss)
+ val dismiss = itemView.requireViewById<ViewGroup>(R.id.dismiss)
+ val dismissLabel = dismiss.getChildAt(0)
val settings = itemView.requireViewById<View>(R.id.settings)
init {
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index 9e326aaec3c1..c8244589ce44 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -91,9 +91,9 @@ class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: R
}
private var playbackState: PlaybackState? = null
private var callback = object : MediaController.Callback() {
- override fun onPlaybackStateChanged(state: PlaybackState) {
+ override fun onPlaybackStateChanged(state: PlaybackState?) {
playbackState = state
- if (PlaybackState.STATE_NONE.equals(playbackState)) {
+ if (playbackState == null || PlaybackState.STATE_NONE.equals(playbackState)) {
clearController()
} else {
checkIfPollingNeeded()
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
new file mode 100644
index 000000000000..57ac9dfb52cd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaModule.java
@@ -0,0 +1,66 @@
+/*
+ * 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.media.dagger;
+
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.media.MediaDataManager;
+import com.android.systemui.media.MediaHierarchyManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.media.MediaHostStatesManager;
+
+import javax.inject.Named;
+
+import dagger.Module;
+import dagger.Provides;
+
+/** Dagger module for the media package. */
+@Module
+public interface MediaModule {
+ String QS_PANEL = "media_qs_panel";
+ String QUICK_QS_PANEL = "media_quick_qs_panel";
+ String KEYGUARD = "media_keyguard";
+
+ /** */
+ @Provides
+ @SysUISingleton
+ @Named(QS_PANEL)
+ static MediaHost providesQSMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
+ @Named(QUICK_QS_PANEL)
+ static MediaHost providesQuickQSMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
+ @Named(KEYGUARD)
+ static MediaHost providesKeyguardMediaHost(MediaHost.MediaHostStateHolder stateHolder,
+ MediaHierarchyManager hierarchyManager, MediaDataManager dataManager,
+ MediaHostStatesManager statesManager) {
+ return new MediaHost(stateHolder, hierarchyManager, dataManager, statesManager);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index d1630ebe8dc8..0d5faff65aab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -22,6 +22,7 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.text.SpannableString;
+import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.View;
@@ -45,6 +46,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private ViewGroup mConnectedItem;
+ private boolean mInclueDynamicGroup;
public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
@@ -61,9 +63,21 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
final int size = mController.getMediaDevices().size();
- if (mController.isZeroMode() && position == size) {
+ if (position == size && mController.isZeroMode()) {
viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
true /* bottomMargin */);
+ } else if (mInclueDynamicGroup) {
+ if (position == 0) {
+ viewHolder.onBind(CUSTOMIZED_ITEM_DYNAMIC_GROUP, true /* topMargin */,
+ false /* bottomMargin */);
+ } else {
+ // When group item is added at the first(position == 0), devices will be added from
+ // the second item(position == 1). It means that the index of device list starts
+ // from "position - 1".
+ viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices()))
+ .get(position - 1),
+ false /* topMargin */, position == size /* bottomMargin */);
+ }
} else if (position < size) {
viewHolder.onBind(((List<MediaDevice>) (mController.getMediaDevices())).get(position),
position == 0 /* topMargin */, position == (size - 1) /* bottomMargin */);
@@ -74,8 +88,9 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public int getItemCount() {
- if (mController.isZeroMode()) {
- // Add extra one for "pair new"
+ mInclueDynamicGroup = mController.getSelectedMediaDevice().size() > 1;
+ if (mController.isZeroMode() || mInclueDynamicGroup) {
+ // Add extra one for "pair new" or dynamic group
return mController.getMediaDevices().size() + 1;
}
return mController.getMediaDevices().size();
@@ -107,36 +122,47 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
super.onBind(device, topMargin, bottomMargin);
- final boolean currentlyConnected = isCurrentlyConnected(device);
+ final boolean currentlyConnected = !mInclueDynamicGroup && isCurrentlyConnected(device);
if (currentlyConnected) {
- mConnectedItem = mFrameLayout;
+ mConnectedItem = mContainerLayout;
+ }
+ mBottomDivider.setVisibility(View.GONE);
+ mCheckBox.setVisibility(View.GONE);
+ if (currentlyConnected && mController.isActiveRemoteDevice(device)) {
+ // Init active device layout
+ mDivider.setVisibility(View.VISIBLE);
+ mDivider.setTransitionAlpha(1);
+ mAddIcon.setVisibility(View.VISIBLE);
+ mAddIcon.setTransitionAlpha(1);
+ mAddIcon.setOnClickListener(v -> onEndItemClick());
+ } else {
+ // Init non-active device layout
+ mDivider.setVisibility(View.GONE);
+ mAddIcon.setVisibility(View.GONE);
}
if (mController.isTransferring()) {
if (device.getState() == MediaDeviceState.STATE_CONNECTING
&& !mController.hasAdjustVolumeUserRestriction()) {
- setTwoLineLayout(device, null /* title */, true /* bFocused */,
- false /* showSeekBar*/, true /* showProgressBar */,
- false /* showSubtitle */);
+ setTwoLineLayout(device, true /* bFocused */, false /* showSeekBar*/,
+ true /* showProgressBar */, false /* showSubtitle */);
} else {
setSingleLineLayout(getItemTitle(device), false /* bFocused */);
}
} else {
// Set different layout for each device
if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
- setTwoLineLayout(device, null /* title */, false /* bFocused */,
- false /* showSeekBar*/, false /* showProgressBar */,
+ setTwoLineLayout(device, false /* bFocused */,
+ false /* showSeekBar */, false /* showProgressBar */,
true /* showSubtitle */);
mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
- mFrameLayout.setOnClickListener(v -> onItemClick(v, device));
- } else if (!mController.hasAdjustVolumeUserRestriction()
- && currentlyConnected) {
- setTwoLineLayout(device, null /* title */, true /* bFocused */,
- true /* showSeekBar*/, false /* showProgressBar */,
- false /* showSubtitle */);
+ mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
+ } else if (!mController.hasAdjustVolumeUserRestriction() && currentlyConnected) {
+ setTwoLineLayout(device, true /* bFocused */, true /* showSeekBar */,
+ false /* showProgressBar */, false /* showSubtitle */);
initSeekbar(device);
} else {
setSingleLineLayout(getItemTitle(device), false /* bFocused */);
- mFrameLayout.setOnClickListener(v -> onItemClick(v, device));
+ mContainerLayout.setOnClickListener(v -> onItemClick(v, device));
}
}
}
@@ -145,13 +171,33 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
super.onBind(customizedItem, topMargin, bottomMargin);
if (customizedItem == CUSTOMIZED_ITEM_PAIR_NEW) {
+ mCheckBox.setVisibility(View.GONE);
+ mDivider.setVisibility(View.GONE);
+ mAddIcon.setVisibility(View.GONE);
+ mBottomDivider.setVisibility(View.GONE);
setSingleLineLayout(mContext.getText(R.string.media_output_dialog_pairing_new),
false /* bFocused */);
final Drawable d = mContext.getDrawable(R.drawable.ic_add);
d.setColorFilter(new PorterDuffColorFilter(
Utils.getColorAccentDefaultColor(mContext), PorterDuff.Mode.SRC_IN));
mTitleIcon.setImageDrawable(d);
- mFrameLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
+ mContainerLayout.setOnClickListener(v -> onItemClick(CUSTOMIZED_ITEM_PAIR_NEW));
+ } else if (customizedItem == CUSTOMIZED_ITEM_DYNAMIC_GROUP) {
+ mConnectedItem = mContainerLayout;
+ mBottomDivider.setVisibility(View.GONE);
+ mCheckBox.setVisibility(View.GONE);
+ mDivider.setVisibility(View.VISIBLE);
+ mDivider.setTransitionAlpha(1);
+ mAddIcon.setVisibility(View.VISIBLE);
+ mAddIcon.setTransitionAlpha(1);
+ mAddIcon.setOnClickListener(v -> onEndItemClick());
+ mTitleIcon.setImageDrawable(getSpeakerDrawable());
+ final CharSequence sessionName = mController.getSessionName();
+ final CharSequence title = TextUtils.isEmpty(sessionName)
+ ? mContext.getString(R.string.media_output_dialog_group) : sessionName;
+ setTwoLineLayout(title, true /* bFocused */, true /* showSeekBar */,
+ false /* showProgressBar */, false /* showSubtitle */);
+ initSessionSeekbar();
}
}
@@ -173,5 +219,9 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
mController.launchBluetoothPairing();
}
}
+
+ private void onEndItemClick() {
+ mController.launchMediaOutputGroupDialog();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 2d3e77db1ea3..f1d4804aa622 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -19,13 +19,18 @@ package com.android.systemui.media.dialog;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.FrameLayout;
+import android.widget.CheckBox;
import android.widget.ImageView;
+import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
@@ -34,6 +39,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.media.MediaDevice;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -44,19 +50,18 @@ import com.android.systemui.R;
public abstract class MediaOutputBaseAdapter extends
RecyclerView.Adapter<MediaOutputBaseAdapter.MediaDeviceBaseViewHolder> {
- private static final String FONT_SELECTED_TITLE = "sans-serif-medium";
- private static final String FONT_TITLE = "sans-serif";
-
static final int CUSTOMIZED_ITEM_PAIR_NEW = 1;
+ static final int CUSTOMIZED_ITEM_GROUP = 2;
+ static final int CUSTOMIZED_ITEM_DYNAMIC_GROUP = 3;
final MediaOutputController mController;
- private boolean mIsDragging;
private int mMargin;
private boolean mIsAnimating;
Context mContext;
View mHolderView;
+ boolean mIsDragging;
public MediaOutputBaseAdapter(MediaOutputController controller) {
mController = controller;
@@ -99,27 +104,33 @@ public abstract class MediaOutputBaseAdapter extends
private static final int ANIM_DURATION = 200;
- final FrameLayout mFrameLayout;
+ final LinearLayout mContainerLayout;
final TextView mTitleText;
final TextView mTwoLineTitleText;
final TextView mSubTitleText;
final ImageView mTitleIcon;
- final ImageView mEndIcon;
+ final ImageView mAddIcon;
final ProgressBar mProgressBar;
final SeekBar mSeekBar;
final RelativeLayout mTwoLineLayout;
+ final View mDivider;
+ final View mBottomDivider;
+ final CheckBox mCheckBox;
MediaDeviceBaseViewHolder(View view) {
super(view);
- mFrameLayout = view.requireViewById(R.id.device_container);
+ mContainerLayout = view.requireViewById(R.id.device_container);
mTitleText = view.requireViewById(R.id.title);
mSubTitleText = view.requireViewById(R.id.subtitle);
mTwoLineLayout = view.requireViewById(R.id.two_line_layout);
mTwoLineTitleText = view.requireViewById(R.id.two_line_title);
mTitleIcon = view.requireViewById(R.id.title_icon);
- mEndIcon = view.requireViewById(R.id.end_icon);
mProgressBar = view.requireViewById(R.id.volume_indeterminate_progress);
mSeekBar = view.requireViewById(R.id.volume_seekbar);
+ mDivider = view.requireViewById(R.id.end_divider);
+ mBottomDivider = view.requireViewById(R.id.bottom_divider);
+ mAddIcon = view.requireViewById(R.id.add_icon);
+ mCheckBox = view.requireViewById(R.id.check_box);
}
void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
@@ -132,11 +143,11 @@ public abstract class MediaOutputBaseAdapter extends
}
private void setMargin(boolean topMargin, boolean bottomMargin) {
- ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mFrameLayout
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) mContainerLayout
.getLayoutParams();
params.topMargin = topMargin ? mMargin : 0;
params.bottomMargin = bottomMargin ? mMargin : 0;
- mFrameLayout.setLayoutParams(params);
+ mContainerLayout.setLayoutParams(params);
}
void setSingleLineLayout(CharSequence title, boolean bFocused) {
@@ -146,13 +157,26 @@ public abstract class MediaOutputBaseAdapter extends
mTitleText.setTranslationY(0);
mTitleText.setText(title);
if (bFocused) {
- mTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
+ mTitleText.setTypeface(Typeface.create(mContext.getString(
+ com.android.internal.R.string.config_headlineFontFamilyMedium),
+ Typeface.NORMAL));
} else {
- mTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
+ mTitleText.setTypeface(Typeface.create(mContext.getString(
+ com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL));
}
}
- void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
+ void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
+ boolean showProgressBar, boolean showSubtitle) {
+ setTwoLineLayout(device, null, bFocused, showSeekBar, showProgressBar, showSubtitle);
+ }
+
+ void setTwoLineLayout(CharSequence title, boolean bFocused, boolean showSeekBar,
+ boolean showProgressBar, boolean showSubtitle) {
+ setTwoLineLayout(null, title, bFocused, showSeekBar, showProgressBar, showSubtitle);
+ }
+
+ private void setTwoLineLayout(MediaDevice device, CharSequence title, boolean bFocused,
boolean showSeekBar, boolean showProgressBar, boolean showSubtitle) {
mTitleText.setVisibility(View.GONE);
mTwoLineLayout.setVisibility(View.VISIBLE);
@@ -168,18 +192,21 @@ public abstract class MediaOutputBaseAdapter extends
}
if (bFocused) {
- mTwoLineTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE,
+ mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
+ com.android.internal.R.string.config_headlineFontFamilyMedium),
Typeface.NORMAL));
} else {
- mTwoLineTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
+ mTwoLineTitleText.setTypeface(Typeface.create(mContext.getString(
+ com.android.internal.R.string.config_headlineFontFamily), Typeface.NORMAL));
}
}
void initSeekbar(MediaDevice device) {
mSeekBar.setMax(device.getMaxVolume());
mSeekBar.setMin(0);
- if (mSeekBar.getProgress() != device.getCurrentVolume()) {
- mSeekBar.setProgress(device.getCurrentVolume());
+ final int currentVolume = device.getCurrentVolume();
+ if (mSeekBar.getProgress() != currentVolume) {
+ mSeekBar.setProgress(currentVolume);
}
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
@@ -202,6 +229,34 @@ public abstract class MediaOutputBaseAdapter extends
});
}
+ void initSessionSeekbar() {
+ mSeekBar.setMax(mController.getSessionVolumeMax());
+ mSeekBar.setMin(0);
+ final int currentVolume = mController.getSessionVolume();
+ if (mSeekBar.getProgress() != currentVolume) {
+ mSeekBar.setProgress(currentVolume);
+ }
+ mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (!fromUser) {
+ return;
+ }
+ mController.adjustSessionVolume(progress);
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mIsDragging = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mIsDragging = false;
+ }
+ });
+ }
+
void playSwitchingAnim(@NonNull View from, @NonNull View to) {
final float delta = (float) (mContext.getResources().getDimensionPixelSize(
R.dimen.media_output_dialog_title_anim_y_delta));
@@ -213,7 +268,9 @@ public abstract class MediaOutputBaseAdapter extends
}
mIsAnimating = true;
// Animation for title text
- toTitleText.setTypeface(Typeface.create(FONT_SELECTED_TITLE, Typeface.NORMAL));
+ toTitleText.setTypeface(Typeface.create(mContext.getString(
+ com.android.internal.R.string.config_headlineFontFamilyMedium),
+ Typeface.NORMAL));
toTitleText.animate()
.setDuration(ANIM_DURATION)
.translationY(-delta)
@@ -234,7 +291,9 @@ public abstract class MediaOutputBaseAdapter extends
public void onAnimationEnd(Animator animation) {
final TextView fromTitleText = from.requireViewById(
R.id.two_line_title);
- fromTitleText.setTypeface(Typeface.create(FONT_TITLE, Typeface.NORMAL));
+ fromTitleText.setTypeface(Typeface.create(mContext.getString(
+ com.android.internal.R.string.config_headlineFontFamily),
+ Typeface.NORMAL));
fromTitleText.animate()
.setDuration(ANIM_DURATION)
.translationY(delta)
@@ -249,5 +308,15 @@ public abstract class MediaOutputBaseAdapter extends
}
});
}
+
+ Drawable getSpeakerDrawable() {
+ final Drawable drawable = mContext.getDrawable(R.drawable.ic_speaker_group_black_24dp)
+ .mutate();
+ final ColorStateList list = mContext.getResources().getColorStateList(
+ R.color.advanced_icon_color, mContext.getTheme());
+ drawable.setColorFilter(new PorterDuffColorFilter(list.getDefaultColor(),
+ PorterDuff.Mode.SRC_IN));
+ return BluetoothUtils.buildAdvancedDrawable(mContext, drawable);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index caef536961f1..78939dffb4fb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -119,6 +119,8 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
// Init device list
mDevicesRecyclerView.setLayoutManager(mLayoutManager);
mDevicesRecyclerView.setAdapter(mAdapter);
+ // Init header icon
+ mHeaderIcon.setOnClickListener(v -> onHeaderIconClick());
// Init bottom buttons
mDoneButton.setOnClickListener(v -> dismiss());
mStopButton.setOnClickListener(v -> {
@@ -218,4 +220,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
dismiss();
}
}
+
+ void onHeaderIconClick() {
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index b1f1bda25961..451bd42bd053 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -16,13 +16,12 @@
package com.android.systemui.media.dialog;
+import android.app.Notification;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.media.MediaMetadata;
import android.media.MediaRoute2Info;
import android.media.RoutingSessionInfo;
@@ -49,6 +48,8 @@ import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.ShadeController;
import java.util.ArrayList;
@@ -61,7 +62,7 @@ import javax.inject.Inject;
/**
* Controller for media output dialog
*/
-public class MediaOutputController implements LocalMediaManager.DeviceCallback{
+public class MediaOutputController implements LocalMediaManager.DeviceCallback {
private static final String TAG = "MediaOutputController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -71,6 +72,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
private final MediaSessionManager mMediaSessionManager;
private final ShadeController mShadeController;
private final ActivityStarter mActivityStarter;
+ private final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
+ private final boolean mAboveStatusbar;
+ private final NotificationEntryManager mNotificationEntryManager;
@VisibleForTesting
final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@@ -82,13 +86,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
@Inject
public MediaOutputController(@NonNull Context context, String packageName,
- MediaSessionManager mediaSessionManager, LocalBluetoothManager
- lbm, ShadeController shadeController, ActivityStarter starter) {
+ boolean aboveStatusbar, MediaSessionManager mediaSessionManager, LocalBluetoothManager
+ lbm, ShadeController shadeController, ActivityStarter starter,
+ NotificationEntryManager notificationEntryManager) {
mContext = context;
mPackageName = packageName;
mMediaSessionManager = mediaSessionManager;
mShadeController = shadeController;
mActivityStarter = starter;
+ mAboveStatusbar = aboveStatusbar;
+ mNotificationEntryManager = notificationEntryManager;
InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
}
@@ -194,7 +201,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
if (DEBUG) {
Log.d(TAG, "Media meta data does not contain icon information");
}
- return getPackageIcon();
+ return getNotificationIcon();
}
IconCompat getDeviceIconCompat(MediaDevice device) {
@@ -210,24 +217,20 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
return BluetoothUtils.createIconWithDrawable(drawable);
}
- private IconCompat getPackageIcon() {
+ IconCompat getNotificationIcon() {
if (TextUtils.isEmpty(mPackageName)) {
return null;
}
- try {
- final Drawable drawable = mContext.getPackageManager().getApplicationIcon(mPackageName);
- if (drawable instanceof BitmapDrawable) {
- return IconCompat.createWithBitmap(((BitmapDrawable) drawable).getBitmap());
- }
- final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(bitmap);
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
- return IconCompat.createWithBitmap(bitmap);
- } catch (PackageManager.NameNotFoundException e) {
- if (DEBUG) {
- Log.e(TAG, "Package is not found. Unable to get package icon.");
+ for (NotificationEntry entry
+ : mNotificationEntryManager.getActiveNotificationsForCurrentUser()) {
+ final Notification notification = entry.getSbn().getNotification();
+ if (notification.hasMediaSession()
+ && TextUtils.equals(entry.getSbn().getPackageName(), mPackageName)) {
+ final Icon icon = notification.getLargeIcon();
+ if (icon == null) {
+ break;
+ }
+ return IconCompat.createFromIcon(icon);
}
}
return null;
@@ -271,6 +274,42 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
mMediaDevices.addAll(targetMediaDevices);
}
+ List<MediaDevice> getGroupMediaDevices() {
+ final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
+ final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
+ if (mGroupMediaDevices.isEmpty()) {
+ mGroupMediaDevices.addAll(selectedDevices);
+ mGroupMediaDevices.addAll(selectableDevices);
+ return mGroupMediaDevices;
+ }
+ // To keep the same list order
+ final Collection<MediaDevice> sourceDevices = new ArrayList<>();
+ final Collection<MediaDevice> targetMediaDevices = new ArrayList<>();
+ sourceDevices.addAll(selectedDevices);
+ sourceDevices.addAll(selectableDevices);
+ for (MediaDevice originalDevice : mGroupMediaDevices) {
+ for (MediaDevice newDevice : sourceDevices) {
+ if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) {
+ targetMediaDevices.add(newDevice);
+ sourceDevices.remove(newDevice);
+ break;
+ }
+ }
+ }
+ // Add new devices at the end of list if necessary
+ if (!sourceDevices.isEmpty()) {
+ targetMediaDevices.addAll(sourceDevices);
+ }
+ mGroupMediaDevices.clear();
+ mGroupMediaDevices.addAll(targetMediaDevices);
+
+ return mGroupMediaDevices;
+ }
+
+ void resetGroupMediaDevices() {
+ mGroupMediaDevices.clear();
+ }
+
void connectDevice(MediaDevice device) {
ThreadUtils.postOnBackgroundThread(() -> {
mLocalMediaManager.connectDevice(device);
@@ -309,15 +348,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
return mLocalMediaManager.getDeselectableMediaDevice();
}
- boolean isDeviceIncluded(Collection<MediaDevice> deviceCollection, MediaDevice targetDevice) {
- for (MediaDevice device : deviceCollection) {
- if (TextUtils.equals(device.getId(), targetDevice.getId())) {
- return true;
- }
- }
- return false;
- }
-
void adjustSessionVolume(String sessionId, int volume) {
mLocalMediaManager.adjustSessionVolume(sessionId, volume);
}
@@ -407,6 +437,16 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback{
mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true);
}
+ void launchMediaOutputDialog() {
+ mCallback.dismissDialog();
+ new MediaOutputDialog(mContext, mAboveStatusbar, this);
+ }
+
+ void launchMediaOutputGroupDialog() {
+ mCallback.dismissDialog();
+ new MediaOutputGroupDialog(mContext, mAboveStatusbar, this);
+ }
+
boolean isActiveRemoteDevice(@NonNull MediaDevice device) {
final List<String> features = device.getFeatures();
return (features.contains(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 4cdca4cbcf1e..7d1a7ced7472 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.media.session.MediaSessionManager
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.phone.ShadeController
import javax.inject.Inject
@@ -31,7 +32,8 @@ class MediaOutputDialogFactory @Inject constructor(
private val mediaSessionManager: MediaSessionManager,
private val lbm: LocalBluetoothManager?,
private val shadeController: ShadeController,
- private val starter: ActivityStarter
+ private val starter: ActivityStarter,
+ private val notificationEntryManager: NotificationEntryManager
) {
companion object {
var mediaOutputDialog: MediaOutputDialog? = null
@@ -40,9 +42,8 @@ class MediaOutputDialogFactory @Inject constructor(
/** Creates a [MediaOutputDialog] for the given package. */
fun create(packageName: String, aboveStatusBar: Boolean) {
mediaOutputDialog?.dismiss()
-
- mediaOutputDialog = MediaOutputController(context, packageName, mediaSessionManager, lbm,
- shadeController, starter).run {
+ mediaOutputDialog = MediaOutputController(context, packageName, aboveStatusBar,
+ mediaSessionManager, lbm, shadeController, starter, notificationEntryManager).run {
MediaOutputDialog(context, aboveStatusBar, this) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
new file mode 100644
index 000000000000..24e076bb22f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
@@ -0,0 +1,177 @@
+/*
+ * 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.media.dialog;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
+
+import java.util.List;
+
+/**
+ * Adapter for media output dynamic group dialog.
+ */
+public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
+
+ private static final String TAG = "MediaOutputGroupAdapter";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final List<MediaDevice> mGroupMediaDevices;
+
+ public MediaOutputGroupAdapter(MediaOutputController controller) {
+ super(controller);
+ mGroupMediaDevices = controller.getGroupMediaDevices();
+ }
+
+ @Override
+ public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
+ int viewType) {
+ super.onCreateViewHolder(viewGroup, viewType);
+
+ return new GroupViewHolder(mHolderView);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
+ // Add "Group"
+ if (position == 0) {
+ viewHolder.onBind(CUSTOMIZED_ITEM_GROUP, true /* topMargin */,
+ false /* bottomMargin */);
+ return;
+ }
+ // Add available devices
+ final int newPosition = position - 1;
+ final int size = mGroupMediaDevices.size();
+ if (newPosition < size) {
+ viewHolder.onBind(mGroupMediaDevices.get(newPosition), false /* topMargin */,
+ newPosition == (size - 1) /* bottomMargin */);
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Incorrect position: " + position);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ // Require extra item for group volume operation
+ return mGroupMediaDevices.size() + 1;
+ }
+
+ @Override
+ CharSequence getItemTitle(MediaDevice device) {
+ return super.getItemTitle(device);
+ }
+
+ class GroupViewHolder extends MediaDeviceBaseViewHolder {
+
+ GroupViewHolder(View view) {
+ super(view);
+ }
+
+ @Override
+ void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin) {
+ super.onBind(device, topMargin, bottomMargin);
+ mDivider.setVisibility(View.GONE);
+ mAddIcon.setVisibility(View.GONE);
+ mBottomDivider.setVisibility(View.GONE);
+ mCheckBox.setVisibility(View.VISIBLE);
+ mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ onCheckBoxClicked(isChecked, device);
+ });
+ setTwoLineLayout(device, false /* bFocused */, true /* showSeekBar */,
+ false /* showProgressBar */, false /* showSubtitle*/);
+ initSeekbar(device);
+ final List<MediaDevice> selectedDevices = mController.getSelectedMediaDevice();
+ if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+ mCheckBox.setButtonDrawable(R.drawable.ic_check_box);
+ mCheckBox.setChecked(false);
+ mCheckBox.setEnabled(true);
+ } else if (isDeviceIncluded(selectedDevices, device)) {
+ if (selectedDevices.size() == 1 || !isDeviceIncluded(
+ mController.getDeselectableMediaDevice(), device)) {
+ mCheckBox.setButtonDrawable(getDisabledCheckboxDrawable());
+ mCheckBox.setChecked(true);
+ mCheckBox.setEnabled(false);
+ } else {
+ mCheckBox.setButtonDrawable(R.drawable.ic_check_box);
+ mCheckBox.setChecked(true);
+ mCheckBox.setEnabled(true);
+ }
+ }
+ }
+
+ @Override
+ void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
+ super.onBind(customizedItem, topMargin, bottomMargin);
+ if (customizedItem == CUSTOMIZED_ITEM_GROUP) {
+ setTwoLineLayout(mContext.getText(R.string.media_output_dialog_group),
+ true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */,
+ false /* showSubtitle*/);
+ mTitleIcon.setImageDrawable(getSpeakerDrawable());
+ mBottomDivider.setVisibility(View.VISIBLE);
+ mCheckBox.setVisibility(View.GONE);
+ mDivider.setVisibility(View.GONE);
+ mAddIcon.setVisibility(View.GONE);
+ initSessionSeekbar();
+ }
+ }
+
+ private void onCheckBoxClicked(boolean isChecked, MediaDevice device) {
+ if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
+ mController.addDeviceToPlayMedia(device);
+ } else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(),
+ device)) {
+ mController.removeDeviceFromPlayMedia(device);
+ }
+ }
+
+ private Drawable getDisabledCheckboxDrawable() {
+ final Drawable drawable = mContext.getDrawable(R.drawable.ic_check_box_blue_24dp)
+ .mutate();
+ final Bitmap checkbox = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(checkbox);
+ TypedValue value = new TypedValue();
+ mContext.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true);
+ drawable.setAlpha((int) (value.getFloat() * 255));
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return drawable;
+ }
+
+ private boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
+ for (MediaDevice device : deviceList) {
+ if (TextUtils.equals(device.getId(), targetDevice.getId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
new file mode 100644
index 000000000000..407930492fbe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
@@ -0,0 +1,88 @@
+/*
+ * 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.media.dialog;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.core.graphics.drawable.IconCompat;
+
+import com.android.systemui.R;
+
+/**
+ * Dialog for media output group.
+ */
+public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
+
+ MediaOutputGroupDialog(Context context, boolean aboveStatusbar, MediaOutputController
+ mediaOutputController) {
+ super(context, mediaOutputController);
+ mMediaOutputController.resetGroupMediaDevices();
+ mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
+ if (!aboveStatusbar) {
+ getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ }
+ show();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ int getHeaderIconRes() {
+ return R.drawable.ic_arrow_back;
+ }
+
+ @Override
+ IconCompat getHeaderIcon() {
+ return null;
+ }
+
+ @Override
+ int getHeaderIconSize() {
+ return mContext.getResources().getDimensionPixelSize(
+ R.dimen.media_output_dialog_header_back_icon_size);
+ }
+
+ @Override
+ CharSequence getHeaderText() {
+ return mContext.getString(R.string.media_output_dialog_add_output);
+ }
+
+ @Override
+ CharSequence getHeaderSubtitle() {
+ final int size = mMediaOutputController.getSelectedMediaDevice().size();
+ if (size == 1) {
+ return mContext.getText(R.string.media_output_dialog_single_device);
+ }
+ return mContext.getString(R.string.media_output_dialog_multiple_devices, size);
+ }
+
+ @Override
+ int getStopButtonVisibility() {
+ return View.VISIBLE;
+ }
+
+ @Override
+ void onHeaderIconClick() {
+ mMediaOutputController.launchMediaOutputDialog();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 18cc746666d8..f9982d04e04b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -137,7 +137,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
&& (properties.getKeyset().contains(
SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD)
|| properties.getKeyset().contains(
- SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL))) {
+ SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL)
+ || properties.getKeyset().contains(
+ SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME))) {
updateMLModelState();
}
}
@@ -483,14 +485,15 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
private void updateMLModelState() {
boolean newState = mIsEnabled && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.USE_BACK_GESTURE_ML_MODEL, false);
-
if (newState == mUseMLModel) {
return;
}
if (newState) {
+ String mlModelName = DeviceConfig.getString(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME, "backgesture");
mBackGestureTfClassifierProvider = SystemUIFactory.getInstance()
- .createBackGestureTfClassifierProvider(mContext.getAssets());
+ .createBackGestureTfClassifierProvider(mContext.getAssets(), mlModelName);
mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
if (mBackGestureTfClassifierProvider.isActive()) {
@@ -540,21 +543,22 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa
boolean withinRange = false;
float results = -1;
+ // Disallow if we are in the bottom gesture area
+ if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ return false;
+ }
+ // If the point is way too far (twice the margin), it is
+ // not interesting to us for logging purposes, nor we
+ // should process it. Simply return false and keep
+ // mLogGesture = false.
+ if (x > 2 * (mEdgeWidthLeft + mLeftInset)
+ && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
+ return false;
+ }
+
if (mUseMLModel && (results = getBackGesturePredictionsCategory(x, y)) != -1) {
withinRange = results == 1 ? true : false;
} else {
- // Disallow if we are in the bottom gesture area
- if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
- return false;
- }
- // If the point is way too far (twice the margin), it is
- // not interesting to us for logging purposes, nor we
- // should process it. Simply return false and keep
- // mLogGesture = false.
- if (x > 2 * (mEdgeWidthLeft + mLeftInset)
- && x < (mDisplaySize.x - 2 * (mEdgeWidthRight + mRightInset))) {
- return false;
- }
// Denotes whether we should proceed with the gesture.
// Even if it is false, we may want to log it assuming
// it is not invalid due to exclusion.
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index eeb93bb7d766..6a78c64638aa 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -23,22 +23,15 @@ import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
-import android.icu.text.MeasureFormat;
-import android.icu.util.Measure;
-import android.icu.util.MeasureUnit;
import android.os.Bundle;
import android.os.ServiceManager;
import android.os.UserHandle;
-import android.service.notification.ConversationChannelWrapper;
import android.util.Log;
import android.view.ViewGroup;
import com.android.systemui.R;
-import java.time.Duration;
import java.util.List;
-import java.util.Locale;
-import java.util.stream.Collectors;
/**
* Shows the user their tiles for their priority People (go/live-status).
@@ -52,7 +45,6 @@ public class PeopleSpaceActivity extends Activity {
private INotificationManager mNotificationManager;
private PackageManager mPackageManager;
private LauncherApps mLauncherApps;
- private List<ConversationChannelWrapper> mConversations;
private Context mContext;
@Override
@@ -77,15 +69,13 @@ public class PeopleSpaceActivity extends Activity {
*/
private void setTileViewsWithPriorityConversations() {
try {
- List<ConversationChannelWrapper> conversations =
- mNotificationManager.getConversations(
- true /* priority only */).getList();
- mConversations = conversations.stream().filter(
- c -> shouldKeepConversation(c)).collect(Collectors.toList());
- for (ConversationChannelWrapper conversation : mConversations) {
+ List<ShortcutInfo> shortcutInfos =
+ PeopleSpaceUtils.getShortcutInfos(
+ mContext, mNotificationManager, mPeopleManager);
+ for (ShortcutInfo conversation : shortcutInfos) {
PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
mPeopleSpaceLayout,
- conversation.getShortcutInfo().getId());
+ conversation.getId());
setTileView(tileView, conversation);
}
} catch (Exception e) {
@@ -95,11 +85,10 @@ public class PeopleSpaceActivity extends Activity {
/** Sets {@code tileView} with the data in {@code conversation}. */
private void setTileView(PeopleSpaceTileView tileView,
- ConversationChannelWrapper conversation) {
+ ShortcutInfo shortcutInfo) {
try {
- ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
int userId = UserHandle.getUserHandleForUid(
- conversation.getUid()).getIdentifier();
+ shortcutInfo.getUserId()).getIdentifier();
String pkg = shortcutInfo.getPackage();
long lastInteraction = mPeopleManager.getLastInteraction(
@@ -107,7 +96,7 @@ public class PeopleSpaceActivity extends Activity {
shortcutInfo.getId());
String status = lastInteraction != 0l ? mContext.getString(
R.string.last_interaction_status,
- getLastInteractionString(
+ PeopleSpaceUtils.getLastInteractionString(
lastInteraction)) : mContext.getString(R.string.basic_status);
tileView.setStatus(status);
@@ -120,46 +109,6 @@ public class PeopleSpaceActivity extends Activity {
}
}
- /** Returns a readable representation of {@code lastInteraction}. */
- private String getLastInteractionString(long lastInteraction) {
- long now = System.currentTimeMillis();
- Duration durationSinceLastInteraction = Duration.ofMillis(
- now - lastInteraction);
- MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
- MeasureFormat.FormatWidth.WIDE);
- if (durationSinceLastInteraction.toDays() >= 1) {
- return
- formatter
- .formatMeasures(new Measure(durationSinceLastInteraction.toDays(),
- MeasureUnit.DAY));
- } else if (durationSinceLastInteraction.toHours() >= 1) {
- return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toHours(),
- MeasureUnit.HOUR));
- } else if (durationSinceLastInteraction.toMinutes() >= 1) {
- return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toMinutes(),
- MeasureUnit.MINUTE));
- } else {
- return formatter.formatMeasures(
- new Measure(durationSinceLastInteraction.toMillis() / 1000,
- MeasureUnit.SECOND));
- }
- }
-
- /**
- * Returns whether the {@code conversation} should be kept for display in the People Space.
- *
- * <p>A valid {@code conversation} must:
- * <ul>
- * <li>Have a non-null {@link ShortcutInfo}
- * <li>Have an associated label in the {@link ShortcutInfo}
- * </ul>
- * </li>
- */
- private boolean shouldKeepConversation(ConversationChannelWrapper conversation) {
- ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
- return shortcutInfo != null && shortcutInfo.getLabel().length() != 0;
- }
-
@Override
protected void onResume() {
super.onResume();
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
new file mode 100644
index 000000000000..1a9dd712bd0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.people;
+
+import android.app.INotificationManager;
+import android.app.people.ConversationChannel;
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.icu.text.MeasureFormat;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
+import android.provider.Settings;
+import android.service.notification.ConversationChannelWrapper;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/** Utils class for People Space. */
+public class PeopleSpaceUtils {
+ private static final String TAG = "PeopleSpaceUtils";
+
+ /** Turns on debugging information about People Space. */
+ public static final boolean DEBUG = true;
+
+ /** Returns a list of {@link ShortcutInfo} corresponding to user's conversations. */
+ public static List<ShortcutInfo> getShortcutInfos(
+ Context context,
+ INotificationManager notificationManager,
+ IPeopleManager peopleManager
+ ) throws Exception {
+ boolean showAllConversations = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0;
+ List<ConversationChannelWrapper> conversations =
+ notificationManager.getConversations(
+ !showAllConversations /* priority only */).getList();
+ List<ShortcutInfo> shortcutInfos = conversations.stream().filter(
+ c -> shouldKeepConversation(c)).map(
+ c -> c.getShortcutInfo()).collect(Collectors.toList());
+ if (showAllConversations) {
+ List<ConversationChannel> recentConversations =
+ peopleManager.getRecentConversations().getList();
+ List<ShortcutInfo> recentShortcuts = recentConversations.stream().map(
+ c -> c.getShortcutInfo()).collect(Collectors.toList());
+ shortcutInfos.addAll(recentShortcuts);
+ }
+ return shortcutInfos;
+ }
+
+ /** Converts {@code drawable} to a {@link Bitmap}. */
+ public static Bitmap convertDrawableToBitmap(Drawable drawable) {
+ if (drawable == null) {
+ return null;
+ }
+
+ if (drawable instanceof BitmapDrawable) {
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ if (bitmapDrawable.getBitmap() != null) {
+ return bitmapDrawable.getBitmap();
+ }
+ }
+
+ Bitmap bitmap;
+ if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+ bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+ // Single color bitmap will be created of 1x1 pixel
+ } else {
+ bitmap = Bitmap.createBitmap(
+ drawable.getIntrinsicWidth(),
+ drawable.getIntrinsicHeight(),
+ Bitmap.Config.ARGB_8888
+ );
+ }
+
+ Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+ return bitmap;
+ }
+
+ /** Returns a readable representation of {@code lastInteraction}. */
+ public static String getLastInteractionString(long lastInteraction) {
+ long now = System.currentTimeMillis();
+ Duration durationSinceLastInteraction = Duration.ofMillis(
+ now - lastInteraction);
+ MeasureFormat formatter = MeasureFormat.getInstance(Locale.getDefault(),
+ MeasureFormat.FormatWidth.WIDE);
+ if (durationSinceLastInteraction.toDays() >= 1) {
+ return
+ formatter
+ .formatMeasures(new Measure(durationSinceLastInteraction.toDays(),
+ MeasureUnit.DAY));
+ } else if (durationSinceLastInteraction.toHours() >= 1) {
+ return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toHours(),
+ MeasureUnit.HOUR));
+ } else if (durationSinceLastInteraction.toMinutes() >= 1) {
+ return formatter.formatMeasures(new Measure(durationSinceLastInteraction.toMinutes(),
+ MeasureUnit.MINUTE));
+ } else {
+ return formatter.formatMeasures(
+ new Measure(durationSinceLastInteraction.toMillis() / 1000,
+ MeasureUnit.SECOND));
+ }
+ }
+
+ /**
+ * Returns whether the {@code conversation} should be kept for display in the People Space.
+ *
+ * <p>A valid {@code conversation} must:
+ * <ul>
+ * <li>Have a non-null {@link ShortcutInfo}
+ * <li>Have an associated label in the {@link ShortcutInfo}
+ * </ul>
+ * </li>
+ */
+ public static boolean shouldKeepConversation(ConversationChannelWrapper conversation) {
+ ShortcutInfo shortcutInfo = conversation.getShortcutInfo();
+ return shortcutInfo != null && shortcutInfo.getLabel().length() != 0;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
new file mode 100644
index 000000000000..44f173bc5175
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -0,0 +1,59 @@
+/*
+ * 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.people.widget;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.systemui.people.PeopleSpaceUtils;
+
+/** Proxy activity to launch ShortcutInfo's conversation. */
+public class LaunchConversationActivity extends Activity {
+ private static final String TAG = "PeopleSpaceLaunchConv";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) Log.d(TAG, "onCreate called");
+
+ Intent intent = getIntent();
+ ShortcutInfo shortcutInfo = (ShortcutInfo) intent.getParcelableExtra(
+ PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO
+ );
+ if (shortcutInfo != null) {
+ if (DEBUG) {
+ Log.d(TAG, "Launching conversation with shortcutInfo id " + shortcutInfo.getId());
+ }
+ try {
+ LauncherApps launcherApps =
+ getApplicationContext().getSystemService(LauncherApps.class);
+ launcherApps.startShortcut(
+ shortcutInfo, null, null);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception starting shortcut:" + e);
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "Trying to launch conversation with null shortcutInfo.");
+ }
+ finish();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
new file mode 100644
index 000000000000..aa98b61ff947
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java
@@ -0,0 +1,69 @@
+/*
+ * 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.people.widget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.systemui.R;
+import com.android.systemui.people.PeopleSpaceUtils;
+
+/** People Space Widget Provider class. */
+public class PeopleSpaceWidgetProvider extends AppWidgetProvider {
+ private static final String TAG = "PeopleSpaceWidgetPvd";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ public static final String EXTRA_SHORTCUT_INFO = "extra_shortcut_info";
+
+ /** Called when widget updates. */
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds);
+
+ if (DEBUG) Log.d(TAG, "onUpdate called");
+ // Perform this loop procedure for each App Widget that belongs to this provider
+ for (int appWidgetId : appWidgetIds) {
+ RemoteViews views =
+ new RemoteViews(context.getPackageName(), R.layout.people_space_widget);
+
+ Intent intent = new Intent(context, PeopleSpaceWidgetService.class);
+ intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ views.setRemoteAdapter(R.id.widget_list_view, intent);
+
+ Intent activityIntent = new Intent(context, LaunchConversationActivity.class);
+ activityIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NO_HISTORY
+ | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ appWidgetId,
+ activityIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
+ views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent);
+
+ // Tell the AppWidgetManager to perform an update on the current app widget
+ appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list_view);
+ appWidgetManager.updateAppWidget(appWidgetId, views);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java
new file mode 100644
index 000000000000..c68c30632b6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.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.people.widget;
+
+import android.app.INotificationManager;
+import android.app.people.IPeopleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import com.android.systemui.R;
+import com.android.systemui.people.PeopleSpaceTileView;
+import com.android.systemui.people.PeopleSpaceUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** People Space Widget RemoteViewsFactory class. */
+public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
+ private static final String TAG = "PeopleSpaceWRVFactory";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ private IPeopleManager mPeopleManager;
+ private INotificationManager mNotificationManager;
+ private PackageManager mPackageManager;
+ private LauncherApps mLauncherApps;
+ private List<ShortcutInfo> mShortcutInfos = new ArrayList<>();
+ private Context mContext;
+
+ public PeopleSpaceWidgetRemoteViewsFactory(Context context, Intent intent) {
+ this.mContext = context;
+ }
+
+ @Override
+ public void onCreate() {
+ if (DEBUG) Log.d(TAG, "onCreate called");
+ mNotificationManager =
+ INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mPackageManager = mContext.getPackageManager();
+ mPeopleManager = IPeopleManager.Stub.asInterface(
+ ServiceManager.getService(Context.PEOPLE_SERVICE));
+ mLauncherApps = mContext.getSystemService(LauncherApps.class);
+ setTileViewsWithPriorityConversations();
+ }
+
+ /**
+ * Retrieves all priority conversations and sets a {@link PeopleSpaceTileView}s for each
+ * priority conversation.
+ */
+ private void setTileViewsWithPriorityConversations() {
+ try {
+ mShortcutInfos =
+ PeopleSpaceUtils.getShortcutInfos(
+ mContext, mNotificationManager, mPeopleManager);
+ } catch (Exception e) {
+ Log.e(TAG, "Couldn't retrieve conversations", e);
+ }
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ if (DEBUG) Log.d(TAG, "onDataSetChanged called");
+ setTileViewsWithPriorityConversations();
+ }
+
+ @Override
+ public void onDestroy() {
+ mShortcutInfos.clear();
+ }
+
+ @Override
+ public int getCount() {
+ return mShortcutInfos.size();
+ }
+
+ @Override
+ public RemoteViews getViewAt(int i) {
+ if (DEBUG) Log.d(TAG, "getViewAt called, index: " + i);
+
+ RemoteViews personView =
+ new RemoteViews(mContext.getPackageName(), R.layout.people_space_widget_item);
+ try {
+ ShortcutInfo shortcutInfo = mShortcutInfos.get(i);
+ int userId = UserHandle.getUserHandleForUid(
+ shortcutInfo.getUserId()).getIdentifier();
+ String pkg = shortcutInfo.getPackage();
+ long lastInteraction = mPeopleManager.getLastInteraction(
+ pkg, userId,
+ shortcutInfo.getId());
+
+ String status = lastInteraction != 0L ? mContext.getString(
+ R.string.last_interaction_status,
+ PeopleSpaceUtils.getLastInteractionString(
+ lastInteraction)) : mContext.getString(R.string.basic_status);
+
+ personView.setTextViewText(R.id.status, status);
+ personView.setTextViewText(R.id.name, shortcutInfo.getLabel().toString());
+
+ personView.setImageViewBitmap(
+ R.id.package_icon,
+ PeopleSpaceUtils.convertDrawableToBitmap(
+ mPackageManager.getApplicationIcon(pkg)
+ )
+ );
+ personView.setImageViewBitmap(
+ R.id.person_icon,
+ PeopleSpaceUtils.convertDrawableToBitmap(
+ mLauncherApps.getShortcutIconDrawable(shortcutInfo, 0)
+ )
+ );
+
+ Intent fillInIntent = new Intent();
+ fillInIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_SHORTCUT_INFO, shortcutInfo);
+ personView.setOnClickFillInIntent(R.id.item, fillInIntent);
+ } catch (Exception e) {
+ Log.e(TAG, "Couldn't retrieve shortcut information", e);
+ }
+ return personView;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetService.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetService.java
new file mode 100644
index 000000000000..c0e43473d069
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetService.java
@@ -0,0 +1,34 @@
+/*
+ * 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.people.widget;
+import android.content.Intent;
+import android.util.Log;
+import android.widget.RemoteViewsService;
+
+import com.android.systemui.people.PeopleSpaceUtils;
+
+/** People Space Widget Service class. */
+public class PeopleSpaceWidgetService extends RemoteViewsService {
+ private static final String TAG = "PeopleSpaceWidgetSvc";
+ private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
+
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ if (DEBUG) Log.d(TAG, "onGetViewFactory called");
+ return new PeopleSpaceWidgetRemoteViewsFactory(this.getApplicationContext(), intent);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 3da1363f2a56..7359e79b26f5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -19,20 +19,31 @@ import com.android.systemui.R
typealias Privacy = PrivacyType
-enum class PrivacyType(val nameId: Int, val iconId: Int) {
+enum class PrivacyType(val nameId: Int, val iconId: Int, val logName: String) {
// This is uses the icons used by the corresponding permission groups in the AndroidManifest
- TYPE_CAMERA(R.string.privacy_type_camera,
- com.android.internal.R.drawable.perm_group_camera),
- TYPE_MICROPHONE(R.string.privacy_type_microphone,
- com.android.internal.R.drawable.perm_group_microphone),
- TYPE_LOCATION(R.string.privacy_type_location,
- com.android.internal.R.drawable.perm_group_location);
+ TYPE_CAMERA(
+ R.string.privacy_type_camera,
+ com.android.internal.R.drawable.perm_group_camera,
+ "camera"
+ ),
+ TYPE_MICROPHONE(
+ R.string.privacy_type_microphone,
+ com.android.internal.R.drawable.perm_group_microphone,
+ "microphone"
+ ),
+ TYPE_LOCATION(
+ R.string.privacy_type_location,
+ com.android.internal.R.drawable.perm_group_location,
+ "location"
+ );
fun getName(context: Context) = context.resources.getString(nameId)
fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
}
-data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication)
+data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) {
+ fun toLog(): String = "(${privacyType.logName}, ${application.packageName}(${application.uid}))"
+}
data class PrivacyApplication(val packageName: String, val uid: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index f56e6cdf5cb7..87ffbd465109 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -32,6 +32,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
+import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -48,6 +49,7 @@ class PrivacyItemController @Inject constructor(
@Background private val bgExecutor: Executor,
private val deviceConfigProxy: DeviceConfigProxy,
private val userTracker: UserTracker,
+ private val logger: PrivacyLogger,
dumpManager: DumpManager
) : Dumpable {
@@ -69,8 +71,10 @@ class PrivacyItemController @Inject constructor(
private const val ALL_INDICATORS =
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
+ private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
private const val DEFAULT_ALL_INDICATORS = false
private const val DEFAULT_MIC_CAMERA = true
+ private const val DEFAULT_LOCATION = false
}
@VisibleForTesting
@@ -88,6 +92,11 @@ class PrivacyItemController @Inject constructor(
return true
}
+ private fun isLocationEnabled(): Boolean {
+ return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ LOCATION, DEFAULT_LOCATION)
+ }
+
private var currentUserIds = emptyList<Int>()
private var listening = false
private val callbacks = mutableListOf<WeakReference<Callback>>()
@@ -107,13 +116,15 @@ class PrivacyItemController @Inject constructor(
private set
var micCameraAvailable = isMicCameraEnabled()
private set
+ var locationAvailable = isLocationEnabled()
private val devicePropertiesChangedListener =
object : DeviceConfig.OnPropertiesChangedListener {
override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
(properties.keyset.contains(ALL_INDICATORS) ||
- properties.keyset.contains(MIC_CAMERA))) {
+ properties.keyset.contains(MIC_CAMERA) ||
+ properties.keyset.contains(LOCATION))) {
// Running on the ui executor so can iterate on callbacks
if (properties.keyset.contains(ALL_INDICATORS)) {
@@ -126,6 +137,10 @@ class PrivacyItemController @Inject constructor(
// micCameraAvailable = properties.getBoolean(MIC_CAMERA, DEFAULT_MIC_CAMERA)
// callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) }
// }
+ if (properties.keyset.contains(LOCATION)) {
+ locationAvailable = properties.getBoolean(LOCATION, DEFAULT_LOCATION)
+ callbacks.forEach { it.get()?.onFlagLocationChanged(locationAvailable) }
+ }
internalUiExecutor.updateListeningState()
}
}
@@ -139,11 +154,13 @@ class PrivacyItemController @Inject constructor(
active: Boolean
) {
// Check if we care about this code right now
- if (!allIndicatorsAvailable && code in OPS_LOCATION) {
+ if (!allIndicatorsAvailable &&
+ (code in OPS_LOCATION && !locationAvailable)) {
return
}
val userId = UserHandle.getUserId(uid)
if (userId in currentUserIds) {
+ logger.logUpdatedItemFromAppOps(code, uid, packageName, active)
update(false)
}
}
@@ -180,6 +197,7 @@ class PrivacyItemController @Inject constructor(
bgExecutor.execute {
if (updateUsers) {
currentUserIds = userTracker.userProfiles.map { it.id }
+ logger.logCurrentProfilesChanged(currentUserIds)
}
updateListAndNotifyChanges.run()
}
@@ -195,7 +213,8 @@ class PrivacyItemController @Inject constructor(
* main thread.
*/
private fun setListeningState() {
- val listen = !callbacks.isEmpty() and (allIndicatorsAvailable || micCameraAvailable)
+ val listen = !callbacks.isEmpty() and
+ (allIndicatorsAvailable || micCameraAvailable || locationAvailable)
if (listening == listen) return
listening = listen
if (listening) {
@@ -245,6 +264,8 @@ class PrivacyItemController @Inject constructor(
}
val list = currentUserIds.flatMap { appOpsController.getActiveAppOpsForUser(it) }
.mapNotNull { toPrivacyItem(it) }.distinct()
+ logger.logUpdatedPrivacyItemsList(
+ list.joinToString(separator = ", ", transform = PrivacyItem::toLog))
privacyList = list
}
@@ -258,7 +279,9 @@ class PrivacyItemController @Inject constructor(
AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
else -> return null
}
- if (type == PrivacyType.TYPE_LOCATION && !allIndicatorsAvailable) return null
+ if (type == PrivacyType.TYPE_LOCATION && (!allIndicatorsAvailable && !locationAvailable)) {
+ return null
+ }
val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
return PrivacyItem(type, app)
}
@@ -271,6 +294,9 @@ class PrivacyItemController @Inject constructor(
@JvmDefault
fun onFlagMicCameraChanged(flag: Boolean) {}
+
+ @JvmDefault
+ fun onFlagLocationChanged(flag: Boolean) {}
}
private class NotifyChangesToCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
new file mode 100644
index 000000000000..c88676e713b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -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.systemui.privacy.logging
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.LogMessage
+import com.android.systemui.log.dagger.PrivacyLog
+import javax.inject.Inject
+
+private const val TAG = "PrivacyLog"
+
+class PrivacyLogger @Inject constructor(
+ @PrivacyLog private val buffer: LogBuffer
+) {
+
+ fun logUpdatedItemFromAppOps(code: Int, uid: Int, packageName: String, active: Boolean) {
+ log(LogLevel.INFO, {
+ int1 = code
+ int2 = uid
+ str1 = packageName
+ bool1 = active
+ }, {
+ "App Op: $int1 for $str1($int2), active=$bool1"
+ })
+ }
+
+ fun logUpdatedPrivacyItemsList(listAsString: String) {
+ log(LogLevel.INFO, {
+ str1 = listAsString
+ }, {
+ "Updated list: $str1"
+ })
+ }
+
+ fun logCurrentProfilesChanged(profiles: List<Int>) {
+ log(LogLevel.INFO, {
+ str1 = profiles.toString()
+ }, {
+ "Profiles changed: $str1"
+ })
+ }
+
+ fun logChipVisible(visible: Boolean) {
+ log(LogLevel.INFO, {
+ bool1 = visible
+ }, {
+ "Chip visible: $bool1"
+ })
+ }
+
+ fun logStatusBarIconsVisible(
+ showCamera: Boolean,
+ showMichrophone: Boolean,
+ showLocation: Boolean
+ ) {
+ log(LogLevel.INFO, {
+ bool1 = showCamera
+ bool2 = showMichrophone
+ bool3 = showLocation
+ }, {
+ "Status bar icons visible: camera=$bool1, microphone=$bool2, location=$bool3"
+ })
+ }
+
+ private inline fun log(
+ logLevel: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ buffer.log(TAG, logLevel, initializer, printer)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index dc157a8dd257..6ac1e7079531 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -33,7 +33,7 @@ class DoubleLineTileLayout(
private const val NUM_LINES = 2
}
- protected val mRecords = ArrayList<QSPanel.TileRecord>()
+ protected val mRecords = ArrayList<QSPanelControllerBase.TileRecord>()
private var _listening = false
private var smallTileSize = 0
private val twoLineHeight
@@ -50,17 +50,17 @@ class DoubleLineTileLayout(
updateResources()
}
- override fun addTile(tile: QSPanel.TileRecord) {
+ override fun addTile(tile: QSPanelControllerBase.TileRecord) {
mRecords.add(tile)
tile.tile.setListening(this, _listening)
addTileView(tile)
}
- protected fun addTileView(tile: QSPanel.TileRecord) {
+ protected fun addTileView(tile: QSPanelControllerBase.TileRecord) {
addView(tile.tileView)
}
- override fun removeTile(tile: QSPanel.TileRecord) {
+ override fun removeTile(tile: QSPanelControllerBase.TileRecord) {
mRecords.remove(tile)
tile.tile.setListening(this, false)
removeView(tile.tileView)
@@ -72,7 +72,7 @@ class DoubleLineTileLayout(
super.removeAllViews()
}
- override fun getOffsetTop(tile: QSPanel.TileRecord?) = top
+ override fun getOffsetTop(tile: QSPanelControllerBase.TileRecord?) = top
override fun updateResources(): Boolean {
with(mContext.resources) {
@@ -99,7 +99,7 @@ class DoubleLineTileLayout(
}
}
- override fun getNumVisibleTiles() = tilesToShow
+ override fun getNumVisibleTiles() = Math.min(mRecords.size, tilesToShow)
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 04f379ef35ea..3062a77bcbe1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -26,7 +26,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSPanel.QSTileLayout;
-import com.android.systemui.qs.QSPanel.TileRecord;
+import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
import java.util.ArrayList;
import java.util.Set;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 9dcc924f161e..4d4195063227 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -28,12 +28,17 @@ import com.android.systemui.qs.QSHost.Callback;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.qs.TouchAnimator.Listener;
+import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import java.util.ArrayList;
import java.util.Collection;
+import javax.inject.Inject;
+
+/** */
+@QSScope
public class QSAnimator implements Callback, PageListener, Listener, OnLayoutChangeListener,
OnAttachStateChangeListener, Tunable {
@@ -53,6 +58,9 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private final ArrayList<View> mQuickQsViews = new ArrayList<>();
private final QuickQSPanel mQuickQsPanel;
private final QSPanel mQsPanel;
+ private final QSPanelController mQsPanelController;
+ private final QuickQSPanelController mQuickQSPanelController;
+ private final QSSecurityFooter mSecurityFooter;
private final QS mQs;
private PagedTileLayout mPagedLayout;
@@ -78,10 +86,19 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
private QSTileHost mHost;
private boolean mShowCollapsedOnKeyguard;
- public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanel panel) {
+ @Inject
+ public QSAnimator(QS qs, QuickQSPanel quickPanel, QSPanel panel,
+ QSPanelController qsPanelController, QuickQSPanelController quickQSPanelController,
+ QSTileHost qsTileHost,
+ QSSecurityFooter securityFooter) {
mQs = qs;
mQuickQsPanel = quickPanel;
mQsPanel = panel;
+ mQsPanelController = qsPanelController;
+ mQuickQSPanelController = quickQSPanelController;
+ mSecurityFooter = securityFooter;
+ mHost = qsTileHost;
+ mHost.addCallback(this);
mQsPanel.addOnAttachStateChangeListener(this);
qs.getView().addOnLayoutChangeListener(this);
if (mQsPanel.isAttachedToWindow()) {
@@ -134,12 +151,6 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
&& !mShowCollapsedOnKeyguard ? View.INVISIBLE : View.VISIBLE);
}
- public void setHost(QSTileHost qsh) {
- mHost = qsh;
- qsh.addCallback(this);
- updateAnimators();
- }
-
@Override
public void onViewAttachedToWindow(View v) {
Dependency.get(TunerService.class).addTunable(this, ALLOW_FANCY_ANIMATION,
@@ -148,9 +159,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
@Override
public void onViewDetachedFromWindow(View v) {
- if (mHost != null) {
- mHost.removeCallback(this);
- }
+ mHost.removeCallback(this);
Dependency.get(TunerService.class).removeTunable(this);
}
@@ -185,8 +194,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
TouchAnimator.Builder translationXBuilder = new Builder();
TouchAnimator.Builder translationYBuilder = new Builder();
- if (mQsPanel.getHost() == null) return;
- Collection<QSTile> tiles = mQsPanel.getHost().getTiles();
+ Collection<QSTile> tiles = mHost.getTiles();
int count = 0;
int[] loc1 = new int[2];
int[] loc2 = new int[2];
@@ -206,7 +214,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
firstPageBuilder.addFloat(tileLayout, "translationY", heightDiff, 0);
for (QSTile tile : tiles) {
- QSTileView tileView = mQsPanel.getTileView(tile);
+ QSTileView tileView = mQsPanelController.getTileView(tile);
if (tileView == null) {
Log.e(TAG, "tileView is null " + tile.getTileSpec());
continue;
@@ -217,7 +225,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
// This case: less tiles to animate in small displays.
if (count < mQuickQsPanel.getTileLayout().getNumVisibleTiles() && mAllowFancy) {
// Quick tiles.
- QSTileView quickTileView = mQuickQsPanel.getTileView(tile);
+ QSTileView quickTileView = mQuickQSPanelController.getTileView(tile);
if (quickTileView == null) continue;
lastX = loc1[0];
@@ -302,16 +310,12 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha
// Fade in the security footer and the divider as we reach the final position
builder = new Builder().setStartDelay(EXPANDED_TILE_DELAY);
- if (mQsPanel.getSecurityFooter() != null) {
- builder.addFloat(mQsPanel.getSecurityFooter().getView(), "alpha", 0, 1);
- }
+ builder.addFloat(mSecurityFooter.getView(), "alpha", 0, 1);
if (mQsPanel.getDivider() != null) {
builder.addFloat(mQsPanel.getDivider(), "alpha", 0, 1);
}
mAllPagesDelayedAnimator = builder.build();
- if (mQsPanel.getSecurityFooter() != null) {
- mAllViews.add(mQsPanel.getSecurityFooter().getView());
- }
+ mAllViews.add(mSecurityFooter.getView());
if (mQsPanel.getDivider() != null) {
mAllViews.add(mQsPanel.getDivider());
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7e2433a1fd33..a35151068bee 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -71,8 +71,7 @@ public class QSContainerImpl extends FrameLayout {
private int mSideMargins;
private boolean mQsDisabled;
- private int mContentPaddingStart = -1;
- private int mContentPaddingEnd = -1;
+ private int mContentPadding = -1;
private boolean mAnimateBottomOnNextLayout;
public QSContainerImpl(Context context, AttributeSet attrs) {
@@ -205,12 +204,10 @@ public class QSContainerImpl extends FrameLayout {
mQSPanelContainer.setLayoutParams(layoutParams);
mSideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
- mContentPaddingStart = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_start);
- int newPaddingEnd = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_content_margin_end);
- boolean marginsChanged = newPaddingEnd != mContentPaddingEnd;
- mContentPaddingEnd = newPaddingEnd;
+ int padding = getResources().getDimensionPixelSize(
+ R.dimen.notification_shade_content_margin_horizontal);
+ boolean marginsChanged = padding != mContentPadding;
+ mContentPadding = padding;
if (marginsChanged) {
updatePaddingsAndMargins();
}
@@ -291,19 +288,19 @@ public class QSContainerImpl extends FrameLayout {
lp.leftMargin = mSideMargins;
if (view == mQSPanelContainer) {
// QS panel lays out some of its content full width
- mQSPanel.setContentMargins(mContentPaddingStart, mContentPaddingEnd);
+ mQSPanel.setContentMargins(mContentPadding, mContentPadding);
Pair<Integer, Integer> margins = mQSPanel.getVisualSideMargins();
// Apply paddings based on QSPanel
mQSCustomizer.setContentPaddings(margins.first, margins.second);
} else if (view == mHeader) {
// The header contains the QQS panel which needs to have special padding, to
// visually align them.
- mHeader.setContentMargins(mContentPaddingStart, mContentPaddingEnd);
+ mHeader.setContentMargins(mContentPadding, mContentPadding);
} else {
view.setPaddingRelative(
- mContentPaddingStart,
+ mContentPadding,
view.getPaddingTop(),
- mContentPaddingEnd,
+ mContentPadding,
view.getPaddingBottom());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
index 1e239b1e9ec9..4b9f4316f2bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImplController.java
@@ -16,24 +16,25 @@
package com.android.systemui.qs;
-import com.android.systemui.R;
+import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
-class QSContainerImplController extends ViewController<QSContainerImpl> {
+/** */
+@QSScope
+public class QSContainerImplController extends ViewController<QSContainerImpl> {
private final QuickStatusBarHeaderController mQuickStatusBarHeaderController;
- private QSContainerImplController(QSContainerImpl view,
- QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
+ @Inject
+ QSContainerImplController(QSContainerImpl view,
+ QuickStatusBarHeaderController quickStatusBarHeaderController) {
super(view);
- mQuickStatusBarHeaderController = quickStatusBarHeaderControllerBuilder
- .setQuickStatusBarHeader(mView.findViewById(R.id.header)).build();
+ mQuickStatusBarHeaderController = quickStatusBarHeaderController;
}
@Override
- public void init() {
- super.init();
+ public void onInit() {
mQuickStatusBarHeaderController.init();
}
@@ -49,23 +50,7 @@ class QSContainerImplController extends ViewController<QSContainerImpl> {
protected void onViewDetached() {
}
- static class Builder {
- private final QuickStatusBarHeaderController.Builder mQuickStatusBarHeaderControllerBuilder;
- private QSContainerImpl mView;
-
- @Inject
- Builder(
- QuickStatusBarHeaderController.Builder quickStatusBarHeaderControllerBuilder) {
- mQuickStatusBarHeaderControllerBuilder = quickStatusBarHeaderControllerBuilder;
- }
-
- public Builder setQSContainerImpl(QSContainerImpl view) {
- mView = view;
- return this;
- }
-
- public QSContainerImplController build() {
- return new QSContainerImplController(mView, mQuickStatusBarHeaderControllerBuilder);
- }
+ public QSContainerImpl getView() {
+ return mView;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 2be8a9704e1c..619729e55314 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -57,7 +57,7 @@ public class QSDetail extends LinearLayout {
protected TextView mDetailDoneButton;
private QSDetailClipper mClipper;
private DetailAdapter mDetailAdapter;
- private QSPanel mQsPanel;
+ private QSPanelController mQsPanelController;
protected View mQsDetailHeader;
protected TextView mQsDetailHeaderTitle;
@@ -76,7 +76,7 @@ public class QSDetail extends LinearLayout {
private int mOpenY;
private boolean mAnimatingOpen;
private boolean mSwitchState;
- private View mFooter;
+ private QSFooter mFooter;
public QSDetail(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -114,18 +114,20 @@ public class QSDetail extends LinearLayout {
public void onClick(View v) {
announceForAccessibility(
mContext.getString(R.string.accessibility_desc_quick_settings));
- mQsPanel.closeDetail();
+ mQsPanelController.closeDetail();
}
};
mDetailDoneButton.setOnClickListener(doneListener);
}
- public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, View footer) {
- mQsPanel = panel;
+ /** */
+ public void setQsPanel(QSPanelController panelController, QuickStatusBarHeader header,
+ QSFooter footer) {
+ mQsPanelController = panelController;
mHeader = header;
mFooter = footer;
mHeader.setCallback(mQsPanelCallback);
- mQsPanel.setCallback(mQsPanelCallback);
+ mQsPanelController.setCallback(mQsPanelCallback);
}
public void setHost(QSTileHost host) {
@@ -220,7 +222,7 @@ public class QSDetail extends LinearLayout {
listener = mTeardownDetailWhenDone;
mHeader.setVisibility(View.VISIBLE);
mFooter.setVisibility(View.VISIBLE);
- mQsPanel.setGridContentVisibility(true);
+ mQsPanelController.setGridContentVisibility(true);
mQsPanelCallback.onScanStateChanged(false);
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -361,7 +363,7 @@ public class QSDetail extends LinearLayout {
public void onAnimationEnd(Animator animation) {
// Only hide content if still in detail state.
if (mDetailAdapter != null) {
- mQsPanel.setGridContentVisibility(false);
+ mQsPanelController.setGridContentVisibility(false);
mHeader.setVisibility(View.INVISIBLE);
mFooter.setVisibility(View.INVISIBLE);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index 84563a078447..8b9dae14c809 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 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,19 +11,14 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT 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.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
import android.content.Context;
-import android.content.Intent;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.PorterDuff.Mode;
@@ -36,50 +31,27 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
-import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
-import com.android.systemui.tuner.TunerService;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class QSFooterImpl extends FrameLayout implements QSFooter,
- OnClickListener, OnUserInfoChangedListener {
- private static final String TAG = "QSFooterImpl";
-
- private final ActivityStarter mActivityStarter;
- private final UserInfoController mUserInfoController;
- private final DeviceProvisionedController mDeviceProvisionedController;
- private final UserTracker mUserTracker;
+/** */
+public class QSFooterView extends FrameLayout {
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
@@ -87,7 +59,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private boolean mShouldShowBuildText;
private boolean mQsDisabled;
- private QSPanel mQsPanel;
private QuickQSPanel mQuickQsPanel;
private boolean mExpanded;
@@ -117,39 +88,19 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
};
- @Inject
- public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- ActivityStarter activityStarter, UserInfoController userInfoController,
- DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) {
+ public QSFooterView(Context context, AttributeSet attrs) {
super(context, attrs);
- mActivityStarter = activityStarter;
- mUserInfoController = userInfoController;
- mDeviceProvisionedController = deviceProvisionedController;
- mUserTracker = userTracker;
- }
-
- @VisibleForTesting
- public QSFooterImpl(Context context, AttributeSet attrs) {
- this(context, attrs,
- Dependency.get(ActivityStarter.class),
- Dependency.get(UserInfoController.class),
- Dependency.get(DeviceProvisionedController.class),
- Dependency.get(UserTracker.class));
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mEdit = findViewById(android.R.id.edit);
- mEdit.setOnClickListener(view ->
- mActivityStarter.postQSRunnableDismissingKeyguard(() ->
- mQsPanel.showEdit(view)));
mPageIndicator = findViewById(R.id.footer_page_indicator);
mSettingsButton = findViewById(R.id.settings_button);
mSettingsContainer = findViewById(R.id.settings_button_container);
- mSettingsButton.setOnClickListener(this);
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -157,19 +108,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
- mBuildText.setOnLongClickListener(view -> {
- CharSequence buildText = mBuildText.getText();
- if (!TextUtils.isEmpty(buildText)) {
- ClipboardManager service =
- mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
- String label = mContext.getString(R.string.build_number_clip_data_label);
- service.setPrimaryClip(ClipData.newPlainText(label, buildText));
- Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
- .show();
- return true;
- }
- return false;
- });
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -180,7 +118,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
oldBottom) -> updateAnimator(right - left));
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
- updateEverything();
setBuildText();
}
@@ -249,24 +186,22 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
.build();
}
- @Override
- public void setKeyguardShowing(boolean keyguardShowing) {
+ /** */
+ public void setKeyguardShowing() {
setExpansion(mExpansionAmount);
}
- @Override
public void setExpandClickListener(OnClickListener onClickListener) {
mExpandClickListener = onClickListener;
}
- @Override
- public void setExpanded(boolean expanded) {
+ void setExpanded(boolean expanded, boolean isTunerEnabled) {
if (mExpanded == expanded) return;
mExpanded = expanded;
- updateEverything();
+ updateEverything(isTunerEnabled);
}
- @Override
+ /** */
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
@@ -287,18 +222,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
@Override
@VisibleForTesting
public void onDetachedFromWindow() {
- setListening(false);
mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);
super.onDetachedFromWindow();
}
- @Override
+ /** */
public void setListening(boolean listening) {
if (listening == mListening) {
return;
}
mListening = listening;
- updateListeners();
}
@Override
@@ -318,17 +251,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
}
- @Override
- public void disable(int state1, int state2, boolean animate) {
+ void disable(int state2, boolean isTunerEnabled) {
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
- updateEverything();
+ updateEverything(isTunerEnabled);
}
- public void updateEverything() {
+ void updateEverything(boolean isTunerEnabled) {
post(() -> {
- updateVisibilities();
+ updateVisibilities(isTunerEnabled);
updateClickabilities();
setClickable(false);
});
@@ -341,11 +273,10 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
- private void updateVisibilities() {
+ private void updateVisibilities(boolean isTunerEnabled) {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE
- : View.INVISIBLE);
+ isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -358,78 +289,22 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
return mExpanded && mMultiUserSwitch.isMultiUserEnabled();
}
- private void updateListeners() {
- if (mListening) {
- mUserInfoController.addCallback(this);
- } else {
- mUserInfoController.removeCallback(this);
- }
- }
-
- @Override
+ /** */
public void setQSPanel(final QSPanel qsPanel) {
- mQsPanel = qsPanel;
- if (mQsPanel != null) {
+ if (qsPanel != null) {
mMultiUserSwitch.setQsPanel(qsPanel);
- mQsPanel.setFooterPageIndicator(mPageIndicator);
+ qsPanel.setFooterPageIndicator(mPageIndicator);
}
}
- @Override
public void setQQSPanel(@Nullable QuickQSPanel panel) {
mQuickQsPanel = panel;
}
- @Override
- public void onClick(View v) {
- // Don't do anything until view are unhidden
- if (!mExpanded) {
- return;
- }
-
- if (v == mSettingsButton) {
- if (!mDeviceProvisionedController.isCurrentUserSetup()) {
- // If user isn't setup just unlock the device and dump them back at SUW.
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- });
- return;
- }
- MetricsLogger.action(mContext,
- mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
- : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
- if (mSettingsButton.isTunerClick()) {
- mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
- if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) {
- TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(),
- () -> {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- });
- } else {
- Toast.makeText(getContext(), R.string.tuner_toast,
- Toast.LENGTH_LONG).show();
- TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true);
- }
- startSettingsActivity();
-
- });
- } else {
- startSettingsActivity();
- }
- }
- }
-
- private void startSettingsActivity() {
- mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
- true /* dismissShade */);
- }
- @Override
- public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
- if (picture != null &&
- UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) &&
- !(picture instanceof UserIconDrawable)) {
- picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate();
+ void onUserInfoChanged(Drawable picture, boolean isGuestUser) {
+ if (picture != null && isGuestUser && !(picture instanceof UserIconDrawable)) {
+ picture = picture.getConstantState().newDrawable(getResources()).mutate();
picture.setColorFilter(
Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
Mode.SRC_IN);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
new file mode 100644
index 000000000000..e3af04bdc31e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java
@@ -0,0 +1,241 @@
+/*
+ * 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.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.os.UserManager;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SettingsButton;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.ViewController;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link QSFooterView}.
+ */
+@QSScope
+public class QSFooterViewController extends ViewController<QSFooterView> implements QSFooter {
+
+ private final UserManager mUserManager;
+ private final UserInfoController mUserInfoController;
+ private final ActivityStarter mActivityStarter;
+ private final DeviceProvisionedController mDeviceProvisionedController;
+ private final UserTracker mUserTracker;
+ private final QSPanelController mQsPanelController;
+ private final TunerService mTunerService;
+ private final MetricsLogger mMetricsLogger;
+ private final SettingsButton mSettingsButton;
+ private final TextView mBuildText;
+ private final View mEdit;
+
+ private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
+ new UserInfoController.OnUserInfoChangedListener() {
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
+ boolean isGuestUser = mUserManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser());
+ mView.onUserInfoChanged(picture, isGuestUser);
+ }
+ };
+
+ private final View.OnClickListener mSettingsOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Don't do anything until view are unhidden
+ if (!mExpanded) {
+ return;
+ }
+
+ if (v == mSettingsButton) {
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) {
+ // If user isn't setup just unlock the device and dump them back at SUW.
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ });
+ return;
+ }
+ mMetricsLogger.action(
+ mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
+ : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
+ if (mSettingsButton.isTunerClick()) {
+ mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
+ if (isTunerEnabled()) {
+ mTunerService.showResetRequest(
+ mUserTracker.getUserHandle(),
+ () -> {
+ // Relaunch settings so that the tuner disappears.
+ startSettingsActivity();
+ });
+ } else {
+ Toast.makeText(getContext(), R.string.tuner_toast,
+ Toast.LENGTH_LONG).show();
+ mTunerService.setTunerEnabled(mUserTracker.getUserHandle(), true);
+ }
+ startSettingsActivity();
+
+ });
+ } else {
+ startSettingsActivity();
+ }
+ }
+ }
+ };
+
+ private boolean mListening;
+ private boolean mExpanded;
+
+ @Inject
+ QSFooterViewController(QSFooterView view, UserManager userManager,
+ UserInfoController userInfoController, ActivityStarter activityStarter,
+ DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
+ QSPanelController qsPanelController, TunerService tunerService,
+ MetricsLogger metricsLogger) {
+ super(view);
+ mUserManager = userManager;
+ mUserInfoController = userInfoController;
+ mActivityStarter = activityStarter;
+ mDeviceProvisionedController = deviceProvisionedController;
+ mUserTracker = userTracker;
+ mQsPanelController = qsPanelController;
+ mTunerService = tunerService;
+ mMetricsLogger = metricsLogger;
+
+ mSettingsButton = mView.findViewById(R.id.settings_button);
+ mBuildText = mView.findViewById(R.id.build);
+ mEdit = mView.findViewById(android.R.id.edit);
+ }
+
+
+ @Override
+ protected void onViewAttached() {
+ mSettingsButton.setOnClickListener(mSettingsOnClickListener);
+ mBuildText.setOnLongClickListener(view -> {
+ CharSequence buildText = mBuildText.getText();
+ if (!TextUtils.isEmpty(buildText)) {
+ ClipboardManager service =
+ mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
+ String label = getResources().getString(R.string.build_number_clip_data_label);
+ service.setPrimaryClip(ClipData.newPlainText(label, buildText));
+ Toast.makeText(getContext(), R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
+ .show();
+ return true;
+ }
+ return false;
+ });
+
+ mEdit.setOnClickListener(view ->
+ mActivityStarter.postQSRunnableDismissingKeyguard(() ->
+ mQsPanelController.showEdit(view)));
+
+ mView.updateEverything(isTunerEnabled());
+ }
+
+ @Override
+ protected void onViewDetached() {
+ setListening(false);
+ }
+
+
+ @Override
+ public void setQSPanel(@Nullable QSPanel panel) {
+ mView.setQSPanel(panel);
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mView.setVisibility(visibility);
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ mExpanded = expanded;
+ mView.setExpanded(expanded, isTunerEnabled());
+ }
+
+
+ @Override
+ public int getHeight() {
+ return mView.getHeight();
+ }
+
+ @Override
+ public void setExpansion(float expansion) {
+ mView.setExpansion(expansion);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) {
+ return;
+ }
+
+ mListening = listening;
+ if (mListening) {
+ mUserInfoController.addCallback(mOnUserInfoChangedListener);
+ } else {
+ mUserInfoController.removeCallback(mOnUserInfoChangedListener);
+ }
+ }
+
+ @Override
+ public void setKeyguardShowing(boolean keyguardShowing) {
+ mView.setKeyguardShowing();
+ }
+
+ /** */
+ @Override
+ public void setExpandClickListener(View.OnClickListener onClickListener) {
+ mView.setExpandClickListener(onClickListener);
+ }
+
+ @Override
+ public void setQQSPanel(@Nullable QuickQSPanel panel) {
+ mView.setQQSPanel(panel);
+ }
+
+ @Override
+ public void disable(int state1, int state2, boolean animate) {
+ mView.disable(state2, isTunerEnabled());
+ }
+
+
+ private void startSettingsActivity() {
+ mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
+ true /* dismissShade */);
+ }
+
+ private boolean isTunerEnabled() {
+ return mTunerService.isTunerEnabled(mUserTracker.getUserHandle());
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index 3a783653a2d8..e1bca4a42b89 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -35,11 +35,11 @@ import androidx.annotation.VisibleForTesting;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.R.id;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.dagger.QSFragmentComponent;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
@@ -69,8 +69,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private QSAnimator mQSAnimator;
private HeightListener mPanelView;
protected QuickStatusBarHeader mHeader;
- private QSCustomizer mQSCustomizer;
- protected QSPanel mQSPanel;
protected NonInterceptingScrollView mQSPanelScrollView;
private QSDetail mQSDetail;
private boolean mListening;
@@ -82,7 +80,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final InjectionInflationController mInjectionInflater;
- private final QSContainerImplController.Builder mQSContainerImplControllerBuilder;
+ private final QSFragmentComponent.Factory mQsComponentFactory;
private final QSTileHost mHost;
private boolean mShowCollapsedOnKeyguard;
private boolean mLastKeyguardAndExpanded;
@@ -96,15 +94,18 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private int[] mTmpLocation = new int[2];
private int mLastViewHeight;
private float mLastHeaderTranslation;
+ private QSPanelController mQSPanelController;
+ private QuickQSPanelController mQuickQSPanelController;
+ private QSCustomizerController mQSCustomizerController;
@Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
InjectionInflationController injectionInflater, QSTileHost qsTileHost,
StatusBarStateController statusBarStateController, CommandQueue commandQueue,
- QSContainerImplController.Builder qsContainerImplControllerBuilder) {
+ QSFragmentComponent.Factory qsComponentFactory) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mInjectionInflater = injectionInflater;
- mQSContainerImplControllerBuilder = qsContainerImplControllerBuilder;
+ mQsComponentFactory = qsComponentFactory;
commandQueue.observe(getLifecycle(), this);
mHost = qsTileHost;
mStatusBarStateController = statusBarStateController;
@@ -120,8 +121,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- mQSPanel = view.findViewById(R.id.quick_settings_panel);
+ QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this);
+ mQSPanelController = qsFragmentComponent.getQSPanelController();
+ mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController();
+
+ mQSPanelController.init();
+ mQuickQSPanelController.init();
+
mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
mQSPanelScrollView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
@@ -135,28 +141,26 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
});
mQSDetail = view.findViewById(R.id.qs_detail);
mHeader = view.findViewById(R.id.header);
- mQSPanel.setHeaderContainer(view.findViewById(R.id.header_text_container));
- mFooter = view.findViewById(R.id.qs_footer);
- mContainer = view.findViewById(id.quick_settings_container);
+ mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
+ mFooter = qsFragmentComponent.getQSFooter();
- mQSContainerImplController = mQSContainerImplControllerBuilder
- .setQSContainerImpl((QSContainerImpl) view)
- .build();
+ mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
mQSContainerImplController.init();
+ mContainer = mQSContainerImplController.getView();
- mQSDetail.setQsPanel(mQSPanel, mHeader, (View) mFooter);
- mQSAnimator = new QSAnimator(this, mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
-
+ mQSDetail.setQsPanel(mQSPanelController, mHeader, mFooter);
+ mQSAnimator = qsFragmentComponent.getQSAnimator();
- mQSCustomizer = view.findViewById(R.id.qs_customize);
- mQSCustomizer.setQs(this);
+ mQSCustomizerController = qsFragmentComponent.getQSCustomizerController();
+ mQSCustomizerController.init();
+ mQSCustomizerController.setQs(this);
if (savedInstanceState != null) {
setExpanded(savedInstanceState.getBoolean(EXTRA_EXPANDED));
setListening(savedInstanceState.getBoolean(EXTRA_LISTENING));
setEditLocation(view);
- mQSCustomizer.restoreInstanceState(savedInstanceState);
+ mQSCustomizerController.restoreInstanceState(savedInstanceState);
if (mQsExpanded) {
- mQSPanel.getTileLayout().restoreInstanceState(savedInstanceState);
+ mQSPanelController.getTileLayout().restoreInstanceState(savedInstanceState);
}
}
setHost(mHost);
@@ -178,7 +182,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (mListening) {
setListening(false);
}
- mQSCustomizer.setQs(null);
+ mQSCustomizerController.setQs(null);
}
@Override
@@ -186,9 +190,9 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
super.onSaveInstanceState(outState);
outState.putBoolean(EXTRA_EXPANDED, mQsExpanded);
outState.putBoolean(EXTRA_LISTENING, mListening);
- mQSCustomizer.saveInstanceState(outState);
+ mQSCustomizerController.saveInstanceState(outState);
if (mQsExpanded) {
- mQSPanel.getTileLayout().saveInstanceState(outState);
+ mQSPanelController.getTileLayout().saveInstanceState(outState);
}
}
@@ -233,30 +237,25 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
int[] loc = edit.getLocationOnScreen();
int x = loc[0] + edit.getWidth() / 2;
int y = loc[1] + edit.getHeight() / 2;
- mQSCustomizer.setEditLocation(x, y);
+ mQSCustomizerController.setEditLocation(x, y);
}
@Override
public void setContainer(ViewGroup container) {
if (container instanceof NotificationsQuickSettingsContainer) {
- mQSCustomizer.setContainer((NotificationsQuickSettingsContainer) container);
+ mQSCustomizerController.setContainer((NotificationsQuickSettingsContainer) container);
}
}
@Override
public boolean isCustomizing() {
- return mQSCustomizer.isCustomizing();
+ return mQSCustomizerController.isCustomizing();
}
public void setHost(QSTileHost qsh) {
- mQSPanel.setHost(qsh, mQSCustomizer);
- mHeader.setQSPanel(mQSPanel);
- mFooter.setQSPanel(mQSPanel);
+ mHeader.setQSPanel(mQSPanelController.getView());
+ mFooter.setQSPanel(mQSPanelController.getView());
mQSDetail.setHost(qsh);
-
- if (mQSAnimator != null) {
- mQSAnimator.setHost(qsh);
- }
}
@Override
@@ -278,7 +277,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
private void updateQsState() {
final boolean expandVisually = mQsExpanded || mStackScrollerOverscrolling
|| mHeaderAnimating;
- mQSPanel.setExpanded(mQsExpanded);
+ mQSPanelController.setExpanded(mQsExpanded);
mQSDetail.setExpanded(mQsExpanded);
boolean keyguardShowing = isKeyguardShowing();
mHeader.setVisibility((mQsExpanded || !keyguardShowing || mHeaderAnimating
@@ -294,7 +293,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
: View.INVISIBLE);
mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
|| (mQsExpanded && !mStackScrollerOverscrolling));
- mQSPanel.setVisibility(!mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
+ mQSPanelController.setVisibility(
+ !mQsDisabled && expandVisually ? View.VISIBLE : View.INVISIBLE);
}
private boolean isKeyguardShowing() {
@@ -317,17 +317,17 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
}
}
- public QSPanel getQsPanel() {
- return mQSPanel;
+ public QSPanelController getQSPanelController() {
+ return mQSPanelController;
}
- public QSCustomizer getCustomizer() {
- return mQSCustomizer;
+ public QSPanel getQsPanel() {
+ return mQSPanelController.getView();
}
@Override
public boolean isShowingDetail() {
- return mQSPanel.isShowingCustomize() || mQSDetail.isShowingDetail();
+ return mQSCustomizerController.isCustomizing() || mQSDetail.isShowingDetail();
}
@Override
@@ -339,7 +339,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
public void setExpanded(boolean expanded) {
if (DEBUG) Log.d(TAG, "setExpanded " + expanded);
mQsExpanded = expanded;
- mQSPanel.setListening(mListening, mQsExpanded);
+ mQSPanelController.setListening(mListening, mQsExpanded);
updateQsState();
}
@@ -368,7 +368,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mListening = listening;
mQSContainerImplController.setListening(listening);
mFooter.setListening(listening);
- mQSPanel.setListening(mListening, mQsExpanded);
+ mQSPanelController.setListening(mListening, mQsExpanded);
}
@Override
@@ -406,11 +406,15 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
float panelTranslationY = translationScaleY * heightDiff;
// Let the views animate their contents correctly by giving them the necessary context.
- mHeader.setExpansion(onKeyguardAndExpanded, expansion,
- panelTranslationY);
+ mHeader.setExpansion(onKeyguardAndExpanded, expansion, panelTranslationY);
+ if (expansion < 1 && expansion > 0.99) {
+ if (mQuickQSPanelController.switchTileLayout(false)) {
+ mHeader.updateResources();
+ }
+ }
mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
- mQSPanel.getQsTileRevealController().setExpansion(expansion);
- mQSPanel.getTileLayout().setExpansion(expansion);
+ mQSPanelController.getQsTileRevealController().setExpansion(expansion);
+ mQSPanelController.getTileLayout().setExpansion(expansion);
mQSPanelScrollView.setTranslationY(translationScaleY * heightDiff);
if (fullyCollapsed) {
mQSPanelScrollView.setScrollY(0);
@@ -448,7 +452,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
+ mQSPanelScrollView.getScrollRange();
// The expanded media host should never move below the laid out position
- pinToBottom(expandedMediaPosition, mQSPanel.getMediaHost(), true /* expanded */);
+ pinToBottom(
+ expandedMediaPosition, mQSPanelController.getMediaHost(), true /* expanded */);
// The expanded media host should never move above the laid out position
pinToBottom(absoluteBottomPosition, mHeader.getHeaderQsPanel().getMediaHost(),
false /* expanded */);
@@ -538,27 +543,28 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
@Override
public void closeDetail() {
- mQSPanel.closeDetail();
+ mQSPanelController.closeDetail();
}
public void notifyCustomizeChanged() {
// The customize state changed, so our height changed.
mContainer.updateExpansion();
- mQSPanelScrollView.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE
+ mQSPanelScrollView.setVisibility(!mQSCustomizerController.isCustomizing() ? View.VISIBLE
: View.INVISIBLE);
- mFooter.setVisibility(!mQSCustomizer.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
+ mFooter.setVisibility(
+ !mQSCustomizerController.isCustomizing() ? View.VISIBLE : View.INVISIBLE);
// Let the panel know the position changed and it needs to update where notifications
// and whatnot are.
mPanelView.onQsHeightChanged();
}
/**
- * The height this view wants to be. This is different from {@link #getMeasuredHeight} such that
- * during closing the detail panel, this already returns the smaller height.
+ * The height this view wants to be. This is different from {@link View#getMeasuredHeight} such
+ * that during closing the detail panel, this already returns the smaller height.
*/
@Override
public int getDesiredHeight() {
- if (mQSCustomizer.isCustomizing()) {
+ if (mQSCustomizerController.isCustomizing()) {
return getView().getHeight();
}
if (mQSDetail.isClosingDetail()) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 61c6d3a629ab..758e0c566e5d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -16,16 +16,15 @@
package com.android.systemui.qs;
+import static com.android.systemui.media.dagger.MediaModule.QS_PANEL;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
-import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -42,41 +41,28 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.RemeasuringLinearLayout;
import com.android.systemui.Dependency;
-import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.qs.QSTileView;
-import com.android.systemui.qs.QSHost.Callback;
-import com.android.systemui.qs.customize.QSCustomizer;
-import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
-import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.animation.DisappearParameters;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Named;
/** View that represents the quick settings tile panel (when expanded/pulled down). **/
-public class QSPanel extends LinearLayout implements Tunable, Callback, BrightnessMirrorListener,
- Dumpable {
+public class QSPanel extends LinearLayout implements Tunable, BrightnessMirrorListener {
public static final String QS_SHOW_BRIGHTNESS = "qs_show_brightness";
public static final String QS_SHOW_HEADER = "qs_show_header";
@@ -84,24 +70,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private static final String TAG = "QSPanel";
protected final Context mContext;
- protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
- private final BroadcastDispatcher mBroadcastDispatcher;
protected final MediaHost mMediaHost;
/**
* The index where the content starts that needs to be moved between parents
*/
private final int mMovableContentStartIndex;
- private String mCachedSpecs = "";
@Nullable
protected View mBrightnessView;
- @Nullable
- private BrightnessController mBrightnessController;
private final H mHandler = new H();
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- private QSTileRevealController mQsTileRevealController;
/** Whether or not the QS media player feature is enabled. */
protected boolean mUsingMediaPlayer;
private int mVisualMarginStart;
@@ -111,14 +91,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected boolean mListening;
private QSDetail.Callback mCallback;
- private final DumpManager mDumpManager;
private final QSLogger mQSLogger;
protected final UiEventLogger mUiEventLogger;
protected QSTileHost mHost;
- private final UserTracker mUserTracker;
+ private final List<OnConfigurationChangedListener> mOnConfigurationChangedListeners =
+ new ArrayList<>();
@Nullable
- protected QSSecurityFooter mSecurityFooter;
+ protected View mSecurityFooter;
@Nullable
protected View mFooter;
@@ -134,7 +114,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private int mVisualTilePadding;
private boolean mUsingHorizontalLayout;
- private QSCustomizer mCustomizePanel;
private Record mDetailRecord;
private BrightnessMirrorController mBrightnessMirrorController;
@@ -155,12 +134,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
public QSPanel(
@Named(VIEW_CONTEXT) Context context,
AttributeSet attrs,
- DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- MediaHost mediaHost,
- UiEventLogger uiEventLogger,
- UserTracker userTracker
+ @Named(QS_PANEL) MediaHost mediaHost,
+ UiEventLogger uiEventLogger
) {
super(context, attrs);
mUsingMediaPlayer = useQsMediaPlayer(context);
@@ -173,16 +149,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
});
mContext = context;
mQSLogger = qsLogger;
- mDumpManager = dumpManager;
- mBroadcastDispatcher = broadcastDispatcher;
mUiEventLogger = uiEventLogger;
- mUserTracker = userTracker;
setOrientation(VERTICAL);
addViewsAboveTiles();
mMovableContentStartIndex = getChildCount();
mRegularTileLayout = createRegularTileLayout();
+ mTileLayout = mRegularTileLayout;
if (mUsingMediaPlayer) {
mHorizontalLinearLayout = new RemeasuringLinearLayout(mContext);
@@ -208,35 +182,23 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
initMediaHostState();
}
- addSecurityFooter();
- if (mRegularTileLayout instanceof PagedTileLayout) {
- mQsTileRevealController = new QSTileRevealController(mContext, this,
- (PagedTileLayout) mRegularTileLayout);
- }
- mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), mCachedSpecs);
- updateResources();
+ mQSLogger.logAllTilesChangeListening(mListening, getDumpableTag(), "");
}
protected void onMediaVisibilityChanged(Boolean visible) {
- switchTileLayout();
if (mMediaVisibilityChangedListener != null) {
mMediaVisibilityChangedListener.accept(visible);
}
}
- protected void addSecurityFooter() {
- mSecurityFooter = new QSSecurityFooter(this, mContext, mUserTracker);
- }
-
protected void addViewsAboveTiles() {
mBrightnessView = LayoutInflater.from(mContext).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
addView(mBrightnessView);
- mBrightnessController = new BrightnessController(getContext(),
- findViewById(R.id.brightness_slider), mBroadcastDispatcher);
}
- protected QSTileLayout createRegularTileLayout() {
+ /** */
+ public QSTileLayout createRegularTileLayout() {
if (mRegularTileLayout == null) {
mRegularTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
R.layout.qs_paged_tile_layout, this, false);
@@ -327,46 +289,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
setMeasuredDimension(getMeasuredWidth(), height);
}
- public QSTileRevealController getQsTileRevealController() {
- return mQsTileRevealController;
- }
-
- public boolean isShowingCustomize() {
- return mCustomizePanel != null && mCustomizePanel.isCustomizing();
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- final TunerService tunerService = Dependency.get(TunerService.class);
- tunerService.addTunable(this, QS_SHOW_BRIGHTNESS);
-
- if (mHost != null) {
- setTiles(mHost.getTiles());
- }
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.addCallback(this);
- }
- mDumpManager.registerDumpable(getDumpableTag(), this);
- }
-
@Override
protected void onDetachedFromWindow() {
- Dependency.get(TunerService.class).removeTunable(this);
- if (mHost != null) {
- mHost.removeCallback(this);
- }
if (mTileLayout != null) {
mTileLayout.setListening(false);
}
- for (TileRecord record : mRecords) {
- record.tile.removeCallbacks();
- }
- mRecords.clear();
- if (mBrightnessMirrorController != null) {
- mBrightnessMirrorController.removeCallback(this);
- }
- mDumpManager.unregisterDumpable(getDumpableTag());
super.onDetachedFromWindow();
}
@@ -375,11 +302,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
@Override
- public void onTilesChanged() {
- setTiles(mHost.getTiles());
- }
-
- @Override
public void onTuningChanged(String key, String newValue) {
if (QS_SHOW_BRIGHTNESS.equals(key) && mBrightnessView != null) {
updateViewVisibilityForTuningValue(mBrightnessView, newValue);
@@ -390,8 +312,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
view.setVisibility(TunerService.parseIntegerSwitch(newValue, true) ? VISIBLE : GONE);
}
- public void openDetails(String subPanel) {
- QSTile tile = getTile(subPanel);
+ /** */
+ public void openDetails(QSTile tile) {
// If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
// QSFactory will not be able to create a tile and getTile will return null
if (tile != null) {
@@ -399,15 +321,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
- private QSTile getTile(String subPanel) {
- for (int i = 0; i < mRecords.size(); i++) {
- if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
- return mRecords.get(i).tile;
- }
- }
- return mHost.createTile(subPanel);
- }
-
public void setBrightnessMirror(BrightnessMirrorController c) {
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.removeCallback(this);
@@ -433,19 +346,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mCallback = callback;
}
- public void setHost(QSTileHost host, QSCustomizer customizer) {
- mHost = host;
- mHost.addCallback(this);
- setTiles(mHost.getTiles());
- if (mSecurityFooter != null) {
- mSecurityFooter.setHostEnvironment(host);
- }
- mCustomizePanel = customizer;
- if (mCustomizePanel != null) {
- mCustomizePanel.setHost(mHost);
- }
- }
-
/**
* Links the footer's page indicator, which is used in landscape orientation to save space.
*
@@ -482,12 +382,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
updatePageIndicator();
- for (TileRecord r : mRecords) {
- r.tile.clearState();
- }
- if (mListening) {
- refreshAllTiles();
- }
if (mTileLayout != null) {
mTileLayout.updateResources();
}
@@ -508,20 +402,21 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
}
+ void addOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
+ mOnConfigurationChangedListeners.add(listener);
+ }
+
+ void removeOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
+ mOnConfigurationChangedListeners.remove(listener);
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- if (mSecurityFooter != null) {
- mSecurityFooter.onConfigurationChanged();
- }
- updateResources();
+ mOnConfigurationChangedListeners.forEach(
+ listener -> listener.onConfigurationChange(newConfig));
updateBrightnessMirror();
-
- if (newConfig.orientation != mLastOrientation) {
- mLastOrientation = newConfig.orientation;
- switchTileLayout();
- }
}
@Override
@@ -529,14 +424,9 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
super.onFinishInflate();
mFooter = findViewById(R.id.qs_footer);
mDivider = findViewById(R.id.divider);
- switchTileLayout(true /* force */);
}
- boolean switchTileLayout() {
- return switchTileLayout(false /* force */);
- }
-
- private boolean switchTileLayout(boolean force) {
+ boolean switchTileLayout(boolean force, List<QSPanelControllerBase.TileRecord> records) {
/** Whether or not the QuickQSPanel currently contains a media player. */
boolean horizontal = shouldUseHorizontalLayout();
if (mDivider != null) {
@@ -564,14 +454,12 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
reAttachMediaHost();
if (mTileLayout != null) {
mTileLayout.setListening(false);
- for (TileRecord record : mRecords) {
+ for (QSPanelControllerBase.TileRecord record : records) {
mTileLayout.removeTile(record);
record.tile.removeCallback(record.callback);
}
}
mTileLayout = newLayout;
- if (mHost != null) setTiles(mHost.getTiles());
- newLayout.setListening(mListening);
if (needsDynamicRowsAndColumns()) {
newLayout.setMinRows(horizontal ? 2 : 1);
// Let's use 3 columns to match the current layout
@@ -589,6 +477,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return false;
}
+ /**
+ * Sets the listening state of the current layout to the state of the view. Used after
+ * switching layouts.
+ */
+ public void reSetLayoutListening() {
+ mTileLayout.setListening(mListening);
+ }
+
private void updateHorizontalLinearLayoutMargins() {
if (mHorizontalLinearLayout != null && !displayMediaMarginsOnMedia()) {
LayoutParams lp = (LayoutParams) mHorizontalLinearLayout.getLayoutParams();
@@ -619,20 +515,20 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
index++;
if (mSecurityFooter != null) {
- View view = mSecurityFooter.getView();
- LinearLayout.LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ LinearLayout.LayoutParams layoutParams =
+ (LayoutParams) mSecurityFooter.getLayoutParams();
if (mUsingHorizontalLayout && mHeaderContainer != null) {
// Adding the security view to the header, that enables us to avoid scrolling
layoutParams.width = 0;
layoutParams.weight = 1.6f;
- switchToParent(view, mHeaderContainer, 1 /* always in second place */);
+ switchToParent(mSecurityFooter, mHeaderContainer, 1 /* always in second place */);
} else {
layoutParams.width = LayoutParams.WRAP_CONTENT;
layoutParams.weight = 0;
- switchToParent(view, parent, index);
+ switchToParent(mSecurityFooter, parent, index);
index++;
}
- view.setLayoutParams(layoutParams);
+ mSecurityFooter.setLayoutParams(layoutParams);
}
if (mFooter != null) {
@@ -692,12 +588,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
- public void onCollapse() {
- if (mCustomizePanel != null && mCustomizePanel.isShown()) {
- mCustomizePanel.hide();
- }
- }
-
public void setExpanded(boolean expanded) {
if (mExpanded == expanded) return;
mQSLogger.logPanelExpanded(expanded, getDumpableTag());
@@ -705,14 +595,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
}
- mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
- if (!mExpanded) {
- mUiEventLogger.log(closePanelEvent());
- closeDetail();
- } else {
- mUiEventLogger.log(openPanelEvent());
- logTiles();
- }
}
public void setPageListener(final PagedTileLayout.PageListener pageListener) {
@@ -725,56 +607,16 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return mExpanded;
}
- public void setListening(boolean listening) {
+ /** */
+ public void setListening(boolean listening, String cachedSpecs) {
if (mListening == listening) return;
mListening = listening;
if (mTileLayout != null) {
- mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), mCachedSpecs);
+ mQSLogger.logAllTilesChangeListening(listening, getDumpableTag(), cachedSpecs);
mTileLayout.setListening(listening);
}
- if (mListening) {
- refreshAllTiles();
- }
}
- private String getTilesSpecs() {
- return mRecords.stream()
- .map(tileRecord -> tileRecord.tile.getTileSpec())
- .collect(Collectors.joining(","));
- }
-
- public void setListening(boolean listening, boolean expanded) {
- setListening(listening && expanded);
- if (mSecurityFooter != null) {
- mSecurityFooter.setListening(listening);
- }
- // Set the listening as soon as the QS fragment starts listening regardless of the expansion,
- // so it will update the current brightness before the slider is visible.
- setBrightnessListening(listening);
- }
-
- public void setBrightnessListening(boolean listening) {
- if (mBrightnessController == null) {
- return;
- }
- if (listening) {
- mBrightnessController.registerCallbacks();
- } else {
- mBrightnessController.unregisterCallbacks();
- }
- }
-
- public void refreshAllTiles() {
- if (mBrightnessController != null) {
- mBrightnessController.checkRestrictionAndSetEnabled();
- }
- for (TileRecord r : mRecords) {
- r.tile.refreshState();
- }
- if (mSecurityFooter != null) {
- mSecurityFooter.refreshState();
- }
- }
public void showDetailAdapter(boolean show, DetailAdapter adapter, int[] locationInWindow) {
int xInWindow = locationInWindow[0];
@@ -796,33 +638,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
}
- public void setTiles(Collection<QSTile> tiles) {
- setTiles(tiles, false);
- }
-
- public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
- if (!collapsedView) {
- mQsTileRevealController.updateRevealedTiles(tiles);
- }
- for (TileRecord record : mRecords) {
- mTileLayout.removeTile(record);
- record.tile.removeCallback(record.callback);
- }
- mRecords.clear();
- mCachedSpecs = "";
- for (QSTile tile : tiles) {
- addTile(tile, collapsedView);
- }
- }
-
- protected void drawTile(TileRecord r, QSTile.State state) {
+ protected void drawTile(QSPanelControllerBase.TileRecord r, QSTile.State state) {
r.tileView.onStateChanged(state);
}
- protected QSTileView createTileView(QSTile tile, boolean collapsedView) {
- return mHost.createTileView(tile, collapsedView);
- }
-
protected QSEvent openPanelEvent() {
return QSEvent.QS_PANEL_EXPANDED;
}
@@ -839,14 +658,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return mExpanded;
}
- protected TileRecord addTile(final QSTile tile, boolean collapsedView) {
- final TileRecord r = new TileRecord();
- r.tile = tile;
- r.tileView = createTileView(tile, collapsedView);
+ void addTile(QSPanelControllerBase.TileRecord tileRecord) {
final QSTile.Callback callback = new QSTile.Callback() {
@Override
public void onStateChanged(QSTile.State state) {
- drawTile(r, state);
+ drawTile(tileRecord, state);
}
@Override
@@ -854,22 +670,22 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
// Both the collapsed and full QS panels get this callback, this check determines
// which one should handle showing the detail.
if (shouldShowDetail()) {
- QSPanel.this.showDetail(show, r);
+ QSPanel.this.showDetail(show, tileRecord);
}
}
@Override
public void onToggleStateChanged(boolean state) {
- if (mDetailRecord == r) {
+ if (mDetailRecord == tileRecord) {
fireToggleStateChanged(state);
}
}
@Override
public void onScanStateChanged(boolean state) {
- r.scanState = state;
- if (mDetailRecord == r) {
- fireScanStateChanged(r.scanState);
+ tileRecord.scanState = state;
+ if (mDetailRecord == tileRecord) {
+ fireScanStateChanged(tileRecord.scanState);
}
}
@@ -881,44 +697,22 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
};
- r.tile.addCallback(callback);
- r.callback = callback;
- r.tileView.init(r.tile);
- r.tile.refreshState();
- mRecords.add(r);
- mCachedSpecs = getTilesSpecs();
+
+ tileRecord.tile.addCallback(callback);
+ tileRecord.callback = callback;
+ tileRecord.tileView.init(tileRecord.tile);
+ tileRecord.tile.refreshState();
if (mTileLayout != null) {
- mTileLayout.addTile(r);
+ mTileLayout.addTile(tileRecord);
}
-
- return r;
}
-
- public void showEdit(final View v) {
- v.post(new Runnable() {
- @Override
- public void run() {
- if (mCustomizePanel != null) {
- if (!mCustomizePanel.isCustomizing()) {
- int[] loc = v.getLocationOnScreen();
- int x = loc[0] + v.getWidth() / 2;
- int y = loc[1] + v.getHeight() / 2;
- mCustomizePanel.show(x, y);
- }
- }
-
- }
- });
+ void removeTile(QSPanelControllerBase.TileRecord tileRecord) {
+ mTileLayout.removeTile(tileRecord);
}
- public void closeDetail() {
- if (mCustomizePanel != null && mCustomizePanel.isShown()) {
- // Treat this as a detail panel for now, to make things easy.
- mCustomizePanel.hide();
- return;
- }
+ void closeDetail() {
showDetail(false, mDetailRecord);
}
@@ -927,8 +721,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
protected void handleShowDetail(Record r, boolean show) {
- if (r instanceof TileRecord) {
- handleShowDetailTile((TileRecord) r, show);
+ if (r instanceof QSPanelControllerBase.TileRecord) {
+ handleShowDetailTile((QSPanelControllerBase.TileRecord) r, show);
} else {
int x = 0;
int y = 0;
@@ -940,7 +734,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
- private void handleShowDetailTile(TileRecord r, boolean show) {
+ private void handleShowDetailTile(QSPanelControllerBase.TileRecord r, boolean show) {
if ((mDetailRecord != null) == show && mDetailRecord == r) return;
if (show) {
@@ -961,8 +755,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected void setDetailRecord(Record r) {
if (r == mDetailRecord) return;
mDetailRecord = r;
- final boolean scanState = mDetailRecord instanceof TileRecord
- && ((TileRecord) mDetailRecord).scanState;
+ final boolean scanState = mDetailRecord instanceof QSPanelControllerBase.TileRecord
+ && ((QSPanelControllerBase.TileRecord) mDetailRecord).scanState;
fireScanStateChanged(scanState);
}
@@ -974,15 +768,6 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
mGridContentVisible = visible;
}
-
- private void logTiles() {
- for (int i = 0; i < mRecords.size(); i++) {
- QSTile tile = mRecords.get(i).tile;
- mMetricsLogger.write(tile.populate(new LogMaker(tile.getMetricsCategory())
- .setType(MetricsEvent.TYPE_OPEN)));
- }
- }
-
private void fireShowingDetail(DetailAdapter detail, int x, int y) {
if (mCallback != null) {
mCallback.onShowingDetail(detail, x, y);
@@ -1001,46 +786,15 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
- public void clickTile(ComponentName tile) {
- final String spec = CustomTile.toSpec(tile);
- final int N = mRecords.size();
- for (int i = 0; i < N; i++) {
- if (mRecords.get(i).tile.getTileSpec().equals(spec)) {
- mRecords.get(i).tile.click();
- break;
- }
- }
- }
-
QSTileLayout getTileLayout() {
return mTileLayout;
}
- QSTileView getTileView(QSTile tile) {
- for (TileRecord r : mRecords) {
- if (r.tile == tile) {
- return r.tileView;
- }
- }
- return null;
- }
-
- @Nullable
- public QSSecurityFooter getSecurityFooter() {
- return mSecurityFooter;
- }
-
@Nullable
public View getDivider() {
return mDivider;
}
- public void showDeviceMonitoringDialog() {
- if (mSecurityFooter != null) {
- mSecurityFooter.showDeviceMonitoringDialog();
- }
- }
-
public void setContentMargins(int startMargin, int endMargin) {
// Only some views actually want this content padding, others want to go all the way
// to the edge like the brightness slider
@@ -1123,9 +877,11 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
*/
protected void updateMargins(View view, int start, int end) {
LayoutParams lp = (LayoutParams) view.getLayoutParams();
- lp.setMarginStart(start);
- lp.setMarginEnd(end);
- view.setLayoutParams(lp);
+ if (lp != null) {
+ lp.setMarginStart(start);
+ lp.setMarginEnd(end);
+ view.setLayoutParams(lp);
+ }
}
public MediaHost getMediaHost() {
@@ -1143,6 +899,14 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mMediaVisibilityChangedListener = visibilityChangedListener;
}
+ public boolean isListening() {
+ return mListening;
+ }
+
+ public void setSecurityFooter(View view) {
+ mSecurityFooter = view;
+ }
+
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
@@ -1158,46 +922,32 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
}
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(getClass().getSimpleName() + ":");
- pw.println(" Tile records:");
- for (TileRecord record : mRecords) {
- if (record.tile instanceof Dumpable) {
- pw.print(" "); ((Dumpable) record.tile).dump(fd, pw, args);
- pw.print(" "); pw.println(record.tileView.toString());
- }
- }
- }
-
-
protected static class Record {
DetailAdapter detailAdapter;
int x;
int y;
}
- public static final class TileRecord extends Record {
- public QSTile tile;
- public com.android.systemui.plugins.qs.QSTileView tileView;
- public boolean scanState;
- public QSTile.Callback callback;
- }
-
public interface QSTileLayout {
-
+ /** */
default void saveInstanceState(Bundle outState) {}
+ /** */
default void restoreInstanceState(Bundle savedInstanceState) {}
- void addTile(TileRecord tile);
+ /** */
+ void addTile(QSPanelControllerBase.TileRecord tile);
- void removeTile(TileRecord tile);
+ /** */
+ void removeTile(QSPanelControllerBase.TileRecord tile);
- int getOffsetTop(TileRecord tile);
+ /** */
+ int getOffsetTop(QSPanelControllerBase.TileRecord tile);
+ /** */
boolean updateResources();
+ /** */
void setListening(boolean listening);
/**
@@ -1224,4 +974,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
int getNumVisibleTiles();
}
+
+ interface OnConfigurationChangedListener {
+ void onConfigurationChange(Configuration newConfig);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
new file mode 100644
index 000000000000..e9670a959cee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -0,0 +1,212 @@
+/*
+ * 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 static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
+
+import android.annotation.NonNull;
+import android.content.res.Configuration;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.settings.BrightnessController;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.tuner.TunerService;
+
+import javax.inject.Inject;
+
+/**
+ * Controller for {@link QSPanel}.
+ */
+@QSScope
+public class QSPanelController extends QSPanelControllerBase<QSPanel> {
+ private final QSSecurityFooter mQsSecurityFooter;
+ private final TunerService mTunerService;
+ private final QSCustomizerController mQsCustomizerController;
+ private final BrightnessController mBrightnessController;
+
+ private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
+ new QSPanel.OnConfigurationChangedListener() {
+ @Override
+ public void onConfigurationChange(Configuration newConfig) {
+ mView.updateResources();
+ mQsSecurityFooter.onConfigurationChanged();
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+ }
+ };
+ private BrightnessMirrorController mBrightnessMirrorController;
+
+ @Inject
+ QSPanelController(QSPanel view, QSSecurityFooter qsSecurityFooter, TunerService tunerService,
+ QSTileHost qstileHost, QSCustomizerController qsCustomizerController,
+ QSTileRevealController.Factory qsTileRevealControllerFactory,
+ DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+ BrightnessController.Factory brightnessControllerFactory) {
+ super(view, qstileHost, qsCustomizerController, qsTileRevealControllerFactory,
+ metricsLogger, uiEventLogger, dumpManager);
+ mQsSecurityFooter = qsSecurityFooter;
+ mTunerService = tunerService;
+ mQsCustomizerController = qsCustomizerController;
+ mQsSecurityFooter.setHostEnvironment(qstileHost);
+ mBrightnessController = brightnessControllerFactory.create(
+ mView.findViewById(R.id.brightness_slider));
+ }
+
+ @Override
+ public void onInit() {
+ mQsCustomizerController.init();
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ mTunerService.addTunable(mView, QS_SHOW_BRIGHTNESS);
+ mView.updateResources();
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+ mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mView.setSecurityFooter(mQsSecurityFooter.getView());
+ switchTileLayout(true);
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.addCallback(mView);
+ }
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mTunerService.removeTunable(mView);
+ mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ if (mBrightnessMirrorController != null) {
+ mBrightnessMirrorController.removeCallback(mView);
+ }
+ super.onViewDetached();
+ }
+
+ /** TODO(b/168904199): Remove this method once view is controllerized. */
+ QSPanel getView() {
+ return mView;
+ }
+
+ /**
+ * Set the header container of quick settings.
+ */
+ public void setHeaderContainer(@NonNull ViewGroup headerContainer) {
+ mView.setHeaderContainer(headerContainer);
+ }
+
+ public QSPanel.QSTileLayout getTileLayout() {
+ return mView.getTileLayout();
+ }
+
+ /** */
+ public void setVisibility(int visibility) {
+ mView.setVisibility(visibility);
+ }
+
+ /** */
+ public void setListening(boolean listening, boolean expanded) {
+ setListening(listening && expanded);
+ if (mView.isListening()) {
+ refreshAllTiles();
+ }
+
+ mQsSecurityFooter.setListening(listening);
+
+ // Set the listening as soon as the QS fragment starts listening regardless of the
+ //expansion, so it will update the current brightness before the slider is visible.
+ if (listening) {
+ mBrightnessController.registerCallbacks();
+ } else {
+ mBrightnessController.unregisterCallbacks();
+ }
+ }
+
+ /** */
+ public MediaHost getMediaHost() {
+ return mView.getMediaHost();
+ }
+
+ /** */
+ public void setBrightnessMirror(BrightnessMirrorController brightnessMirrorController) {
+ mBrightnessMirrorController = brightnessMirrorController;
+ mView.setBrightnessMirror(brightnessMirrorController);
+ }
+
+ /** Get the QSTileHost this panel uses. */
+ public QSTileHost getHost() {
+ return mHost;
+ }
+
+
+ /** Open the details for a specific tile.. */
+ public void openDetails(String subPanel) {
+ QSTile tile = getTile(subPanel);
+ if (tile != null) {
+ mView.openDetails(tile);
+ }
+ }
+
+ /** Show the device monitoring dialog. */
+ public void showDeviceMonitoringDialog() {
+ mQsSecurityFooter.showDeviceMonitoringDialog();
+ }
+
+ /** Update appearance of QSPanel. */
+ public void updateResources() {
+ mView.updateResources();
+ }
+
+ /** Update state of all tiles. */
+ public void refreshAllTiles() {
+ mBrightnessController.checkRestrictionAndSetEnabled();
+ super.refreshAllTiles();
+ mQsSecurityFooter.refreshState();
+ }
+
+ /** Start customizing the Quick Settings. */
+ public void showEdit(View view) {
+ view.post(() -> {
+ if (!mQsCustomizerController.isCustomizing()) {
+ int[] loc = view.getLocationOnScreen();
+ int x = loc[0] + view.getWidth() / 2;
+ int y = loc[1] + view.getHeight() / 2;
+ mQsCustomizerController.show(x, y, false);
+ }
+ });
+ }
+
+ /** */
+ public void setCallback(QSDetail.Callback qsPanelCallback) {
+ mView.setCallback(qsPanelCallback);
+ }
+
+ /** */
+ public void setGridContentVisibility(boolean visible) {
+ mView.setGridContentVisibility(visible);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
new file mode 100644
index 000000000000..0a4151b38210
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -0,0 +1,273 @@
+/*
+ * 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 static com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import android.content.ComponentName;
+import android.content.res.Configuration;
+import android.metrics.LogMaker;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaHost;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.qs.QSTileView;
+import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.external.CustomTile;
+import com.android.systemui.util.ViewController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+/**
+ * Controller for QSPanel views.
+ *
+ * @param <T> Type of QSPanel.
+ */
+public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewController<T>
+ implements Dumpable{
+ protected final QSTileHost mHost;
+ private final QSCustomizerController mQsCustomizerController;
+ private final QSTileRevealController.Factory mQsTileRevealControllerFactory;
+ private final MediaHost mMediaHost;
+ private final MetricsLogger mMetricsLogger;
+ private final UiEventLogger mUiEventLogger;
+ private final DumpManager mDumpManager;
+ protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+
+ private int mLastOrientation;
+ private QSTileRevealController mQsTileRevealController;
+
+ private final QSHost.Callback mQSHostCallback = this::setTiles;
+
+ private final QSPanel.OnConfigurationChangedListener mOnConfigurationChangedListener =
+ new QSPanel.OnConfigurationChangedListener() {
+ @Override
+ public void onConfigurationChange(Configuration newConfig) {
+ if (newConfig.orientation != mLastOrientation) {
+ mLastOrientation = newConfig.orientation;
+ switchTileLayout(false);
+ }
+ }
+ };
+ private String mCachedSpecs = "";
+
+ protected QSPanelControllerBase(T view, QSTileHost host,
+ QSCustomizerController qsCustomizerController,
+ QSTileRevealController.Factory qsTileRevealControllerFactory,
+ MetricsLogger metricsLogger, UiEventLogger uiEventLogger, DumpManager dumpManager) {
+ super(view);
+ mHost = host;
+ mQsCustomizerController = qsCustomizerController;
+ mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
+ mMediaHost = mView.getMediaHost();
+ mMetricsLogger = metricsLogger;
+ mUiEventLogger = uiEventLogger;
+ mDumpManager = dumpManager;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ QSPanel.QSTileLayout regularTileLayout = mView.createRegularTileLayout();
+ if (regularTileLayout instanceof PagedTileLayout) {
+ mQsTileRevealController = mQsTileRevealControllerFactory.create(
+ (PagedTileLayout) regularTileLayout);
+ }
+
+ mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mHost.addCallback(mQSHostCallback);
+ mMediaHost.addVisibilityChangeListener(aBoolean -> {
+ switchTileLayout(false);
+ return null;
+ });
+ setTiles();
+ switchTileLayout(true);
+ mDumpManager.registerDumpable(mView.getDumpableTag(), this);
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mView.removeOnConfigurationChangedListener(mOnConfigurationChangedListener);
+ mHost.removeCallback(mQSHostCallback);
+
+ for (TileRecord record : mRecords) {
+ record.tile.removeCallbacks();
+ }
+ mRecords.clear();
+ mDumpManager.unregisterDumpable(mView.getDumpableTag());
+ }
+
+ /** */
+ public void setTiles() {
+ setTiles(mHost.getTiles(), false);
+ }
+
+ /** */
+ public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
+ if (!collapsedView) {
+ mQsTileRevealController.updateRevealedTiles(tiles);
+ }
+ for (QSPanelControllerBase.TileRecord record : mRecords) {
+ mView.removeTile(record);
+ record.tile.removeCallback(record.callback);
+ }
+ mRecords.clear();
+ mCachedSpecs = "";
+ for (QSTile tile : tiles) {
+ addTile(tile, collapsedView);
+ }
+ }
+
+ /** */
+ public void refreshAllTiles() {
+ for (QSPanelControllerBase.TileRecord r : mRecords) {
+ r.tile.refreshState();
+ }
+ }
+
+ private void addTile(final QSTile tile, boolean collapsedView) {
+ final TileRecord r = new TileRecord();
+ r.tile = tile;
+ r.tileView = mHost.createTileView(tile, collapsedView);
+ mView.addTile(r);
+ mRecords.add(r);
+ mCachedSpecs = getTilesSpecs();
+
+ }
+
+ /** */
+ public void clickTile(ComponentName tile) {
+ final String spec = CustomTile.toSpec(tile);
+ for (TileRecord record : mRecords) {
+ if (record.tile.getTileSpec().equals(spec)) {
+ record.tile.click();
+ break;
+ }
+ }
+ }
+ protected QSTile getTile(String subPanel) {
+ for (int i = 0; i < mRecords.size(); i++) {
+ if (subPanel.equals(mRecords.get(i).tile.getTileSpec())) {
+ return mRecords.get(i).tile;
+ }
+ }
+ return mHost.createTile(subPanel);
+ }
+
+
+ QSTileView getTileView(QSTile tile) {
+ for (QSPanelControllerBase.TileRecord r : mRecords) {
+ if (r.tile == tile) {
+ return r.tileView;
+ }
+ }
+ return null;
+ }
+
+ private String getTilesSpecs() {
+ return mRecords.stream()
+ .map(tileRecord -> tileRecord.tile.getTileSpec())
+ .collect(Collectors.joining(","));
+ }
+
+
+ /** */
+ public void setExpanded(boolean expanded) {
+ mView.setExpanded(expanded);
+ mMetricsLogger.visibility(MetricsEvent.QS_PANEL, expanded);
+ if (!expanded) {
+ mUiEventLogger.log(mView.closePanelEvent());
+ closeDetail();
+ } else {
+ mUiEventLogger.log(mView.openPanelEvent());
+ logTiles();
+ }
+ }
+
+ /** */
+ public void closeDetail() {
+ if (mQsCustomizerController.isShown()) {
+ mQsCustomizerController.hide();
+ return;
+ }
+ mView.closeDetail();
+ }
+
+ /** */
+ public void openDetails(String subPanel) {
+ QSTile tile = getTile(subPanel);
+ // If there's no tile with that name (as defined in QSFactoryImpl or other QSFactory),
+ // QSFactory will not be able to create a tile and getTile will return null
+ if (tile != null) {
+ mView.showDetailAdapter(
+ true, tile.getDetailAdapter(), new int[]{mView.getWidth() / 2, 0});
+ }
+ }
+
+
+ void setListening(boolean listening) {
+ mView.setListening(listening, mCachedSpecs);
+ }
+
+ boolean switchTileLayout(boolean force) {
+ if (mView.switchTileLayout(force, mRecords)) {
+ setTiles();
+ mView.reSetLayoutListening();
+ return true;
+ }
+ return false;
+ }
+
+ private void logTiles() {
+ for (int i = 0; i < mRecords.size(); i++) {
+ QSTile tile = mRecords.get(i).tile;
+ mMetricsLogger.write(tile.populate(new LogMaker(tile.getMetricsCategory())
+ .setType(MetricsEvent.TYPE_OPEN)));
+ }
+ }
+
+ /** */
+ public QSTileRevealController getQsTileRevealController() {
+ return mQsTileRevealController;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(getClass().getSimpleName() + ":");
+ pw.println(" Tile records:");
+ for (QSPanelControllerBase.TileRecord record : mRecords) {
+ if (record.tile instanceof Dumpable) {
+ pw.print(" "); ((Dumpable) record.tile).dump(fd, pw, args);
+ pw.print(" "); pw.println(record.tileView.toString());
+ }
+ }
+ }
+
+ /** */
+ public static final class TileRecord extends QSPanel.Record {
+ public QSTile tile;
+ public com.android.systemui.plugins.qs.QSTileView tileView;
+ public boolean scanState;
+ public QSTile.Callback callback;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 0891972c11d2..270fcbffbd71 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -16,11 +16,13 @@
package com.android.systemui.qs;
import android.app.AlertDialog;
+import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyEventLogger;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -39,16 +41,22 @@ import android.view.Window;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.SecurityController;
-public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener {
+import javax.inject.Inject;
+
+@QSScope
+class QSSecurityFooter implements OnClickListener, DialogInterface.OnClickListener {
protected static final String TAG = "QSSecurityFooter";
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean DEBUG_FORCE_VISIBLE = false;
@@ -72,12 +80,13 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
private int mFooterTextId;
private int mFooterIconId;
+ @Inject
public QSSecurityFooter(QSPanel qsPanel, Context context, UserTracker userTracker) {
mRootView = LayoutInflater.from(context)
.inflate(R.layout.quick_settings_footer, qsPanel, false);
mRootView.setOnClickListener(this);
- mFooterText = (TextView) mRootView.findViewById(R.id.footer_text);
- mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon);
+ mFooterText = mRootView.findViewById(R.id.footer_text);
+ mFooterIcon = mRootView.findViewById(R.id.footer_icon);
mFooterIconId = R.drawable.ic_info_outline;
mContext = context;
mMainHandler = new Handler(Looper.myLooper());
@@ -151,15 +160,16 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
mSecurityController.getWorkProfileOrganizationName();
final boolean isProfileOwnerOfOrganizationOwnedDevice =
mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
+ final boolean isParentalControlsEnabled = mSecurityController.isParentalControlsEnabled();
// Update visibility of footer
mIsVisible = (isDeviceManaged && !isDemoDevice) || hasCACerts || hasCACertsInWorkProfile
|| vpnName != null || vpnNameWorkProfile != null
- || isProfileOwnerOfOrganizationOwnedDevice;
+ || isProfileOwnerOfOrganizationOwnedDevice || isParentalControlsEnabled;
// Update the string
mFooterTextContent = getFooterText(isDeviceManaged, hasWorkProfile,
hasCACerts, hasCACertsInWorkProfile, isNetworkLoggingEnabled, vpnName,
vpnNameWorkProfile, organizationName, workProfileOrganizationName,
- isProfileOwnerOfOrganizationOwnedDevice);
+ isProfileOwnerOfOrganizationOwnedDevice, isParentalControlsEnabled);
// Update the icon
int footerIconId = R.drawable.ic_info_outline;
if (vpnName != null || vpnNameWorkProfile != null) {
@@ -180,7 +190,10 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
boolean hasCACerts, boolean hasCACertsInWorkProfile, boolean isNetworkLoggingEnabled,
String vpnName, String vpnNameWorkProfile, CharSequence organizationName,
CharSequence workProfileOrganizationName,
- boolean isProfileOwnerOfOrganizationOwnedDevice) {
+ boolean isProfileOwnerOfOrganizationOwnedDevice, boolean isParentalControlsEnabled) {
+ if (isParentalControlsEnabled) {
+ return mContext.getString(R.string.quick_settings_disclosure_parental_controls);
+ }
if (isDeviceManaged || DEBUG_FORCE_VISIBLE) {
if (hasCACerts || hasCACertsInWorkProfile || isNetworkLoggingEnabled) {
if (organizationName == null) {
@@ -263,6 +276,27 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
}
private void createDialog() {
+ mDialog = new SystemUIDialog(mContext);
+ mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
+ mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getNegativeButton(), this);
+
+ mDialog.setView(createDialogView());
+
+ mDialog.show();
+ mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+
+ @VisibleForTesting
+ View createDialogView() {
+ if (mSecurityController.isParentalControlsEnabled()) {
+ return createParentalControlsDialogView();
+ }
+ return createOrganizationDialogView();
+ }
+
+ private View createOrganizationDialogView() {
final boolean isDeviceManaged = mSecurityController.isDeviceManaged();
boolean isProfileOwnerOfOrganizationOwnedDevice =
mSecurityController.isProfileOwnerOfOrganizationOwnedDevice();
@@ -277,13 +311,10 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
final String vpnName = mSecurityController.getPrimaryVpnName();
final String vpnNameWorkProfile = mSecurityController.getWorkProfileVpnName();
- mDialog = new SystemUIDialog(mContext);
- mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+
View dialogView = LayoutInflater.from(
new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog))
.inflate(R.layout.quick_settings_footer_dialog, null, false);
- mDialog.setView(dialogView);
- mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(), this);
// device management section
CharSequence managementMessage = getManagementMessage(isDeviceManaged,
@@ -348,9 +379,26 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
vpnMessage != null,
dialogView);
- mDialog.show();
- mDialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
+ return dialogView;
+ }
+
+ private View createParentalControlsDialogView() {
+ View dialogView = LayoutInflater.from(
+ new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_Dialog))
+ .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
+
+ DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
+ Drawable icon = mSecurityController.getIcon(info);
+ if (icon != null) {
+ ImageView imageView = (ImageView) dialogView.findViewById(R.id.parental_controls_icon);
+ imageView.setImageDrawable(icon);
+ }
+
+ TextView parentalControlsTitle =
+ (TextView) dialogView.findViewById(R.id.parental_controls_title);
+ parentalControlsTitle.setText(mSecurityController.getLabel(info));
+
+ return dialogView;
}
protected void configSubtitleVisibility(boolean showDeviceManagement, boolean showCaCerts,
@@ -389,6 +437,13 @@ public class QSSecurityFooter implements OnClickListener, DialogInterface.OnClic
return mContext.getString(R.string.ok);
}
+ private String getNegativeButton() {
+ if (mSecurityController.isParentalControlsEnabled()) {
+ return mContext.getString(R.string.monitoring_button_view_controls);
+ }
+ return null;
+ }
+
protected CharSequence getManagementMessage(boolean isDeviceManaged,
CharSequence organizationName, boolean isProfileOwnerOfOrganizationOwnedDevice,
CharSequence workProfileOrganizationName) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
index 2f012e6e608e..9414d0e3ed52 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileRevealController.java
@@ -8,17 +8,23 @@ import android.util.ArraySet;
import com.android.systemui.Prefs;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.dagger.QSScope;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
+import javax.inject.Inject;
+
+/** */
public class QSTileRevealController {
private static final long QS_REVEAL_TILES_DELAY = 500L;
private final Context mContext;
private final QSPanel mQSPanel;
private final PagedTileLayout mPagedTileLayout;
+ private final QSCustomizerController mQsCustomizerController;
private final ArraySet<String> mTilesToReveal = new ArraySet<>();
private final Handler mHandler = new Handler();
@@ -33,11 +39,12 @@ public class QSTileRevealController {
});
}
};
-
- QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout) {
+ QSTileRevealController(Context context, QSPanel qsPanel, PagedTileLayout pagedTileLayout,
+ QSCustomizerController qsCustomizerController) {
mContext = context;
mQSPanel = qsPanel;
mPagedTileLayout = pagedTileLayout;
+ mQsCustomizerController = qsCustomizerController;
}
public void setExpansion(float expansion) {
@@ -56,7 +63,7 @@ public class QSTileRevealController {
final Set<String> revealedTiles = Prefs.getStringSet(
mContext, QS_TILE_SPECS_REVEALED, Collections.EMPTY_SET);
- if (revealedTiles.isEmpty() || mQSPanel.isShowingCustomize()) {
+ if (revealedTiles.isEmpty() || mQsCustomizerController.isCustomizing()) {
// Do not reveal QS tiles the user has upon first load or those that they directly
// added through customization.
addTileSpecsToRevealed(tileSpecs);
@@ -73,4 +80,24 @@ public class QSTileRevealController {
revealedTiles.addAll(specs);
Prefs.putStringSet(mContext, QS_TILE_SPECS_REVEALED, revealedTiles);
}
+
+ /** TODO(b/168904199): Remove this once QSPanel has its rejection removed. */
+ @QSScope
+ static class Factory {
+ private final Context mContext;
+ private final QSPanel mQsPanel;
+ private final QSCustomizerController mQsCustomizerController;
+
+ @Inject
+ Factory(Context context, QSPanel qsPanel, QSCustomizerController qsCustomizerController) {
+ mContext = context;
+ mQsPanel = qsPanel;
+ mQsCustomizerController = qsCustomizerController;
+ }
+
+ QSTileRevealController create(PagedTileLayout pagedTileLayout) {
+ return new QSTileRevealController(mContext, mQsPanel, pagedTileLayout,
+ mQsCustomizerController);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index ea036f6fe0e5..ed0900d07b56 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -16,6 +16,7 @@
package com.android.systemui.qs;
+import static com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import android.content.Context;
@@ -27,23 +28,13 @@ import android.view.View;
import android.widget.LinearLayout;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.SignalState;
import com.android.systemui.plugins.qs.QSTile.State;
-import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-
-import java.util.ArrayList;
-import java.util.Collection;
import javax.inject.Inject;
import javax.inject.Named;
@@ -67,15 +58,10 @@ public class QuickQSPanel extends QSPanel {
public QuickQSPanel(
@Named(VIEW_CONTEXT) Context context,
AttributeSet attrs,
- DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- MediaHost mediaHost,
- UiEventLogger uiEventLogger,
- UserTracker userTracker
- ) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, mediaHost, uiEventLogger,
- userTracker);
+ @Named(QUICK_QS_PANEL) MediaHost mediaHost,
+ UiEventLogger uiEventLogger) {
+ super(context, attrs, qsLogger, mediaHost, uiEventLogger);
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
applyBottomMargin((View) mRegularTileLayout);
}
@@ -88,17 +74,12 @@ public class QuickQSPanel extends QSPanel {
}
@Override
- protected void addSecurityFooter() {
- // No footer needed
- }
-
- @Override
protected void addViewsAboveTiles() {
// Nothing to add above the tiles
}
@Override
- protected TileLayout createRegularTileLayout() {
+ public TileLayout createRegularTileLayout() {
return new QuickQSPanel.HeaderTileLayout(mContext, mUiEventLogger);
}
@@ -108,6 +89,7 @@ public class QuickQSPanel extends QSPanel {
}
@Override
+
protected void initMediaHostState() {
mMediaHost.setExpansion(0.0f);
mMediaHost.setShowsOnlyActiveMedia(true);
@@ -131,18 +113,6 @@ public class QuickQSPanel extends QSPanel {
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- Dependency.get(TunerService.class).addTunable(mNumTiles, NUM_QUICK_TILES);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- Dependency.get(TunerService.class).removeTunable(mNumTiles);
- }
-
- @Override
protected String getDumpableTag() {
return TAG;
}
@@ -157,7 +127,7 @@ public class QuickQSPanel extends QSPanel {
}
@Override
- protected void drawTile(TileRecord r, State state) {
+ protected void drawTile(QSPanelControllerBase.TileRecord r, State state) {
if (state instanceof SignalState) {
SignalState copy = new SignalState();
state.copyTo(copy);
@@ -169,17 +139,8 @@ public class QuickQSPanel extends QSPanel {
super.drawTile(r, state);
}
- @Override
- public void setHost(QSTileHost host, QSCustomizer customizer) {
- super.setHost(host, customizer);
- setTiles(mHost.getTiles());
- }
-
public void setMaxTiles(int maxTiles) {
mMaxTiles = maxTiles;
- if (mHost != null) {
- setTiles(mHost.getTiles());
- }
}
@Override
@@ -190,25 +151,6 @@ public class QuickQSPanel extends QSPanel {
}
}
- @Override
- public void setTiles(Collection<QSTile> tiles) {
- ArrayList<QSTile> quickTiles = new ArrayList<>();
- for (QSTile tile : tiles) {
- quickTiles.add(tile);
- if (quickTiles.size() == mMaxTiles) {
- break;
- }
- }
- super.setTiles(quickTiles, true);
- }
-
- private final Tunable mNumTiles = new Tunable() {
- @Override
- public void onTuningChanged(String key, String newValue) {
- setMaxTiles(parseNumTiles(newValue));
- }
- };
-
public int getNumQuickTiles() {
return mMaxTiles;
}
@@ -306,7 +248,7 @@ public class QuickQSPanel extends QSPanel {
}
@Override
- protected void addTileView(TileRecord tile) {
+ protected void addTileView(QSPanelControllerBase.TileRecord tile) {
addView(tile.tileView, getChildCount(), generateTileLayoutParams());
}
@@ -369,7 +311,7 @@ public class QuickQSPanel extends QSPanel {
private void setAccessibilityOrder() {
if (mRecords != null && mRecords.size() > 0) {
View previousView = this;
- for (TileRecord record : mRecords) {
+ for (QSPanelControllerBase.TileRecord record : mRecords) {
if (record.tileView.getVisibility() == GONE) continue;
previousView = record.tileView.updateAccessibilityOrder(previousView);
}
@@ -381,7 +323,7 @@ public class QuickQSPanel extends QSPanel {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure each QS tile.
- for (TileRecord record : mRecords) {
+ for (QSPanelControllerBase.TileRecord record : mRecords) {
if (record.tileView.getVisibility() == GONE) continue;
record.tileView.measure(exactly(mCellWidth), exactly(mCellHeight));
}
@@ -394,7 +336,7 @@ public class QuickQSPanel extends QSPanel {
@Override
public int getNumVisibleTiles() {
- return mColumns;
+ return Math.min(mRecords.size(), mColumns);
}
@Override
@@ -411,6 +353,7 @@ public class QuickQSPanel extends QSPanel {
boolean startedListening = !mListening && listening;
super.setListening(listening);
if (startedListening) {
+ // getNumVisibleTiles() <= mRecords.size()
for (int i = 0; i < getNumVisibleTiles(); i++) {
QSTile tile = mRecords.get(i).tile;
mUiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
new file mode 100644
index 000000000000..a718271998c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.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.systemui.qs;
+
+import static com.android.systemui.qs.QuickQSPanel.NUM_QUICK_TILES;
+import static com.android.systemui.qs.QuickQSPanel.parseNumTiles;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.customize.QSCustomizerController;
+import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.tuner.TunerService;
+import com.android.systemui.tuner.TunerService.Tunable;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+
+/** Controller for {@link QuickQSPanel}. */
+@QSScope
+public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel> {
+ private final Tunable mNumTiles =
+ (key, newValue) -> setMaxTiles(parseNumTiles(newValue));
+
+ private final TunerService mTunerService;
+
+ @Inject
+ QuickQSPanelController(QuickQSPanel view, TunerService tunerService, QSTileHost qsTileHost,
+ QSCustomizerController qsCustomizerController,
+ QSTileRevealController.Factory qsTileRevealControllerFactory,
+ MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+ DumpManager dumpManager) {
+ super(view, qsTileHost, qsCustomizerController, qsTileRevealControllerFactory,
+ metricsLogger, uiEventLogger, dumpManager);
+ mTunerService = tunerService;
+ }
+
+ @Override
+ protected void onViewAttached() {
+ super.onViewAttached();
+ mTunerService.addTunable(mNumTiles, NUM_QUICK_TILES);
+
+ }
+
+ @Override
+ protected void onViewDetached() {
+ super.onViewDetached();
+ mTunerService.removeTunable(mNumTiles);
+ }
+
+ public boolean isListening() {
+ return mView.isListening();
+ }
+
+ private void setMaxTiles(int parseNumTiles) {
+ mView.setMaxTiles(parseNumTiles);
+ setTiles();
+ }
+
+ @Override
+ public void setTiles() {
+ ArrayList<QSTile> quickTiles = new ArrayList<>();
+ for (QSTile tile : mHost.getTiles()) {
+ quickTiles.add(tile);
+ if (quickTiles.size() == mView.getNumQuickTiles()) {
+ break;
+ }
+ }
+ super.setTiles(quickTiles, true);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
index a9fbc744b38e..5757602b9d0f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java
@@ -353,11 +353,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
mPrivacyChip.setExpanded(expansionFraction > 0.5);
mPrivacyChipAlphaAnimator.setPosition(keyguardExpansionFraction);
}
- if (expansionFraction < 1 && expansionFraction > 0.99) {
- if (mHeaderQsPanel.switchTileLayout()) {
- updateResources();
- }
- }
+
mKeyguardExpansionFraction = keyguardExpansionFraction;
}
@@ -446,7 +442,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements LifecycleOwn
public void setQSPanel(final QSPanel qsPanel) {
//host.setHeaderView(mExpandIndicator);
mHeaderQsPanel.setQSPanelAndHeader(qsPanel, this);
- mHeaderQsPanel.setHost(qsPanel.getHost(), null /* No customization in header */);
Rect tintArea = new Rect(0, 0, 0, 0);
int colorForeground = Utils.getColorAttrDefaultColor(getContext(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index 676a300b0ff2..32904a21cd3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -43,7 +43,9 @@ import com.android.systemui.privacy.OngoingPrivacyChip;
import com.android.systemui.privacy.PrivacyChipEvent;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
+import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.carrier.QSCarrierGroupController;
+import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -64,6 +66,7 @@ import javax.inject.Inject;
/**
* Controller for {@link QuickStatusBarHeader}.
*/
+@QSScope
class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader> {
private static final String TAG = "QuickStatusBarHeader";
@@ -74,7 +77,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final ActivityStarter mActivityStarter;
private final UiEventLogger mUiEventLogger;
private final QSCarrierGroupController mQSCarrierGroupController;
- private final QuickQSPanel mHeaderQsPanel;
+ private final QuickQSPanelController mHeaderQsPanelController;
private final LifecycleRegistry mLifecycle;
private final OngoingPrivacyChip mPrivacyChip;
private final Clock mClockView;
@@ -88,11 +91,13 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private final StatusIconContainer mIconContainer;
private final StatusBarIconController.TintedIconManager mIconManager;
private final DemoMode mDemoModeReceiver;
+ private final PrivacyLogger mPrivacyLogger;
private boolean mListening;
private AlarmClockInfo mNextAlarm;
private boolean mAllIndicatorsEnabled;
private boolean mMicCameraIndicatorsEnabled;
+ private boolean mLocationIndicatorsEnabled;
private boolean mPrivacyChipLogged;
private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
@@ -156,6 +161,14 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}
}
+ @Override
+ public void onFlagLocationChanged(boolean flag) {
+ if (mLocationIndicatorsEnabled != flag) {
+ mLocationIndicatorsEnabled = flag;
+ update();
+ }
+ }
+
private void update() {
StatusIconContainer iconContainer = mView.requireViewById(R.id.statusIcons);
iconContainer.setIgnoredSlots(getIgnoredIconSlots());
@@ -194,14 +207,16 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}
};
- private QuickStatusBarHeaderController(QuickStatusBarHeader view,
+ @Inject
+ QuickStatusBarHeaderController(QuickStatusBarHeader view,
ZenModeController zenModeController, NextAlarmController nextAlarmController,
PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
ActivityStarter activityStarter, UiEventLogger uiEventLogger,
QSTileHost qsTileHost, StatusBarIconController statusBarIconController,
CommandQueue commandQueue, DemoModeController demoModeController,
- UserTracker userTracker,
- QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
+ UserTracker userTracker, QuickQSPanelController quickQSPanelController,
+ QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder,
+ PrivacyLogger privacyLogger) {
super(view);
mZenModeController = zenModeController;
mNextAlarmController = nextAlarmController;
@@ -215,6 +230,8 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mDemoModeController = demoModeController;
mUserTracker = userTracker;
mLifecycle = new LifecycleRegistry(mLifecycleOwner);
+ mHeaderQsPanelController = quickQSPanelController;
+ mPrivacyLogger = privacyLogger;
mQSCarrierGroupController = qsCarrierGroupControllerBuilder
.setQSCarrierGroup(mView.findViewById(R.id.carrier_group))
@@ -222,7 +239,6 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mPrivacyChip = mView.findViewById(R.id.privacy_chip);
- mHeaderQsPanel = mView.findViewById(R.id.quick_qs_panel);
mNextAlarmContainer = mView.findViewById(R.id.alarm_container);
mClockView = mView.findViewById(R.id.clock);
mRingerContainer = mView.findViewById(R.id.ringer_container);
@@ -245,14 +261,15 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mRingerContainer.setOnClickListener(mOnClickListener);
mPrivacyChip.setOnClickListener(mOnClickListener);
+ mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
+ mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+ mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
+
// Ignore privacy icons because they show in the space above QQS
- mIconContainer.addIgnoredSlots(getIgnoredIconSlots());
+ mIconContainer.setIgnoredSlots(getIgnoredIconSlots());
mIconContainer.setShouldRestrictIcons(false);
mStatusBarIconController.addIconGroup(mIconManager);
- mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
- mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
-
setChipVisibility(mPrivacyChip.getVisibility() == View.VISIBLE);
mView.onAttach(mIconManager);
@@ -280,8 +297,12 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
}
mListening = listening;
- mHeaderQsPanel.setListening(listening);
- if (mHeaderQsPanel.switchTileLayout()) {
+ mHeaderQsPanelController.setListening(listening);
+ if (mHeaderQsPanelController.isListening()) {
+ mHeaderQsPanelController.refreshAllTiles();
+ }
+
+ if (mHeaderQsPanelController.switchTileLayout(false)) {
mView.updateResources();
}
@@ -292,6 +313,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
// Get the most up to date info
mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
+ mLocationIndicatorsEnabled = mPrivacyItemController.getLocationAvailable();
mPrivacyItemController.addCallback(mPICCallback);
} else {
mZenModeController.removeCallback(mZenModeControllerCallback);
@@ -305,6 +327,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private void setChipVisibility(boolean chipVisible) {
if (chipVisible && getChipEnabled()) {
mPrivacyChip.setVisibility(View.VISIBLE);
+ mPrivacyLogger.logChipVisible(true);
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
if (!mPrivacyChipLogged && mListening) {
@@ -312,6 +335,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mUiEventLogger.log(PrivacyChipEvent.ONGOING_INDICATORS_CHIP_VIEW);
}
} else {
+ mPrivacyLogger.logChipVisible(false);
mPrivacyChip.setVisibility(View.GONE);
}
}
@@ -319,21 +343,22 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
private List<String> getIgnoredIconSlots() {
ArrayList<String> ignored = new ArrayList<>();
if (getChipEnabled()) {
- ignored.add(mView.getResources().getString(
- com.android.internal.R.string.status_bar_camera));
- ignored.add(mView.getResources().getString(
- com.android.internal.R.string.status_bar_microphone));
- if (mAllIndicatorsEnabled) {
+ if (mAllIndicatorsEnabled || mMicCameraIndicatorsEnabled) {
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_camera));
+ ignored.add(mView.getResources().getString(
+ com.android.internal.R.string.status_bar_microphone));
+ }
+ if (mAllIndicatorsEnabled || mLocationIndicatorsEnabled) {
ignored.add(mView.getResources().getString(
com.android.internal.R.string.status_bar_location));
}
}
-
return ignored;
}
private boolean getChipEnabled() {
- return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
+ return mMicCameraIndicatorsEnabled || mLocationIndicatorsEnabled || mAllIndicatorsEnabled;
}
private boolean isZenOverridingRinger() {
@@ -369,55 +394,4 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mClockView.onDemoModeFinished();
}
}
-
- static class Builder {
- private final ZenModeController mZenModeController;
- private final NextAlarmController mNextAlarmController;
- private final PrivacyItemController mPrivacyItemController;
- private final RingerModeTracker mRingerModeTracker;
- private final ActivityStarter mActivityStarter;
- private final UiEventLogger mUiEventLogger;
- private final QSTileHost mQsTileHost;
- private final StatusBarIconController mStatusBarIconController;
- private final CommandQueue mCommandQueue;
- private final DemoModeController mDemoModeController;
- private final UserTracker mUserTracker;
- private final QSCarrierGroupController.Builder mQSCarrierGroupControllerBuilder;
- private QuickStatusBarHeader mView;
-
- @Inject
- Builder(ZenModeController zenModeController, NextAlarmController nextAlarmController,
- PrivacyItemController privacyItemController, RingerModeTracker ringerModeTracker,
- ActivityStarter activityStarter, UiEventLogger uiEventLogger, QSTileHost qsTileHost,
- StatusBarIconController statusBarIconController, CommandQueue commandQueue,
- DemoModeController demoModeController, UserTracker userTracker,
- QSCarrierGroupController.Builder qsCarrierGroupControllerBuilder) {
- mZenModeController = zenModeController;
- mNextAlarmController = nextAlarmController;
- mPrivacyItemController = privacyItemController;
- mRingerModeTracker = ringerModeTracker;
- mActivityStarter = activityStarter;
- mUiEventLogger = uiEventLogger;
- mQsTileHost = qsTileHost;
- mStatusBarIconController = statusBarIconController;
- mCommandQueue = commandQueue;
- mDemoModeController = demoModeController;
- mUserTracker = userTracker;
- mQSCarrierGroupControllerBuilder = qsCarrierGroupControllerBuilder;
- }
-
- public Builder setQuickStatusBarHeader(QuickStatusBarHeader view) {
- mView = view;
- return this;
- }
-
-
- QuickStatusBarHeaderController build() {
- return new QuickStatusBarHeaderController(mView, mZenModeController,
- mNextAlarmController, mPrivacyItemController, mRingerModeTracker,
- mActivityStarter, mUiEventLogger, mQsTileHost, mStatusBarIconController,
- mCommandQueue, mDemoModeController, mUserTracker,
- mQSCarrierGroupControllerBuilder);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 3ee3e117fb0f..994da9a174df 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -16,18 +16,17 @@
package com.android.systemui.qs;
-import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
-import android.provider.Settings.Secure;
import com.android.systemui.statusbar.policy.Listenable;
+import com.android.systemui.util.settings.SecureSettings;
/** Helper for managing a secure setting. **/
public abstract class SecureSetting extends ContentObserver implements Listenable {
private static final int DEFAULT = 0;
- private final Context mContext;
+ private SecureSettings mSecureSettings;
private final String mSettingName;
private boolean mListening;
@@ -36,19 +35,20 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
protected abstract void handleValueChanged(int value, boolean observedChange);
- public SecureSetting(Context context, Handler handler, String settingName, int userId) {
+ public SecureSetting(SecureSettings secureSettings, Handler handler, String settingName,
+ int userId) {
super(handler);
- mContext = context;
+ mSecureSettings = secureSettings;
mSettingName = settingName;
mUserId = userId;
}
public int getValue() {
- return Secure.getIntForUser(mContext.getContentResolver(), mSettingName, DEFAULT, mUserId);
+ return mSecureSettings.getIntForUser(mSettingName, DEFAULT, mUserId);
}
public void setValue(int value) {
- Secure.putIntForUser(mContext.getContentResolver(), mSettingName, value, mUserId);
+ mSecureSettings.putIntForUser(mSettingName, value, mUserId);
}
@Override
@@ -57,10 +57,10 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
mListening = listening;
if (listening) {
mObservedValue = getValue();
- mContext.getContentResolver().registerContentObserver(
- Secure.getUriFor(mSettingName), false, this, mUserId);
+ mSecureSettings.registerContentObserverForUser(
+ mSecureSettings.getUriFor(mSettingName), false, this, mUserId);
} else {
- mContext.getContentResolver().unregisterContentObserver(this);
+ mSecureSettings.unregisterContentObserver(this);
mObservedValue = DEFAULT;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 694492a33524..4ab7afd46602 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -11,7 +11,7 @@ import android.view.ViewGroup;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel.QSTileLayout;
-import com.android.systemui.qs.QSPanel.TileRecord;
+import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
import java.util.ArrayList;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 55b67e061c13..3291aa0e2099 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -20,41 +20,23 @@ import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
-import android.os.Bundle;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
-import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.Toolbar;
-import android.widget.Toolbar.OnMenuItemClickListener;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
-import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
-import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.R;
-import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSDetailClipper;
-import com.android.systemui.qs.QSEditEvent;
-import com.android.systemui.qs.QSTileHost;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
/**
* Allows full-screen customization of QS, through show() and hide().
@@ -62,24 +44,16 @@ import javax.inject.Inject;
* This adds itself to the status bar window, so it can appear on top of quick settings and
* *someday* do fancy animations to get into/out of it.
*/
-public class QSCustomizer extends LinearLayout implements OnMenuItemClickListener {
+public class QSCustomizer extends LinearLayout {
- private static final int MENU_RESET = Menu.FIRST;
- private static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
- private static final String TAG = "QSCustomizer";
+ static final int MENU_RESET = Menu.FIRST;
+ static final String EXTRA_QS_CUSTOMIZING = "qs_customizing";
private final QSDetailClipper mClipper;
- private final LightBarController mLightBarController;
- private KeyguardStateController mKeyguardStateController;
- private final ScreenLifecycle mScreenLifecycle;
- private final TileQueryHelper mTileQueryHelper;
private final View mTransparentView;
private boolean isShown;
- private QSTileHost mHost;
- private RecyclerView mRecyclerView;
- private TileAdapter mTileAdapter;
- private Toolbar mToolbar;
+ private final RecyclerView mRecyclerView;
private boolean mCustomizing;
private NotificationsQuickSettingsContainer mNotifQsContainer;
private QS mQs;
@@ -87,92 +61,47 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
private int mY;
private boolean mOpening;
private boolean mIsShowingNavBackdrop;
- private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
-
- @Inject
- public QSCustomizer(Context context, AttributeSet attrs,
- LightBarController lightBarController,
- KeyguardStateController keyguardStateController,
- ScreenLifecycle screenLifecycle,
- TileQueryHelper tileQueryHelper,
- UiEventLogger uiEventLogger) {
+
+ public QSCustomizer(Context context, AttributeSet attrs) {
super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
mClipper = new QSDetailClipper(findViewById(R.id.customize_container));
- mToolbar = findViewById(com.android.internal.R.id.action_bar);
+ Toolbar toolbar = findViewById(com.android.internal.R.id.action_bar);
TypedValue value = new TypedValue();
mContext.getTheme().resolveAttribute(android.R.attr.homeAsUpIndicator, value, true);
- mToolbar.setNavigationIcon(
+ toolbar.setNavigationIcon(
getResources().getDrawable(value.resourceId, mContext.getTheme()));
- mToolbar.setNavigationOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- hide();
- }
- });
- mToolbar.setOnMenuItemClickListener(this);
- mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
+
+ toolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
mContext.getString(com.android.internal.R.string.reset));
- mToolbar.setTitle(R.string.qs_edit);
+ toolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
- mTileAdapter = new TileAdapter(getContext(), uiEventLogger);
- mTileQueryHelper = tileQueryHelper;
- mTileQueryHelper.setListener(mTileAdapter);
- mRecyclerView.setAdapter(mTileAdapter);
- mTileAdapter.getItemTouchHelper().attachToRecyclerView(mRecyclerView);
- GridLayoutManager layout = new GridLayoutManager(getContext(), 3) {
- @Override
- public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
- RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
- // Do not read row and column every time it changes.
- }
- };
- layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
- mRecyclerView.setLayoutManager(layout);
- mRecyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
- mRecyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
DefaultItemAnimator animator = new DefaultItemAnimator();
animator.setMoveDuration(TileAdapter.MOVE_DURATION);
mRecyclerView.setItemAnimator(animator);
- mLightBarController = lightBarController;
- mKeyguardStateController = keyguardStateController;
- mScreenLifecycle = screenLifecycle;
- updateNavBackDrop(getResources().getConfiguration());
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- updateNavBackDrop(newConfig);
- updateResources();
- }
-
- private void updateResources() {
+ void updateResources() {
LayoutParams lp = (LayoutParams) mTransparentView.getLayoutParams();
lp.height = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.quick_qs_offset_height);
mTransparentView.setLayoutParams(lp);
}
- private void updateNavBackDrop(Configuration newConfig) {
+ void updateNavBackDrop(Configuration newConfig, LightBarController lightBarController) {
View navBackdrop = findViewById(R.id.nav_bar_background);
mIsShowingNavBackdrop = newConfig.smallestScreenWidthDp >= 600
|| newConfig.orientation != Configuration.ORIENTATION_LANDSCAPE;
if (navBackdrop != null) {
navBackdrop.setVisibility(mIsShowingNavBackdrop ? View.VISIBLE : View.GONE);
}
- updateNavColors();
+ updateNavColors(lightBarController);
}
- private void updateNavColors() {
- mLightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
- }
-
- public void setHost(QSTileHost host) {
- mHost = host;
- mTileAdapter.setHost(host);
+ void updateNavColors(LightBarController lightBarController) {
+ lightBarController.setQsCustomizing(mIsShowingNavBackdrop && isShown);
}
public void setContainer(NotificationsQuickSettingsContainer notificationsQsContainer) {
@@ -186,39 +115,30 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
/** Animate and show QSCustomizer panel.
* @param x,y Location on screen of {@code edit} button to determine center of animation.
*/
- public void show(int x, int y) {
+ void show(int x, int y, TileAdapter tileAdapter) {
if (!isShown) {
- int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen();
+ int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
mX = x - containerLocation[0];
mY = y - containerLocation[1];
- mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
isShown = true;
mOpening = true;
- setTileSpecs();
setVisibility(View.VISIBLE);
- mClipper.animateCircularClip(mX, mY, true, mExpandAnimationListener);
- queryTiles();
+ mClipper.animateCircularClip(mX, mY, true, new ExpandAnimatorListener(tileAdapter));
mNotifQsContainer.setCustomizerAnimating(true);
mNotifQsContainer.setCustomizerShowing(true);
- mKeyguardStateController.addCallback(mKeyguardCallback);
- updateNavColors();
}
}
- public void showImmediately() {
+ void showImmediately() {
if (!isShown) {
setVisibility(VISIBLE);
mClipper.cancelAnimator();
mClipper.showBackground();
isShown = true;
- setTileSpecs();
setCustomizing(true);
- queryTiles();
mNotifQsContainer.setCustomizerAnimating(false);
mNotifQsContainer.setCustomizerShowing(true);
- mKeyguardStateController.addCallback(mKeyguardCallback);
- updateNavColors();
}
}
@@ -227,9 +147,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
* {@link TileAdapter}.
*/
public void setContentPaddings(int paddingStart, int paddingEnd) {
- int halfMargin = mContext.getResources()
- .getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
- mTileAdapter.changeHalfMargin(halfMargin);
mRecyclerView.setPaddingRelative(
paddingStart,
mRecyclerView.getPaddingTop(),
@@ -238,22 +155,14 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
);
}
- private void queryTiles() {
- mTileQueryHelper.queryTiles(mHost);
- }
-
- public void hide() {
- final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
+ /** Hide the customizer. */
+ public void hide(boolean animate) {
if (isShown) {
- mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
isShown = false;
- mToolbar.dismissPopupMenus();
mClipper.cancelAnimator();
// Make sure we're not opening (because we're closing). Nobody can think we are
// customizing after the next two lines.
mOpening = false;
- setCustomizing(false);
- save();
if (animate) {
mClipper.animateCircularClip(mX, mY, false, mCollapseAnimationListener);
} else {
@@ -261,8 +170,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
}
mNotifQsContainer.setCustomizerAnimating(animate);
mNotifQsContainer.setCustomizerShowing(false);
- mKeyguardStateController.removeCallback(mKeyguardCallback);
- updateNavColors();
}
}
@@ -270,7 +177,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
return isShown;
}
- private void setCustomizing(boolean customizing) {
+ void setCustomizing(boolean customizing) {
mCustomizing = customizing;
mQs.notifyCustomizeChanged();
}
@@ -279,78 +186,21 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
return mCustomizing || mOpening;
}
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- switch (item.getItemId()) {
- case MENU_RESET:
- mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET);
- reset();
- break;
- }
- return false;
- }
-
- private void reset() {
- mTileAdapter.resetTileSpecs(mHost, QSTileHost.getDefaultSpecs(mContext));
- }
-
- private void setTileSpecs() {
- List<String> specs = new ArrayList<>();
- for (QSTile tile : mHost.getTiles()) {
- specs.add(tile.getTileSpec());
- }
- mTileAdapter.setTileSpecs(specs);
- mRecyclerView.setAdapter(mTileAdapter);
- }
-
- private void save() {
- if (mTileQueryHelper.isFinished()) {
- mTileAdapter.saveSpecs(mHost);
- }
- }
-
-
- public void saveInstanceState(Bundle outState) {
- if (isShown) {
- mKeyguardStateController.removeCallback(mKeyguardCallback);
- }
- outState.putBoolean(EXTRA_QS_CUSTOMIZING, mCustomizing);
- }
-
- public void restoreInstanceState(Bundle savedInstanceState) {
- boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING);
- if (customizing) {
- setVisibility(VISIBLE);
- addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- removeOnLayoutChangeListener(this);
- showImmediately();
- }
- });
- }
- }
/** @param x,y Location on screen of animation center.
*/
public void setEditLocation(int x, int y) {
- int containerLocation[] = findViewById(R.id.customize_container).getLocationOnScreen();
+ int[] containerLocation = findViewById(R.id.customize_container).getLocationOnScreen();
mX = x - containerLocation[0];
mY = y - containerLocation[1];
}
- private final Callback mKeyguardCallback = new Callback() {
- @Override
- public void onKeyguardShowingChanged() {
- if (!isAttachedToWindow()) return;
- if (mKeyguardStateController.isShowing() && !mOpening) {
- hide();
- }
+ class ExpandAnimatorListener extends AnimatorListenerAdapter {
+ private final TileAdapter mTileAdapter;
+
+ ExpandAnimatorListener(TileAdapter tileAdapter) {
+ mTileAdapter = tileAdapter;
}
- };
- private final AnimatorListener mExpandAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (isShown) {
@@ -358,6 +208,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
}
mOpening = false;
mNotifQsContainer.setCustomizerAnimating(false);
+ mRecyclerView.setAdapter(mTileAdapter);
}
@Override
@@ -365,7 +216,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mOpening = false;
mNotifQsContainer.setCustomizerAnimating(false);
}
- };
+ }
private final AnimatorListener mCollapseAnimationListener = new AnimatorListenerAdapter() {
@Override
@@ -374,7 +225,6 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
setVisibility(View.GONE);
}
mNotifQsContainer.setCustomizerAnimating(false);
- mRecyclerView.setAdapter(mTileAdapter);
}
@Override
@@ -385,4 +235,12 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mNotifQsContainer.setCustomizerAnimating(false);
}
};
-}
+
+ public RecyclerView getRecyclerView() {
+ return mRecyclerView;
+ }
+
+ public boolean isOpening() {
+ return mOpening;
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
new file mode 100644
index 000000000000..9f4c58b58cab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizerController.java
@@ -0,0 +1,247 @@
+/*
+ * 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.customize;
+
+import static com.android.systemui.qs.customize.QSCustomizer.EXTRA_QS_CUSTOMIZING;
+import static com.android.systemui.qs.customize.QSCustomizer.MENU_RESET;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Toolbar;
+import android.widget.Toolbar.OnMenuItemClickListener;
+
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSEditEvent;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.dagger.QSScope;
+import com.android.systemui.statusbar.phone.LightBarController;
+import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ViewController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+/** {@link ViewController} for {@link QSCustomizer}. */
+@QSScope
+public class QSCustomizerController extends ViewController<QSCustomizer> {
+ private final TileQueryHelper mTileQueryHelper;
+ private final QSTileHost mQsTileHost;
+ private final TileAdapter mTileAdapter;
+ private final ScreenLifecycle mScreenLifecycle;
+ private final KeyguardStateController mKeyguardStateController;
+ private final LightBarController mLightBarController;
+ private final ConfigurationController mConfigurationController;
+ private final UiEventLogger mUiEventLogger;
+ private final Toolbar mToolbar;
+
+ private final OnMenuItemClickListener mOnMenuItemClickListener = new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ if (item.getItemId() == MENU_RESET) {
+ mUiEventLogger.log(QSEditEvent.QS_EDIT_RESET);
+ reset();
+ }
+ return false;
+ }
+ };
+
+ private final KeyguardStateController.Callback mKeyguardCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (!mView.isAttachedToWindow()) return;
+ if (mKeyguardStateController.isShowing() && !mView.isOpening()) {
+ hide();
+ }
+ }
+ };
+
+ private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ mView.updateNavBackDrop(newConfig, mLightBarController);
+ mView.updateResources();
+ }
+ };
+
+ @Inject
+ protected QSCustomizerController(QSCustomizer view, TileQueryHelper tileQueryHelper,
+ QSTileHost qsTileHost, TileAdapter tileAdapter, ScreenLifecycle screenLifecycle,
+ KeyguardStateController keyguardStateController, LightBarController lightBarController,
+ ConfigurationController configurationController, UiEventLogger uiEventLogger) {
+ super(view);
+ mTileQueryHelper = tileQueryHelper;
+ mQsTileHost = qsTileHost;
+ mTileAdapter = tileAdapter;
+ mScreenLifecycle = screenLifecycle;
+ mKeyguardStateController = keyguardStateController;
+ mLightBarController = lightBarController;
+ mConfigurationController = configurationController;
+ mUiEventLogger = uiEventLogger;
+
+ mToolbar = mView.findViewById(com.android.internal.R.id.action_bar);
+ }
+
+ @Override
+ protected void onViewAttached() {
+ mView.updateNavBackDrop(getResources().getConfiguration(), mLightBarController);
+
+ mConfigurationController.addCallback(mConfigurationListener);
+
+ mTileQueryHelper.setListener(mTileAdapter);
+ int halfMargin =
+ getResources().getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2;
+ mTileAdapter.changeHalfMargin(halfMargin);
+
+ RecyclerView recyclerView = mView.getRecyclerView();
+ recyclerView.setAdapter(mTileAdapter);
+ mTileAdapter.getItemTouchHelper().attachToRecyclerView(recyclerView);
+ GridLayoutManager layout = new GridLayoutManager(getContext(), 3) {
+ @Override
+ public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
+ RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
+ // Do not read row and column every time it changes.
+ }
+ };
+ layout.setSpanSizeLookup(mTileAdapter.getSizeLookup());
+ recyclerView.setLayoutManager(layout);
+ recyclerView.addItemDecoration(mTileAdapter.getItemDecoration());
+ recyclerView.addItemDecoration(mTileAdapter.getMarginItemDecoration());
+
+ mToolbar.setOnMenuItemClickListener(mOnMenuItemClickListener);
+ mToolbar.setNavigationOnClickListener(v -> hide());
+ }
+
+ @Override
+ protected void onViewDetached() {
+ mTileQueryHelper.setListener(null);
+ mToolbar.setOnMenuItemClickListener(null);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ }
+
+
+ private void reset() {
+ mTileAdapter.resetTileSpecs(QSTileHost.getDefaultSpecs(getContext()));
+ }
+
+ public boolean isCustomizing() {
+ return mView.isCustomizing();
+ }
+
+ /** */
+ public void show(int x, int y, boolean immediate) {
+ if (!mView.isShown()) {
+ setTileSpecs();
+ if (immediate) {
+ mView.showImmediately();
+ } else {
+ mView.show(x, y, mTileAdapter);
+ mUiEventLogger.log(QSEditEvent.QS_EDIT_OPEN);
+ }
+ mTileQueryHelper.queryTiles(mQsTileHost);
+ mKeyguardStateController.addCallback(mKeyguardCallback);
+ mView.updateNavColors(mLightBarController);
+ }
+ }
+
+ /** */
+ public void setQs(QSFragment qsFragment) {
+ mView.setQs(qsFragment);
+ }
+
+ /** */
+ public void restoreInstanceState(Bundle savedInstanceState) {
+ boolean customizing = savedInstanceState.getBoolean(EXTRA_QS_CUSTOMIZING);
+ if (customizing) {
+ mView.setVisibility(View.VISIBLE);
+ mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ mView.removeOnLayoutChangeListener(this);
+ show(0, 0, true);
+ }
+ });
+ }
+ }
+
+ /** */
+ public void saveInstanceState(Bundle outState) {
+ if (mView.isShown()) {
+ mKeyguardStateController.removeCallback(mKeyguardCallback);
+ }
+ outState.putBoolean(EXTRA_QS_CUSTOMIZING, mView.isCustomizing());
+ }
+
+ /** */
+ public void setEditLocation(int x, int y) {
+ mView.setEditLocation(x, y);
+ }
+
+ /** */
+ public void setContainer(NotificationsQuickSettingsContainer container) {
+ mView.setContainer(container);
+ }
+
+ public boolean isShown() {
+ return mView.isShown();
+ }
+
+ /** Hice the customizer. */
+ public void hide() {
+ final boolean animate = mScreenLifecycle.getScreenState() != ScreenLifecycle.SCREEN_OFF;
+ if (mView.isShown()) {
+ mUiEventLogger.log(QSEditEvent.QS_EDIT_CLOSED);
+ mToolbar.dismissPopupMenus();
+ mView.setCustomizing(false);
+ save();
+ mView.hide(animate);
+ mView.updateNavColors(mLightBarController);
+ mKeyguardStateController.removeCallback(mKeyguardCallback);
+ }
+ }
+
+ private void save() {
+ if (mTileQueryHelper.isFinished()) {
+ mTileAdapter.saveSpecs(mQsTileHost);
+ }
+ }
+
+ private void setTileSpecs() {
+ List<String> specs = new ArrayList<>();
+ for (QSTile tile : mQsTileHost.getTiles()) {
+ specs.add(tile.getTileSpec());
+ }
+ mTileAdapter.setTileSpecs(specs);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index b471dfae02d1..dfc771beab1c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -46,12 +46,17 @@ import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.customize.TileAdapter.Holder;
import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener;
+import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSIconViewImpl;
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
+/** */
+@QSScope
public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileStateListener {
private static final long DRAG_LENGTH = 100;
private static final float DRAG_SCALE = 1.2f;
@@ -78,6 +83,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
private final ItemDecoration mDecoration;
private final MarginTileDecoration mMarginDecoration;
private final int mMinNumTiles;
+ private final QSTileHost mHost;
private int mEditIndex;
private int mTileDividerIndex;
private int mFocusIndex;
@@ -89,13 +95,14 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
private Holder mCurrentDrag;
private int mAccessibilityAction = ACTION_NONE;
private int mAccessibilityFromIndex;
- private QSTileHost mHost;
private final UiEventLogger mUiEventLogger;
private final AccessibilityDelegateCompat mAccessibilityDelegate;
private RecyclerView mRecyclerView;
- public TileAdapter(Context context, UiEventLogger uiEventLogger) {
+ @Inject
+ public TileAdapter(Context context, QSTileHost qsHost, UiEventLogger uiEventLogger) {
mContext = context;
+ mHost = qsHost;
mUiEventLogger = uiEventLogger;
mItemTouchHelper = new ItemTouchHelper(mCallbacks);
mDecoration = new TileItemDecoration(context);
@@ -114,10 +121,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
mRecyclerView = null;
}
- public void setHost(QSTileHost host) {
- mHost = host;
- }
-
public ItemTouchHelper getItemTouchHelper() {
return mItemTouchHelper;
}
@@ -154,9 +157,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
mAccessibilityAction = ACTION_NONE;
}
- public void resetTileSpecs(QSTileHost host, List<String> specs) {
+ /** */
+ public void resetTileSpecs(List<String> specs) {
// Notify the host so the tiles get removed callbacks.
- host.changeTiles(mCurrentSpecs, specs);
+ mHost.changeTiles(mCurrentSpecs, specs);
setTileSpecs(specs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index b795a5f5ea19..59490c666a83 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -37,6 +37,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon;
import com.android.systemui.settings.UserTracker;
@@ -50,6 +51,8 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
+/** */
+@QSScope
public class TileQueryHelper {
private static final String TAG = "TileQueryHelper";
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
new file mode 100644
index 000000000000..8cc05026e1f1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java
@@ -0,0 +1,60 @@
+/*
+ * 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.dagger;
+
+import com.android.systemui.qs.QSAnimator;
+import com.android.systemui.qs.QSContainerImplController;
+import com.android.systemui.qs.QSFooter;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.QuickQSPanelController;
+import com.android.systemui.qs.customize.QSCustomizerController;
+
+import dagger.BindsInstance;
+import dagger.Subcomponent;
+
+/**
+ * Dagger Subcomponent for {@link QSFragment}.
+ */
+@Subcomponent(modules = {QSFragmentModule.class})
+@QSScope
+public interface QSFragmentComponent {
+
+ /** Factory for building a {@link QSFragmentComponent}. */
+ @Subcomponent.Factory
+ interface Factory {
+ QSFragmentComponent create(@BindsInstance QSFragment qsFragment);
+ }
+
+ /** Construct a {@link QSPanelController}. */
+ QSPanelController getQSPanelController();
+
+ /** Construct a {@link QuickQSPanelController}. */
+ QuickQSPanelController getQuickQSPanelController();
+
+ /** Construct a {@link QSAnimator}. */
+ QSAnimator getQSAnimator();
+
+ /** Construct a {@link QSContainerImplController}. */
+ QSContainerImplController getQSContainerImplController();
+
+ /** Construct a {@link QSFooter} */
+ QSFooter getQSFooter();
+
+ /** Construct a {@link QSCustomizerController}. */
+ QSCustomizerController getQSCustomizerController();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
new file mode 100644
index 000000000000..354b2c944248
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -0,0 +1,98 @@
+/*
+ * 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.dagger;
+
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.qs.QSContainerImpl;
+import com.android.systemui.qs.QSFooter;
+import com.android.systemui.qs.QSFooterView;
+import com.android.systemui.qs.QSFooterViewController;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QuickQSPanel;
+import com.android.systemui.qs.QuickStatusBarHeader;
+import com.android.systemui.qs.customize.QSCustomizer;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for {@link QSFragmentComponent}.
+ */
+@Module
+public interface QSFragmentModule {
+ /** */
+ @Provides
+ @RootView
+ static View provideRootView(QSFragment qsFragment) {
+ return qsFragment.getView();
+ }
+
+ /** */
+ @Provides
+ static QSPanel provideQSPanel(@RootView View view) {
+ return view.findViewById(R.id.quick_settings_panel);
+ }
+
+ /** */
+ @Provides
+ static QSContainerImpl providesQSContainerImpl(@RootView View view) {
+ return view.findViewById(R.id.quick_settings_container);
+ }
+
+ /** */
+ @Binds
+ QS bindQS(QSFragment qsFragment);
+
+ /** */
+ @Provides
+ static QuickStatusBarHeader providesQuickStatusBarHeader(@RootView View view) {
+ return view.findViewById(R.id.header);
+ }
+
+ /** */
+ @Provides
+ static QuickQSPanel providesQuickQSPanel(QuickStatusBarHeader quickStatusBarHeader) {
+ return quickStatusBarHeader.findViewById(R.id.quick_qs_panel);
+ }
+
+ /** */
+ @Provides
+ static QSFooterView providesQSFooterView(@RootView View view) {
+ return view.findViewById(R.id.qs_footer);
+ }
+
+ /** */
+ @Provides
+ @QSScope
+ static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
+ qsFooterViewController.init();
+ return qsFooterViewController;
+ }
+
+ /** */
+ @Provides
+ @QSScope
+ static QSCustomizer providesQSCutomizer(@RootView View view) {
+ return view.findViewById(R.id.qs_customize);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 8ff96c8a4a37..cfc81eee9b3c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -21,6 +21,7 @@ import android.hardware.display.NightDisplayListener;
import android.os.Handler;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.media.dagger.MediaModule;
import com.android.systemui.qs.AutoAddTracker;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QSTileHost;
@@ -29,6 +30,7 @@ import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.util.settings.SecureSettings;
import dagger.Binds;
import dagger.Module;
@@ -37,8 +39,8 @@ import dagger.Provides;
/**
* Module for QS dependencies
*/
-// TODO: Add other QS classes
-@Module
+@Module(subcomponents = {QSFragmentComponent.class},
+ includes = {MediaModule.class})
public interface QSModule {
@Provides
@@ -47,19 +49,28 @@ public interface QSModule {
AutoAddTracker.Builder autoAddTrackerBuilder,
QSTileHost host,
@Background Handler handler,
+ SecureSettings secureSettings,
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
NightDisplayListener nightDisplayListener,
CastController castController) {
- AutoTileManager manager = new AutoTileManager(context, autoAddTrackerBuilder,
- host, handler, hotspotController, dataSaverController, managedProfileController,
- nightDisplayListener, castController);
+ AutoTileManager manager = new AutoTileManager(
+ context,
+ autoAddTrackerBuilder,
+ host,
+ handler,
+ secureSettings,
+ hotspotController,
+ dataSaverController,
+ managedProfileController,
+ nightDisplayListener,
+ castController
+ );
manager.init();
return manager;
}
-
/** */
@Binds
QSHost provideQsHost(QSTileHost controllerImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScope.java
index 24768cd84a76..f615eabb67dc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSScope.java
@@ -13,17 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.bubbles.storage
-import android.annotation.DimenRes
-import android.annotation.UserIdInt
+package com.android.systemui.qs.dagger;
-data class BubbleEntity(
- @UserIdInt val userId: Int,
- val packageName: String,
- val shortcutId: String,
- val key: String,
- val desiredHeight: Int,
- @DimenRes val desiredHeightResId: Int,
- val title: String? = null
-)
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Scope;
+
+/**
+ * Scope annotation for singleton items within the {@link QSFragmentComponent}.
+ */
+@Documented
+@Retention(RUNTIME)
+@Scope
+public @interface QSScope {}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index c64fc50b8237..bf3e4be9b9db 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -36,6 +36,7 @@ import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
@@ -62,15 +63,20 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- BatteryController batteryController
+ BatteryController batteryController,
+ SecureSettings secureSettings
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
mBatteryController = batteryController;
mBatteryController.observe(getLifecycle(), this);
int currentUser = host.getUserContext().getUserId();
- mSetting = new SecureSetting(mContext, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
- currentUser) {
+ mSetting = new SecureSetting(
+ secureSettings,
+ mHandler,
+ Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
+ currentUser
+ ) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
// mHandler is the background handler so calling this is OK
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index eeb9de4f88a9..abffbba8a90c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -25,6 +25,7 @@ import android.content.Intent;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.telephony.SubscriptionManager;
@@ -244,7 +245,8 @@ public class CellularTile extends QSTileImpl<SignalState> {
@Override
public boolean isAvailable() {
- return mController.hasMobileDataFeature();
+ return mController.hasMobileDataFeature()
+ && mHost.getUserContext().getUserId() == UserHandle.USER_SYSTEM;
}
private static final class CallbackInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index 98782f7c8b55..39952488799a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -39,6 +39,7 @@ import com.android.systemui.qs.SecureSetting;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
import javax.inject.Inject;
@@ -63,12 +64,13 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> {
StatusBarStateController statusBarStateController,
ActivityStarter activityStarter,
QSLogger qsLogger,
- UserTracker userTracker
+ UserTracker userTracker,
+ SecureSettings secureSettings
) {
super(host, backgroundLooper, mainHandler, metricsLogger, statusBarStateController,
activityStarter, qsLogger);
- mSetting = new SecureSetting(mContext, mainHandler,
+ mSetting = new SecureSetting(secureSettings, mainHandler,
Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, userTracker.getUserId()) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 5279a20a67a7..ddf30ad663dd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -95,7 +95,6 @@ import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.phone.PipUtils;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
@@ -151,7 +150,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
private int mConnectionBackoffAttempts;
private boolean mBound;
private boolean mIsEnabled;
- private boolean mHasPipFeature;
private int mCurrentBoundedUserId = -1;
private float mNavBarButtonAlpha;
private boolean mInputFocusTransferStarted;
@@ -377,9 +375,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void setShelfHeight(boolean visible, int shelfHeight) {
- if (!verifyCaller("setShelfHeight") || !mHasPipFeature) {
- Log.w(TAG_OPS,
- "ByPass setShelfHeight, FEATURE_PICTURE_IN_PICTURE:" + mHasPipFeature);
+ if (!verifyCaller("setShelfHeight")) {
return;
}
final long token = Binder.clearCallingIdentity();
@@ -405,9 +401,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void notifySwipeToHomeFinished() {
- if (!verifyCaller("notifySwipeToHomeFinished") || !mHasPipFeature) {
- Log.w(TAG_OPS, "ByPass notifySwipeToHomeFinished, FEATURE_PICTURE_IN_PICTURE:"
- + mHasPipFeature);
+ if (!verifyCaller("notifySwipeToHomeFinished")) {
return;
}
final long token = Binder.clearCallingIdentity();
@@ -422,9 +416,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void setPinnedStackAnimationListener(IPinnedStackAnimationListener listener) {
- if (!verifyCaller("setPinnedStackAnimationListener") || !mHasPipFeature) {
- Log.w(TAG_OPS, "ByPass setPinnedStackAnimationListener, FEATURE_PICTURE_IN_PICTURE:"
- + mHasPipFeature);
+ if (!verifyCaller("setPinnedStackAnimationListener")) {
return;
}
mIPinnedStackAnimationListener = listener;
@@ -509,7 +501,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
PictureInPictureParams pictureInPictureParams,
int launcherRotation, int shelfHeight) {
- if (!verifyCaller("startSwipePipToHome") || !mHasPipFeature) {
+ if (!verifyCaller("startSwipePipToHome")) {
return null;
}
final long binderToken = Binder.clearCallingIdentity();
@@ -525,7 +517,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
@Override
public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds) {
- if (!verifyCaller("stopSwipePipToHome") || !mHasPipFeature) {
+ if (!verifyCaller("stopSwipePipToHome")) {
return;
}
final long binderToken = Binder.clearCallingIdentity();
@@ -650,7 +642,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
super(broadcastDispatcher);
mContext = context;
mPipOptional = pipOptional;
- mHasPipFeature = PipUtils.hasSystemFeature(mContext);
mStatusBarOptionalLazy = statusBarOptionalLazy;
mHandler = new Handler();
mNavBarControllerLazy = navBarControllerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 1a9abb9cf27d..45564b0bfa6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -30,6 +30,8 @@ import android.graphics.Bitmap;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.media.ThumbnailUtils;
@@ -124,17 +126,19 @@ public class ScreenMediaRecorder {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getRealMetrics(metrics);
- int screenWidth = metrics.widthPixels;
- int screenHeight = metrics.heightPixels;
- int refereshRate = (int) wm.getDefaultDisplay().getRefreshRate();
- int vidBitRate = screenHeight * screenWidth * refereshRate / VIDEO_FRAME_RATE
+ int refreshRate = (int) wm.getDefaultDisplay().getRefreshRate();
+ int[] dimens = getSupportedSize(metrics.widthPixels, metrics.heightPixels, refreshRate);
+ int width = dimens[0];
+ int height = dimens[1];
+ refreshRate = dimens[2];
+ int vidBitRate = width * height * refreshRate / VIDEO_FRAME_RATE
* VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO;
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setVideoEncodingProfileLevel(
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
- MediaCodecInfo.CodecProfileLevel.AVCLevel42);
- mMediaRecorder.setVideoSize(screenWidth, screenHeight);
- mMediaRecorder.setVideoFrameRate(refereshRate);
+ MediaCodecInfo.CodecProfileLevel.AVCLevel3);
+ mMediaRecorder.setVideoSize(width, height);
+ mMediaRecorder.setVideoFrameRate(refreshRate);
mMediaRecorder.setVideoEncodingBitRate(vidBitRate);
mMediaRecorder.setMaxDuration(MAX_DURATION_MS);
mMediaRecorder.setMaxFileSize(MAX_FILESIZE_BYTES);
@@ -153,8 +157,8 @@ public class ScreenMediaRecorder {
mInputSurface = mMediaRecorder.getSurface();
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
"Recording Display",
- screenWidth,
- screenHeight,
+ width,
+ height,
metrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mInputSurface,
@@ -173,6 +177,84 @@ public class ScreenMediaRecorder {
}
/**
+ * Find the highest supported screen resolution and refresh rate for the given dimensions on
+ * this device, up to actual size and given rate.
+ * If possible this will return the same values as given, but values may be smaller on some
+ * devices.
+ *
+ * @param screenWidth Actual pixel width of screen
+ * @param screenHeight Actual pixel height of screen
+ * @param refreshRate Desired refresh rate
+ * @return array with supported width, height, and refresh rate
+ */
+ private int[] getSupportedSize(int screenWidth, int screenHeight, int refreshRate) {
+ double maxScale = 0;
+
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ MediaCodecInfo.VideoCapabilities maxInfo = null;
+ for (MediaCodecInfo codec : codecList.getCodecInfos()) {
+ String videoType = MediaFormat.MIMETYPE_VIDEO_AVC;
+ String[] types = codec.getSupportedTypes();
+ for (String t : types) {
+ if (!t.equalsIgnoreCase(videoType)) {
+ continue;
+ }
+ MediaCodecInfo.CodecCapabilities capabilities =
+ codec.getCapabilitiesForType(videoType);
+ if (capabilities != null && capabilities.getVideoCapabilities() != null) {
+ MediaCodecInfo.VideoCapabilities vc = capabilities.getVideoCapabilities();
+
+ int width = vc.getSupportedWidths().getUpper();
+ int height = vc.getSupportedHeights().getUpper();
+
+ if (width >= screenWidth && height >= screenHeight
+ && vc.isSizeSupported(screenWidth, screenHeight)) {
+
+ // Desired size is supported, now get the rate
+ int maxRate = vc.getSupportedFrameRatesFor(screenWidth, screenHeight)
+ .getUpper().intValue();
+
+ if (maxRate < refreshRate) {
+ refreshRate = maxRate;
+ }
+ Log.d(TAG, "Screen size supported at rate " + refreshRate);
+ return new int[]{screenWidth, screenHeight, refreshRate};
+ }
+
+ // Otherwise, continue searching
+ double scale = Math.min(((double) width / screenWidth),
+ ((double) height / screenHeight));
+ if (scale > maxScale) {
+ maxScale = scale;
+ maxInfo = vc;
+ }
+ }
+ }
+ }
+
+ // Resize for max supported size
+ int scaledWidth = (int) (screenWidth * maxScale);
+ int scaledHeight = (int) (screenHeight * maxScale);
+ if (scaledWidth % maxInfo.getWidthAlignment() != 0) {
+ scaledWidth -= (scaledWidth % maxInfo.getWidthAlignment());
+ }
+ if (scaledHeight % maxInfo.getHeightAlignment() != 0) {
+ scaledHeight -= (scaledHeight % maxInfo.getHeightAlignment());
+ }
+
+ // Find max supported rate for size
+ int maxRate = maxInfo.getSupportedFrameRatesFor(scaledWidth, scaledHeight)
+ .getUpper().intValue();
+ if (maxRate < refreshRate) {
+ refreshRate = maxRate;
+ }
+
+ Log.d(TAG, "Resized by " + maxScale + ": " + scaledWidth + ", " + scaledHeight
+ + ", " + refreshRate);
+ return new int[]{scaledWidth, scaledHeight, refreshRate};
+ }
+
+ /**
* Start screen recording
*/
void start() throws IOException, RemoteException, IllegalStateException {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index 3fd7f94514f3..5c26d9400c9f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -16,12 +16,12 @@
package com.android.systemui.screenshot;
-import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_EDIT;
-import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_SHARE;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED;
+import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_EDIT;
+import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_SHARE;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_DISALLOW_ENTER_PIP;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
import android.app.ActivityOptions;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
index 9028bb57c8e5..35839f39b491 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
@@ -16,10 +16,10 @@
package com.android.systemui.screenshot;
-import static com.android.systemui.screenshot.GlobalScreenshot.ACTION_TYPE_DELETE;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED;
-import static com.android.systemui.screenshot.GlobalScreenshot.SCREENSHOT_URI_ID;
+import static com.android.systemui.screenshot.ScreenshotController.ACTION_TYPE_DELETE;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED;
+import static com.android.systemui.screenshot.ScreenshotController.SCREENSHOT_URI_ID;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
deleted file mode 100644
index aaa335c25d5d..000000000000
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ /dev/null
@@ -1,1144 +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.
- */
-
-package com.android.systemui.screenshot;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.Insets;
-import android.graphics.Outline;
-import android.graphics.PixelFormat;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.media.MediaActionSound;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.Display;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.SurfaceControl;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AccelerateInterpolator;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.widget.FrameLayout;
-import android.widget.HorizontalScrollView;
-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.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * Class for handling device screen shots
- */
-@SysUISingleton
-public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInsetsListener {
-
- /**
- * POD used in the AsyncTask which saves an image in the background.
- */
- static class SaveImageInBackgroundData {
- public Bitmap image;
- public Consumer<Uri> finisher;
- public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
-
- void clearImage() {
- image = 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(SavedImageData imageData);
- }
-
- // These strings are used for communicating the action invoked to
- // ScreenshotNotificationSmartActionsProvider.
- static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
- static final String EXTRA_ID = "android:screenshot_id";
- static final String ACTION_TYPE_DELETE = "Delete";
- static final String ACTION_TYPE_SHARE = "Share";
- static final String ACTION_TYPE_EDIT = "Edit";
- static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
- static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
-
- static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
- static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
- static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
-
- // From WizardManagerHelper.java
- private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
-
- private static final String TAG = "GlobalScreenshot";
-
- private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
- private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
- // delay before starting to fade in dismiss button
- private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
- private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
- private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
- private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
- private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
- private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
- private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
- private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
- private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
- private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
- private static final float ROUNDED_CORNER_RADIUS = .05f;
- private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
- private static final int MESSAGE_CORNER_TIMEOUT = 2;
-
- private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
-
- private final ScreenshotNotificationsController mNotificationsController;
- private final UiEventLogger mUiEventLogger;
-
- private final Context mContext;
- private final ScreenshotSmartActions mScreenshotSmartActions;
- private final WindowManager mWindowManager;
- private final WindowManager.LayoutParams mWindowLayoutParams;
- private final Display mDisplay;
- private final DisplayMetrics mDisplayMetrics;
- private final AccessibilityManager mAccessibilityManager;
-
- private View mScreenshotLayout;
- private ScreenshotSelectorView mScreenshotSelectorView;
- private ImageView mScreenshotAnimatedView;
- private ImageView mScreenshotPreview;
- private ImageView mScreenshotFlash;
- private ImageView mActionsContainerBackground;
- private HorizontalScrollView mActionsContainer;
- private LinearLayout mActionsView;
- private ImageView mBackgroundProtection;
- private FrameLayout mDismissButton;
-
- private Bitmap mScreenBitmap;
- private SaveImageInBackgroundTask mSaveInBgTask;
- private Animator mScreenshotAnimation;
- private Runnable mOnCompleteRunnable;
- private Animator mDismissAnimation;
- private boolean mInDarkMode;
- private boolean mDirectionLTR;
- private boolean mOrientationPortrait;
-
- private float mCornerSizeX;
- private float mDismissDeltaY;
-
- private MediaActionSound mCameraSound;
-
- private int mNavMode;
- private int mLeftInset;
- private int mRightInset;
-
- // standard material ease
- private final Interpolator mFastOutSlowIn;
-
- private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_CORNER_TIMEOUT:
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
- GlobalScreenshot.this.dismissScreenshot("timeout", false);
- mOnCompleteRunnable.run();
- break;
- default:
- break;
- }
- }
- };
-
- @Inject
- public GlobalScreenshot(
- Context context, @Main Resources resources,
- ScreenshotSmartActions screenshotSmartActions,
- ScreenshotNotificationsController screenshotNotificationsController,
- UiEventLogger uiEventLogger) {
- mContext = context;
- mScreenshotSmartActions = screenshotSmartActions;
- mNotificationsController = screenshotNotificationsController;
- mUiEventLogger = uiEventLogger;
- mAccessibilityManager = AccessibilityManager.getInstance(mContext);
-
- reloadAssets();
- Configuration config = mContext.getResources().getConfiguration();
- mInDarkMode = config.isNightModeActive();
- mDirectionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
- mOrientationPortrait = config.orientation == ORIENTATION_PORTRAIT;
-
- // Setup the window that we are going to use
- mWindowLayoutParams = new WindowManager.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
- WindowManager.LayoutParams.TYPE_SCREENSHOT,
- WindowManager.LayoutParams.FLAG_FULLSCREEN
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
- PixelFormat.TRANSLUCENT);
- mWindowLayoutParams.setTitle("ScreenshotAnimation");
- mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mWindowLayoutParams.setFitInsetsTypes(0 /* types */);
- mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
- mDisplay = mWindowManager.getDefaultDisplay();
- mDisplayMetrics = new DisplayMetrics();
- mDisplay.getRealMetrics(mDisplayMetrics);
-
- mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
- mDismissDeltaY = resources.getDimensionPixelSize(R.dimen.screenshot_dismissal_height_delta);
-
- mFastOutSlowIn =
- AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
-
- // Setup the Camera shutter sound
- mCameraSound = new MediaActionSound();
- mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
- }
-
- @Override // ViewTreeObserver.OnComputeInternalInsetsListener
- public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- Region touchRegion = new Region();
-
- Rect screenshotRect = new Rect();
- mScreenshotPreview.getBoundsOnScreen(screenshotRect);
- touchRegion.op(screenshotRect, Region.Op.UNION);
- Rect actionsRect = new Rect();
- mActionsContainer.getBoundsOnScreen(actionsRect);
- touchRegion.op(actionsRect, Region.Op.UNION);
- Rect dismissRect = new Rect();
- mDismissButton.getBoundsOnScreen(dismissRect);
- touchRegion.op(dismissRect, Region.Op.UNION);
-
- if (QuickStepContract.isGesturalMode(mNavMode)) {
- // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
- Rect inset = new Rect(0, 0, mLeftInset, mDisplayMetrics.heightPixels);
- touchRegion.op(inset, Region.Op.UNION);
- inset.set(mDisplayMetrics.widthPixels - mRightInset, 0, mDisplayMetrics.widthPixels,
- mDisplayMetrics.heightPixels);
- touchRegion.op(inset, Region.Op.UNION);
- }
-
- inoutInfo.touchableRegion.set(touchRegion);
- }
-
- void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
- mOnCompleteRunnable = onComplete;
-
- mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshotInternal(
- finisher,
- new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
- }
-
- void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
- Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
- Consumer<Uri> finisher, Runnable onComplete) {
- // TODO: use task Id, userId, topComponent for smart handler
- mOnCompleteRunnable = onComplete;
-
- if (screenshot == null) {
- Log.e(TAG, "Got null bitmap from screenshot message");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- finisher.accept(null);
- mOnCompleteRunnable.run();
- return;
- }
-
- if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
- saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
- } else {
- saveScreenshot(screenshot, finisher,
- new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
- true);
- }
- }
-
- /**
- * Displays a screenshot selector
- */
- @SuppressLint("ClickableViewAccessibility")
- void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
- dismissScreenshot("new screenshot requested", true);
- mOnCompleteRunnable = onComplete;
-
- mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
- mScreenshotSelectorView.setOnTouchListener((v, event) -> {
- ScreenshotSelectorView view = (ScreenshotSelectorView) v;
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- view.startSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_MOVE:
- view.updateSelection((int) event.getX(), (int) event.getY());
- return true;
- case MotionEvent.ACTION_UP:
- view.setVisibility(View.GONE);
- mWindowManager.removeView(mScreenshotLayout);
- final Rect rect = view.getSelectionRect();
- if (rect != null) {
- if (rect.width() != 0 && rect.height() != 0) {
- // Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(() -> takeScreenshotInternal(finisher, rect));
- }
- }
-
- view.stopSelection();
- return true;
- }
-
- return false;
- });
- mScreenshotLayout.post(() -> {
- mScreenshotSelectorView.setVisibility(View.VISIBLE);
- mScreenshotSelectorView.requestFocus();
- });
- }
-
- /**
- * Cancels screenshot request
- */
- void stopScreenshot() {
- // If the selector layer still presents on screen, we remove it and resets its state.
- if (mScreenshotSelectorView.getSelectionRect() != null) {
- mWindowManager.removeView(mScreenshotLayout);
- mScreenshotSelectorView.stopSelection();
- }
- }
-
- /**
- * Clears current screenshot
- */
- void dismissScreenshot(String reason, boolean immediate) {
- Log.v(TAG, "clearing screenshot: " + reason);
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
- if (!immediate) {
- mDismissAnimation = createScreenshotDismissAnimation();
- mDismissAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- clearScreenshot();
- }
- });
- mDismissAnimation.start();
- } else {
- clearScreenshot();
- }
- }
-
- private void onConfigChanged(Configuration newConfig) {
- boolean needsUpdate = false;
- // dark mode
- if (newConfig.isNightModeActive()) {
- // Night mode is active, we're using dark theme
- if (!mInDarkMode) {
- mInDarkMode = true;
- needsUpdate = true;
- }
- } else {
- // Night mode is not active, we're using the light theme
- if (mInDarkMode) {
- mInDarkMode = false;
- needsUpdate = true;
- }
- }
-
- // RTL configuration
- switch (newConfig.getLayoutDirection()) {
- case View.LAYOUT_DIRECTION_LTR:
- if (!mDirectionLTR) {
- mDirectionLTR = true;
- needsUpdate = true;
- }
- break;
- case View.LAYOUT_DIRECTION_RTL:
- if (mDirectionLTR) {
- mDirectionLTR = false;
- needsUpdate = true;
- }
- break;
- }
-
- // portrait/landscape orientation
- switch (newConfig.orientation) {
- case ORIENTATION_PORTRAIT:
- if (!mOrientationPortrait) {
- mOrientationPortrait = true;
- needsUpdate = true;
- }
- break;
- case ORIENTATION_LANDSCAPE:
- if (mOrientationPortrait) {
- mOrientationPortrait = false;
- needsUpdate = true;
- }
- break;
- }
-
- if (needsUpdate) {
- reloadAssets();
- }
-
- mNavMode = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_navBarInteractionMode);
- }
-
- /**
- * Update assets (called when the dark theme status changes). We only need to update the dismiss
- * button and the actions container background, since the buttons are re-inflated on demand.
- */
- private void reloadAssets() {
- boolean wasAttached = mScreenshotLayout != null && mScreenshotLayout.isAttachedToWindow();
- if (wasAttached) {
- mWindowManager.removeView(mScreenshotLayout);
- }
-
- // Inflate the screenshot layout
- mScreenshotLayout = LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
- // TODO(159460485): Remove this when focus is handled properly in the system
- mScreenshotLayout.setOnTouchListener((v, event) -> {
- if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
- // Once the user touches outside, stop listening for input
- setWindowFocusable(false);
- }
- return false;
- });
- mScreenshotLayout.setOnApplyWindowInsetsListener((v, insets) -> {
- if (QuickStepContract.isGesturalMode(mNavMode)) {
- Insets gestureInsets = insets.getInsets(
- WindowInsets.Type.systemGestures());
- mLeftInset = gestureInsets.left;
- mRightInset = gestureInsets.right;
- } else {
- mLeftInset = mRightInset = 0;
- }
- return mScreenshotLayout.onApplyWindowInsets(insets);
- });
- mScreenshotLayout.setOnKeyListener((v, keyCode, event) -> {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- dismissScreenshot("back pressed", false);
- return true;
- }
- return false;
- });
- // Get focus so that the key events go to the layout.
- mScreenshotLayout.setFocusableInTouchMode(true);
- mScreenshotLayout.requestFocus();
-
- mScreenshotAnimatedView =
- mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view);
- mScreenshotAnimatedView.setClipToOutline(true);
- mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
- ROUNDED_CORNER_RADIUS * view.getWidth());
- }
- });
- mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview);
- mScreenshotPreview.setClipToOutline(true);
- mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
- ROUNDED_CORNER_RADIUS * view.getWidth());
- }
- });
-
- mActionsContainerBackground = mScreenshotLayout.findViewById(
- R.id.global_screenshot_actions_container_background);
- mActionsContainer = mScreenshotLayout.findViewById(
- R.id.global_screenshot_actions_container);
- mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions);
- mBackgroundProtection = mScreenshotLayout.findViewById(
- R.id.global_screenshot_actions_background);
- mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
- mDismissButton.setOnClickListener(view -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
- dismissScreenshot("dismiss_button", false);
- mOnCompleteRunnable.run();
- });
-
- mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
- mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
- mScreenshotLayout.setFocusable(true);
- mScreenshotSelectorView.setFocusable(true);
- mScreenshotSelectorView.setFocusableInTouchMode(true);
- mScreenshotAnimatedView.setPivotX(0);
- mScreenshotAnimatedView.setPivotY(0);
- mActionsContainer.setScrollX(0);
-
- if (wasAttached) {
- mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
- }
- }
-
- /**
- * Takes a screenshot of the current display and shows an animation.
- */
- private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
- // copy the input Rect, since SurfaceControl.screenshot can mutate it
- Rect screenRect = new Rect(crop);
- int width = crop.width();
- int height = crop.height();
- final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
- final SurfaceControl.DisplayCaptureArgs captureArgs =
- new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
- .setSourceCrop(crop)
- .setSize(width, height)
- .build();
- final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
- SurfaceControl.captureDisplay(captureArgs);
- Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
-
- if (screenshot == null) {
- Log.e(TAG, "Screenshot bitmap was null");
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- finisher.accept(null);
- mOnCompleteRunnable.run();
- return;
- }
-
- saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
- }
-
- private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
- Insets screenInsets, boolean showFlash) {
- if (mAccessibilityManager.isEnabled()) {
- AccessibilityEvent event =
- new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- event.setContentDescription(
- mContext.getResources().getString(R.string.screenshot_saving_title));
- mAccessibilityManager.sendAccessibilityEvent(event);
- }
-
- if (mScreenshotLayout.isAttachedToWindow()) {
- // if we didn't already dismiss for another reason
- if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
- }
- dismissScreenshot("new screenshot requested", true);
- }
-
- mScreenBitmap = screenshot;
-
- if (!isUserSetupComplete()) {
- // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
- // and sharing shouldn't be exposed to the user.
- saveScreenshotAndToast(finisher);
- return;
- }
-
- // Optimizations
- mScreenBitmap.setHasAlpha(false);
- mScreenBitmap.prepareToDraw();
-
- onConfigChanged(mContext.getResources().getConfiguration());
-
- if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
- mDismissAnimation.cancel();
- }
-
- // The window is focusable by default
- setWindowFocusable(true);
-
- // Start the post-screenshot animation
- startAnimation(finisher, screenRect, screenInsets, showFlash);
- }
-
- /**
- * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
- * failure).
- */
- private void saveScreenshotAndToast(Consumer<Uri> finisher) {
- // Play the shutter sound to notify that we've taken a screenshot
- mScreenshotHandler.post(() -> {
- mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
- });
-
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- 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_save_text);
- } else {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
-
- mScreenshotHandler.post(() -> {
- Toast.makeText(mContext, R.string.screenshot_saved_title,
- Toast.LENGTH_SHORT).show();
- });
- }
- }
- });
- }
-
- /**
- * Starts the animation after taking the screenshot
- */
- private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
- boolean showFlash) {
- mScreenshotHandler.post(() -> {
- if (!mScreenshotLayout.isAttachedToWindow()) {
- mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
- }
- mScreenshotAnimatedView.setImageDrawable(
- createScreenDrawable(mScreenBitmap, screenInsets));
- setAnimatedViewSize(screenRect.width(), screenRect.height());
- // Show when the animation starts
- mScreenshotAnimatedView.setVisibility(View.GONE);
-
- mScreenshotPreview.setImageDrawable(createScreenDrawable(mScreenBitmap, screenInsets));
- // make static preview invisible (from gone) so we can query its location on screen
- mScreenshotPreview.setVisibility(View.INVISIBLE);
-
- mScreenshotHandler.post(() -> {
- mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
-
- mScreenshotAnimation =
- createScreenshotDropInAnimation(screenRect, showFlash);
-
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(SavedImageData imageData) {
- showUiOnActionsReady(imageData);
- }
- });
-
- // Play the shutter sound to notify that we've taken a screenshot
- mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
-
- mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
- mScreenshotPreview.buildLayer();
- mScreenshotAnimation.start();
- });
- });
- }
-
- /**
- * Creates a new worker thread and saves the screenshot to the media store.
- */
- private void saveScreenshotInWorkerThread(
- Consumer<Uri> finisher, @Nullable ActionsReadyListener actionsReadyListener) {
- SaveImageInBackgroundData data = new SaveImageInBackgroundData();
- data.image = mScreenBitmap;
- data.finisher = finisher;
- data.mActionsReadyListener = actionsReadyListener;
-
- if (mSaveInBgTask != null) {
- // just log success/failure for the pre-existing screenshot
- mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
- @Override
- void onActionsReady(SavedImageData imageData) {
- logSuccessOnActionsReady(imageData);
- }
- });
- }
-
- mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);
- mSaveInBgTask.execute();
- }
-
- /**
- * Sets up the action shade and its entrance animation, once we get the screenshot URI.
- */
- private void showUiOnActionsReady(SavedImageData imageData) {
- logSuccessOnActionsReady(imageData);
-
- AccessibilityManager accessibilityManager = (AccessibilityManager)
- mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
- SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
- AccessibilityManager.FLAG_CONTENT_CONTROLS);
-
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- timeoutMs);
-
- if (imageData.uri != null) {
- mScreenshotHandler.post(() -> {
- if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
- mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- createScreenshotActionsShadeAnimation(imageData).start();
- }
- });
- } else {
- createScreenshotActionsShadeAnimation(imageData).start();
- }
- });
- }
- }
-
- /**
- * Logs success/failure of the screenshot saving task, and shows an error if it failed.
- */
- private void logSuccessOnActionsReady(SavedImageData imageData) {
- if (imageData.uri == null) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_save_text);
- } else {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
- }
- }
-
- private AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash) {
- Rect previewBounds = new Rect();
- mScreenshotPreview.getBoundsOnScreen(previewBounds);
-
- float cornerScale =
- mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
- final float currentScale = 1f;
-
- mScreenshotAnimatedView.setScaleX(currentScale);
- mScreenshotAnimatedView.setScaleY(currentScale);
-
- mDismissButton.setAlpha(0);
- mDismissButton.setVisibility(View.VISIBLE);
-
- AnimatorSet dropInAnimation = new AnimatorSet();
- ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
- flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
- flashInAnimator.setInterpolator(mFastOutSlowIn);
- flashInAnimator.addUpdateListener(animation ->
- mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
- ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
- flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
- flashOutAnimator.setInterpolator(mFastOutSlowIn);
- flashOutAnimator.addUpdateListener(animation ->
- mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
-
- // animate from the current location, to the static preview location
- final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
- final PointF finalPos = new PointF(previewBounds.centerX(), previewBounds.centerY());
-
- ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
- toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
- float xPositionPct =
- SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- float dismissPct =
- SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- float scalePct =
- SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
- toCorner.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- if (t < scalePct) {
- float scale = MathUtils.lerp(
- currentScale, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct));
- mScreenshotAnimatedView.setScaleX(scale);
- mScreenshotAnimatedView.setScaleY(scale);
- } else {
- mScreenshotAnimatedView.setScaleX(cornerScale);
- mScreenshotAnimatedView.setScaleY(cornerScale);
- }
-
- float currentScaleX = mScreenshotAnimatedView.getScaleX();
- float currentScaleY = mScreenshotAnimatedView.getScaleY();
-
- if (t < xPositionPct) {
- float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
- mFastOutSlowIn.getInterpolation(t / xPositionPct));
- mScreenshotAnimatedView.setX(xCenter - bounds.width() * currentScaleX / 2f);
- } else {
- mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * currentScaleX / 2f);
- }
- float yCenter = MathUtils.lerp(
- startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
- mScreenshotAnimatedView.setY(yCenter - bounds.height() * currentScaleY / 2f);
-
- if (t >= dismissPct) {
- mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
- float currentX = mScreenshotAnimatedView.getX();
- float currentY = mScreenshotAnimatedView.getY();
- mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
- if (mDirectionLTR) {
- mDismissButton.setX(currentX
- + bounds.width() * currentScaleX - mDismissButton.getWidth() / 2f);
- } else {
- mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
- }
- }
- });
-
- toCorner.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationStart(Animator animation) {
- super.onAnimationStart(animation);
- mScreenshotAnimatedView.setVisibility(View.VISIBLE);
- }
- });
-
- mScreenshotFlash.setAlpha(0f);
- mScreenshotFlash.setVisibility(View.VISIBLE);
-
- if (showFlash) {
- dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
- dropInAnimation.play(flashOutAnimator).with(toCorner);
- } else {
- dropInAnimation.play(toCorner);
- }
-
- dropInAnimation.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- mDismissButton.setAlpha(1);
- float dismissOffset = mDismissButton.getWidth() / 2f;
- float finalDismissX = mDirectionLTR
- ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
- : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
- mDismissButton.setX(finalDismissX);
- mDismissButton.setY(
- finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
- mScreenshotAnimatedView.setScaleX(1);
- mScreenshotAnimatedView.setScaleY(1);
- mScreenshotAnimatedView.setX(finalPos.x - bounds.width() * cornerScale / 2f);
- mScreenshotAnimatedView.setY(finalPos.y - bounds.height() * cornerScale / 2f);
- mScreenshotAnimatedView.setVisibility(View.GONE);
- mScreenshotPreview.setVisibility(View.VISIBLE);
- mScreenshotLayout.forceLayout();
- }
- });
-
- return dropInAnimation;
- }
-
- private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
- LayoutInflater inflater = LayoutInflater.from(mContext);
- mActionsView.removeAllViews();
- mScreenshotLayout.invalidate();
- mScreenshotLayout.requestLayout();
- mScreenshotLayout.getViewTreeObserver().dispatchOnGlobalLayout();
-
- // By default the activities won't be able to start immediately; override this to keep
- // the same behavior as if started from a notification
- try {
- ActivityManager.getService().resumeAppSwitches();
- } catch (RemoteException e) {
- }
-
- ArrayList<ScreenshotActionChip> chips = new ArrayList<>();
-
- 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,
- () -> {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
- dismissScreenshot("chip tapped", false);
- mOnCompleteRunnable.run();
- });
- mActionsView.addView(actionChip);
- chips.add(actionChip);
- }
-
- 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);
- dismissScreenshot("chip tapped", false);
- mOnCompleteRunnable.run();
- });
- mActionsView.addView(shareChip);
- chips.add(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);
- dismissScreenshot("chip tapped", false);
- mOnCompleteRunnable.run();
- });
- mActionsView.addView(editChip);
- chips.add(editChip);
-
- mScreenshotPreview.setOnClickListener(v -> {
- try {
- imageData.editAction.actionIntent.send();
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
- dismissScreenshot("screenshot preview tapped", false);
- mOnCompleteRunnable.run();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Intent cancelled", e);
- }
- });
- mScreenshotPreview.setContentDescription(imageData.editAction.title);
-
- // remove the margin from the last chip so that it's correctly aligned with the end
- LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
- mActionsView.getChildAt(mActionsView.getChildCount() - 1).getLayoutParams();
- params.setMarginEnd(0);
-
- ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
- float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
- / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
- mActionsContainer.setAlpha(0f);
- mActionsContainerBackground.setAlpha(0f);
- mActionsContainer.setVisibility(View.VISIBLE);
- mActionsContainerBackground.setVisibility(View.VISIBLE);
-
- animator.addUpdateListener(animation -> {
- float t = animation.getAnimatedFraction();
- mBackgroundProtection.setAlpha(t);
- float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
- mActionsContainer.setAlpha(containerAlpha);
- mActionsContainerBackground.setAlpha(containerAlpha);
- float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
- + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
- mActionsContainer.setScaleX(containerScale);
- mActionsContainerBackground.setScaleX(containerScale);
- for (ScreenshotActionChip chip : chips) {
- chip.setAlpha(t);
- chip.setScaleX(1 / containerScale); // invert to keep size of children constant
- }
- mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
- mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
- mActionsContainerBackground.setPivotX(
- mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
- });
- return animator;
- }
-
- private AnimatorSet createScreenshotDismissAnimation() {
- ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
- alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
- alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
- alphaAnim.addUpdateListener(animation -> {
- mScreenshotLayout.setAlpha(1 - animation.getAnimatedFraction());
- });
-
- ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
- yAnim.setInterpolator(mAccelerateInterpolator);
- yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
- float screenshotStartY = mScreenshotPreview.getTranslationY();
- float dismissStartY = mDismissButton.getTranslationY();
- yAnim.addUpdateListener(animation -> {
- float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
- mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
- mDismissButton.setTranslationY(dismissStartY + yDelta);
- mActionsContainer.setTranslationY(yDelta);
- mActionsContainerBackground.setTranslationY(yDelta);
- });
-
- AnimatorSet animSet = new AnimatorSet();
- animSet.play(yAnim).with(alphaAnim);
-
- return animSet;
- }
-
- private void clearScreenshot() {
- if (mScreenshotLayout.isAttachedToWindow()) {
- mWindowManager.removeView(mScreenshotLayout);
- }
-
- // Clear any references to the bitmap
- mScreenshotPreview.setImageDrawable(null);
- mScreenshotAnimatedView.setImageDrawable(null);
- mScreenshotAnimatedView.setVisibility(View.GONE);
- mActionsContainerBackground.setVisibility(View.GONE);
- mActionsContainer.setVisibility(View.GONE);
- mBackgroundProtection.setAlpha(0f);
- mDismissButton.setVisibility(View.GONE);
- mScreenshotPreview.setVisibility(View.GONE);
- mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
- mScreenshotPreview.setContentDescription(
- mContext.getResources().getString(R.string.screenshot_preview_description));
- mScreenshotPreview.setOnClickListener(null);
- mScreenshotLayout.setAlpha(1);
- mDismissButton.setTranslationY(0);
- mActionsContainer.setTranslationY(0);
- mActionsContainerBackground.setTranslationY(0);
- mScreenshotPreview.setTranslationY(0);
- }
-
- private void setAnimatedViewSize(int width, int height) {
- ViewGroup.LayoutParams layoutParams = mScreenshotAnimatedView.getLayoutParams();
- layoutParams.width = width;
- layoutParams.height = height;
- mScreenshotAnimatedView.setLayoutParams(layoutParams);
- }
-
- /**
- * Updates the window focusability. If the window is already showing, then it updates the
- * window immediately, otherwise the layout params will be applied when the window is next
- * shown.
- */
- private void setWindowFocusable(boolean focusable) {
- if (focusable) {
- mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
- } else {
- mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
- }
- if (mScreenshotLayout.isAttachedToWindow()) {
- mWindowManager.updateViewLayout(mScreenshotLayout, mWindowLayoutParams);
- }
- }
-
- private boolean isUserSetupComplete() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
- }
-
- /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
- private boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets, Rect screenBounds) {
- int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
- int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom;
-
- if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
- || bitmap.getHeight() == 0) {
- Log.e(TAG, String.format(
- "Provided bitmap and insets create degenerate region: %dx%d %s",
- bitmap.getWidth(), bitmap.getHeight(), bitmapInsets));
- return false;
- }
-
- float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight;
- float boundsAspect = ((float) screenBounds.width()) / screenBounds.height();
-
- boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f;
- if (!matchWithinTolerance) {
- Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f",
- insettedBitmapAspect, boundsAspect));
- }
-
- return matchWithinTolerance;
- }
-
- /**
- * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
- */
- private Drawable createScreenDrawable(Bitmap bitmap, Insets insets) {
- int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
- int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
-
- BitmapDrawable bitmapDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
- if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
- || bitmap.getHeight() == 0) {
- Log.e(TAG, String.format(
- "Can't create insetted drawable, using 0 insets "
- + "bitmap and insets create degenerate region: %dx%d %s",
- bitmap.getWidth(), bitmap.getHeight(), insets));
- return bitmapDrawable;
- }
-
- InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
- -1f * insets.left / insettedWidth,
- -1f * insets.top / insettedHeight,
- -1f * insets.right / insettedWidth,
- -1f * insets.bottom / insettedHeight);
-
- if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
- // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
- // to fill in the background of the drawable.
- return new LayerDrawable(new Drawable[]{
- new ColorDrawable(Color.BLACK), insetDrawable});
- } else {
- return insetDrawable;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index f0ea597c458d..b2ebf3f700b9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -82,8 +82,8 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final ScreenshotSmartActions mScreenshotSmartActions;
- private final GlobalScreenshot.SaveImageInBackgroundData mParams;
- private final GlobalScreenshot.SavedImageData mImageData;
+ private final ScreenshotController.SaveImageInBackgroundData mParams;
+ private final ScreenshotController.SavedImageData mImageData;
private final String mImageFileName;
private final long mImageTime;
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
@@ -92,10 +92,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final Random mRandom = new Random();
SaveImageInBackgroundTask(Context context, ScreenshotSmartActions screenshotSmartActions,
- GlobalScreenshot.SaveImageInBackgroundData data) {
+ ScreenshotController.SaveImageInBackgroundData data) {
mContext = context;
mScreenshotSmartActions = screenshotSmartActions;
- mImageData = new GlobalScreenshot.SavedImageData();
+ mImageData = new ScreenshotController.SavedImageData();
// Prepare all the output metadata
mParams = data;
@@ -234,7 +234,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
* Update the listener run when the saving task completes. Used to avoid showing UI for the
* first screenshot when a second one is taken.
*/
- void setActionsReadyListener(GlobalScreenshot.ActionsReadyListener listener) {
+ void setActionsReadyListener(ScreenshotController.ActionsReadyListener listener) {
mParams.mActionsReadyListener = listener;
}
@@ -281,20 +281,23 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
// cancel current pending intent (if any) since clipData isn't used for matching
- PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
- sharingChooserIntent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ context, 0, sharingChooserIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null, UserHandle.CURRENT);
// Create a share action for the notification
PendingIntent shareAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class)
- .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent)
- .putExtra(GlobalScreenshot.EXTRA_DISALLOW_ENTER_PIP, true)
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
+ .putExtra(ScreenshotController.EXTRA_DISALLOW_ENTER_PIP, true)
+ .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.setAction(Intent.ACTION_SEND)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
@@ -323,7 +326,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivityAsUser(context, 0,
- editIntent, 0, null, UserHandle.CURRENT);
+ editIntent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
// 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.
@@ -332,13 +335,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Create a edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
new Intent(context, ActionProxyReceiver.class)
- .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, pendingIntent)
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, pendingIntent)
+ .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.setAction(Intent.ACTION_EDIT)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.SYSTEM);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
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);
@@ -355,12 +359,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Create a delete action for the notification
PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
new Intent(context, DeleteScreenshotReceiver.class)
- .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ .putExtra(ScreenshotController.SCREENSHOT_URI_ID, uri.toString())
+ .putExtra(ScreenshotController.EXTRA_ID, mScreenshotId)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED,
mSmartActionsEnabled)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ PendingIntent.FLAG_CANCEL_CURRENT
+ | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE);
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);
@@ -395,13 +401,13 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
Intent intent = new Intent(context, SmartActionsReceiver.class)
- .putExtra(GlobalScreenshot.EXTRA_ACTION_INTENT, action.actionIntent)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, action.actionIntent)
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
addIntentExtras(mScreenshotId, intent, actionType, mSmartActionsEnabled);
PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
mRandom.nextInt(),
intent,
- PendingIntent.FLAG_CANCEL_CURRENT);
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
broadcastActions.add(new Notification.Action.Builder(action.getIcon(), action.title,
broadcastIntent).setContextual(true).addExtras(extras).build());
}
@@ -411,9 +417,9 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private static void addIntentExtras(String screenshotId, Intent intent, String actionType,
boolean smartActionsEnabled) {
intent
- .putExtra(GlobalScreenshot.EXTRA_ACTION_TYPE, actionType)
- .putExtra(GlobalScreenshot.EXTRA_ID, screenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
+ .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType)
+ .putExtra(ScreenshotController.EXTRA_ID, screenshotId)
+ .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, smartActionsEnabled);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
index a48870240384..3370946ec274 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -16,7 +16,6 @@
package com.android.systemui.screenshot;
-import android.annotation.ColorInt;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -35,9 +34,9 @@ public class ScreenshotActionChip extends FrameLayout {
private static final String TAG = "ScreenshotActionChip";
- private ImageView mIcon;
- private TextView mText;
- private @ColorInt int mIconColor;
+ private ImageView mIconView;
+ private TextView mTextView;
+ private boolean mIsPending = false;
public ScreenshotActionChip(Context context) {
this(context, null);
@@ -54,25 +53,29 @@ public class ScreenshotActionChip extends FrameLayout {
public ScreenshotActionChip(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
-
- mIconColor = context.getColor(R.color.global_screenshot_button_icon);
}
@Override
protected void onFinishInflate() {
- mIcon = findViewById(R.id.screenshot_action_chip_icon);
- mText = findViewById(R.id.screenshot_action_chip_text);
+ mIconView = findViewById(R.id.screenshot_action_chip_icon);
+ mTextView = findViewById(R.id.screenshot_action_chip_text);
+ }
+
+ @Override
+ public void setPressed(boolean pressed) {
+ // override pressed state to true if there is an action pending
+ super.setPressed(mIsPending || pressed);
}
void setIcon(Icon icon, boolean tint) {
- mIcon.setImageIcon(icon);
+ mIconView.setImageIcon(icon);
if (!tint) {
- mIcon.setImageTintList(null);
+ mIconView.setImageTintList(null);
}
}
void setText(CharSequence text) {
- mText.setText(text);
+ mTextView.setText(text);
}
void setPendingIntent(PendingIntent intent, Runnable finisher) {
@@ -85,4 +88,9 @@ public class ScreenshotActionChip extends FrameLayout {
}
});
}
+
+ void setIsPending(boolean isPending) {
+ mIsPending = isPending;
+ setPressed(mIsPending);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
new file mode 100644
index 000000000000..0dde931d78b2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -0,0 +1,689 @@
+/*
+ * 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.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.media.MediaActionSound;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.Toast;
+
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.util.DeviceConfigProxy;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Controls the state and flow for screenshots.
+ */
+public class ScreenshotController {
+ /**
+ * POD used in the AsyncTask which saves an image in the background.
+ */
+ static class SaveImageInBackgroundData {
+ public Bitmap image;
+ public Consumer<Uri> finisher;
+ public ScreenshotController.ActionsReadyListener mActionsReadyListener;
+
+ void clearImage() {
+ image = 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(ScreenshotController.SavedImageData imageData);
+ }
+
+ private static final String TAG = "ScreenshotController";
+
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ static final String EXTRA_ID = "android:screenshot_id";
+ static final String ACTION_TYPE_DELETE = "Delete";
+ static final String ACTION_TYPE_SHARE = "Share";
+ static final String ACTION_TYPE_EDIT = "Edit";
+ static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+
+ static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+ static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+ static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+
+
+ private static final int MESSAGE_CORNER_TIMEOUT = 2;
+ private static final int SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS = 6000;
+
+ // From WizardManagerHelper.java
+ private static final String SETTINGS_SECURE_USER_SETUP_COMPLETE = "user_setup_complete";
+
+ private final Context mContext;
+ private final ScreenshotNotificationsController mNotificationsController;
+ private final ScreenshotSmartActions mScreenshotSmartActions;
+ private final UiEventLogger mUiEventLogger;
+
+ private final WindowManager mWindowManager;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ private final Display mDisplay;
+ private final DisplayMetrics mDisplayMetrics;
+ private final AccessibilityManager mAccessibilityManager;
+ private final MediaActionSound mCameraSound;
+ private final ScrollCaptureClient mScrollCaptureClient;
+ private final DeviceConfigProxy mConfigProxy;
+
+ private final Binder mWindowToken;
+ private ScreenshotView mScreenshotView;
+ private Bitmap mScreenBitmap;
+ private SaveImageInBackgroundTask mSaveInBgTask;
+
+ private Animator mScreenshotAnimation;
+ private Animator mDismissAnimation;
+
+ private Runnable mOnCompleteRunnable;
+ private boolean mInDarkMode;
+ private boolean mDirectionLTR;
+ private boolean mOrientationPortrait;
+
+ private final Handler mScreenshotHandler = new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_CORNER_TIMEOUT:
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
+ ScreenshotController.this.dismissScreenshot(false);
+ mOnCompleteRunnable.run();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ @Inject
+ ScreenshotController(
+ Context context,
+ ScreenshotSmartActions screenshotSmartActions,
+ ScreenshotNotificationsController screenshotNotificationsController,
+ ScrollCaptureClient scrollCaptureClient,
+ UiEventLogger uiEventLogger,
+ DeviceConfigProxy configProxy) {
+ mScreenshotSmartActions = screenshotSmartActions;
+ mNotificationsController = screenshotNotificationsController;
+ mScrollCaptureClient = scrollCaptureClient;
+ mUiEventLogger = uiEventLogger;
+
+ final DisplayManager dm = requireNonNull(context.getSystemService(DisplayManager.class));
+ mDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+ mContext = context.createDisplayContext(mDisplay);
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+
+ mAccessibilityManager = AccessibilityManager.getInstance(mContext);
+ mConfigProxy = configProxy;
+
+ reloadAssets();
+ Configuration config = mContext.getResources().getConfiguration();
+ mInDarkMode = config.isNightModeActive();
+ mDirectionLTR = config.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+ mOrientationPortrait = config.orientation == ORIENTATION_PORTRAIT;
+ mWindowToken = new Binder("ScreenshotController");
+ mScrollCaptureClient.setHostWindowToken(mWindowToken);
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+ WindowManager.LayoutParams.TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ PixelFormat.TRANSLUCENT);
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+ mWindowLayoutParams.layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setFitInsetsTypes(0 /* types */);
+ mWindowLayoutParams.token = mWindowToken;
+
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplay.getRealMetrics(mDisplayMetrics);
+
+ // Setup the Camera shutter sound
+ mCameraSound = new MediaActionSound();
+ mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+ }
+
+ void takeScreenshotFullscreen(Consumer<Uri> finisher, Runnable onComplete) {
+ mOnCompleteRunnable = onComplete;
+
+ mDisplay.getRealMetrics(mDisplayMetrics);
+ takeScreenshotInternal(
+ finisher,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+ }
+
+ void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
+ Insets visibleInsets, int taskId, int userId, ComponentName topComponent,
+ Consumer<Uri> finisher, Runnable onComplete) {
+ // TODO: use task Id, userId, topComponent for smart handler
+ mOnCompleteRunnable = onComplete;
+
+ if (screenshot == null) {
+ Log.e(TAG, "Got null bitmap from screenshot message");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ mOnCompleteRunnable.run();
+ return;
+ }
+
+ if (aspectRatiosMatch(screenshot, visibleInsets, screenshotScreenBounds)) {
+ saveScreenshot(screenshot, finisher, screenshotScreenBounds, visibleInsets, false);
+ } else {
+ saveScreenshot(screenshot, finisher,
+ new Rect(0, 0, screenshot.getWidth(), screenshot.getHeight()), Insets.NONE,
+ true);
+ }
+ }
+
+ /**
+ * Displays a screenshot selector
+ */
+ @SuppressLint("ClickableViewAccessibility")
+ void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
+ dismissScreenshot(true);
+ mOnCompleteRunnable = onComplete;
+
+ mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
+
+ mScreenshotView.takePartialScreenshot(
+ rect -> takeScreenshotInternal(finisher, rect));
+ }
+
+ boolean isDismissing() {
+ return (mDismissAnimation != null && mDismissAnimation.isRunning());
+ }
+
+ /**
+ * Clears current screenshot
+ */
+ void dismissScreenshot(boolean immediate) {
+ Log.v(TAG, "clearing screenshot");
+ mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+ mScreenshotView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
+ mScreenshotView);
+ if (!immediate) {
+ mDismissAnimation = mScreenshotView.createScreenshotDismissAnimation();
+ mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ clearScreenshot();
+ }
+ });
+ mDismissAnimation.start();
+ } else {
+ clearScreenshot();
+ }
+ }
+
+ private void onConfigChanged(Configuration newConfig) {
+ boolean needsUpdate = false;
+ // dark mode
+ if (newConfig.isNightModeActive()) {
+ // Night mode is active, we're using dark theme
+ if (!mInDarkMode) {
+ mInDarkMode = true;
+ needsUpdate = true;
+ }
+ } else {
+ // Night mode is not active, we're using the light theme
+ if (mInDarkMode) {
+ mInDarkMode = false;
+ needsUpdate = true;
+ }
+ }
+
+ // RTL configuration
+ switch (newConfig.getLayoutDirection()) {
+ case View.LAYOUT_DIRECTION_LTR:
+ if (!mDirectionLTR) {
+ mDirectionLTR = true;
+ needsUpdate = true;
+ }
+ break;
+ case View.LAYOUT_DIRECTION_RTL:
+ if (mDirectionLTR) {
+ mDirectionLTR = false;
+ needsUpdate = true;
+ }
+ break;
+ }
+
+ // portrait/landscape orientation
+ switch (newConfig.orientation) {
+ case ORIENTATION_PORTRAIT:
+ if (!mOrientationPortrait) {
+ mOrientationPortrait = true;
+ needsUpdate = true;
+ }
+ break;
+ case ORIENTATION_LANDSCAPE:
+ if (mOrientationPortrait) {
+ mOrientationPortrait = false;
+ needsUpdate = true;
+ }
+ break;
+ }
+
+ if (needsUpdate) {
+ reloadAssets();
+ }
+ }
+
+ /**
+ * Update assets (called when the dark theme status changes). We only need to update the dismiss
+ * button and the actions container background, since the buttons are re-inflated on demand.
+ */
+ private void reloadAssets() {
+ boolean wasAttached = mScreenshotView != null && mScreenshotView.isAttachedToWindow();
+ if (wasAttached) {
+ mWindowManager.removeView(mScreenshotView);
+ }
+
+ // Inflate the screenshot layout
+ mScreenshotView = (ScreenshotView)
+ LayoutInflater.from(mContext).inflate(R.layout.global_screenshot, null);
+
+ // TODO(159460485): Remove this when focus is handled properly in the system
+ mScreenshotView.setOnTouchListener((v, event) -> {
+ if (event.getActionMasked() == MotionEvent.ACTION_OUTSIDE) {
+ // Once the user touches outside, stop listening for input
+ setWindowFocusable(false);
+ }
+ return false;
+ });
+
+ mScreenshotView.setOnKeyListener((v, keyCode, event) -> {
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ dismissScreenshot(false);
+ return true;
+ }
+ return false;
+ });
+
+ if (wasAttached) {
+ mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
+ }
+ }
+
+ /**
+ * Takes a screenshot of the current display and shows an animation.
+ */
+ private void takeScreenshotInternal(Consumer<Uri> finisher, Rect crop) {
+ // copy the input Rect, since SurfaceControl.screenshot can mutate it
+ Rect screenRect = new Rect(crop);
+ int width = crop.width();
+ int height = crop.height();
+ final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
+ final SurfaceControl.DisplayCaptureArgs captureArgs =
+ new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
+ .setSourceCrop(crop)
+ .setSize(width, height)
+ .build();
+ final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
+ SurfaceControl.captureDisplay(captureArgs);
+ Bitmap screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
+
+ if (screenshot == null) {
+ Log.e(TAG, "Screenshot bitmap was null");
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ mOnCompleteRunnable.run();
+ return;
+ }
+
+ saveScreenshot(screenshot, finisher, screenRect, Insets.NONE, true);
+ }
+
+ private void saveScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect,
+ Insets screenInsets, boolean showFlash) {
+ if (mAccessibilityManager.isEnabled()) {
+ AccessibilityEvent event =
+ new AccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ event.setContentDescription(
+ mContext.getResources().getString(R.string.screenshot_saving_title));
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+
+ if (mScreenshotView.isAttachedToWindow()) {
+ // if we didn't already dismiss for another reason
+ if (mDismissAnimation == null || !mDismissAnimation.isRunning()) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_REENTERED);
+ }
+ dismissScreenshot(true);
+ }
+
+ mScreenBitmap = screenshot;
+
+ if (!isUserSetupComplete()) {
+ // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
+ // and sharing shouldn't be exposed to the user.
+ saveScreenshotAndToast(finisher);
+ return;
+ }
+
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ onConfigChanged(mContext.getResources().getConfiguration());
+
+ if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ mDismissAnimation.cancel();
+ }
+
+ // The window is focusable by default
+ setWindowFocusable(true);
+
+ // Start the post-screenshot animation
+ startAnimation(finisher, screenRect, screenInsets, showFlash);
+
+ if (mConfigProxy.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED, false)) {
+ mScrollCaptureClient.request(DEFAULT_DISPLAY, (connection) ->
+ mScreenshotView.showScrollChip(() ->
+ runScrollCapture(connection,
+ () -> dismissScreenshot(false))));
+ }
+ }
+
+ private void runScrollCapture(ScrollCaptureClient.Connection connection,
+ Runnable after) {
+ new ScrollCaptureController(mContext, connection).run(after);
+ }
+
+ /**
+ * Save the bitmap but don't show the normal screenshot UI.. just a toast (or notification on
+ * failure).
+ */
+ private void saveScreenshotAndToast(Consumer<Uri> finisher) {
+ // Play the shutter sound to notify that we've taken a screenshot
+ mScreenshotHandler.post(() -> {
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+ });
+
+ saveScreenshotInWorkerThread(finisher,
+ new ScreenshotController.ActionsReadyListener() {
+ @Override
+ void onActionsReady(ScreenshotController.SavedImageData imageData) {
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
+
+ mScreenshotHandler.post(() -> {
+ Toast.makeText(mContext, R.string.screenshot_saved_title,
+ Toast.LENGTH_SHORT).show();
+ });
+ }
+ }
+ });
+ }
+
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(final Consumer<Uri> finisher, Rect screenRect, Insets screenInsets,
+ boolean showFlash) {
+ mScreenshotHandler.post(() -> {
+ if (!mScreenshotView.isAttachedToWindow()) {
+ mWindowManager.addView(mScreenshotView, mWindowLayoutParams);
+ }
+
+ mScreenshotView.prepareForAnimation(mScreenBitmap, screenRect, screenInsets);
+
+ mScreenshotHandler.post(() -> {
+ mScreenshotView.getViewTreeObserver().addOnComputeInternalInsetsListener(
+ mScreenshotView);
+
+ mScreenshotAnimation =
+ mScreenshotView.createScreenshotDropInAnimation(screenRect, showFlash,
+ this::onElementTapped);
+
+ saveScreenshotInWorkerThread(finisher,
+ new ScreenshotController.ActionsReadyListener() {
+ @Override
+ void onActionsReady(
+ ScreenshotController.SavedImageData imageData) {
+ showUiOnActionsReady(imageData);
+ }
+ });
+
+ // Play the shutter sound to notify that we've taken a screenshot
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+ mScreenshotAnimation.start();
+ });
+ });
+ }
+
+ /**
+ * Creates a new worker thread and saves the screenshot to the media store.
+ */
+ private void saveScreenshotInWorkerThread(
+ Consumer<Uri> finisher,
+ @Nullable ScreenshotController.ActionsReadyListener actionsReadyListener) {
+ ScreenshotController.SaveImageInBackgroundData
+ data = new ScreenshotController.SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+
+ if (mSaveInBgTask != null) {
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(
+ new ScreenshotController.ActionsReadyListener() {
+ @Override
+ void onActionsReady(ScreenshotController.SavedImageData imageData) {
+ logSuccessOnActionsReady(imageData);
+ }
+ });
+ }
+
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mScreenshotSmartActions, data);
+ mSaveInBgTask.execute();
+ }
+
+ /**
+ * Sets up the action shade and its entrance animation, once we get the screenshot URI.
+ */
+ private void showUiOnActionsReady(ScreenshotController.SavedImageData imageData) {
+ logSuccessOnActionsReady(imageData);
+
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+ SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
+ mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+ mScreenshotHandler.sendMessageDelayed(
+ mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+ timeoutMs);
+
+ if (imageData.uri != null) {
+ mScreenshotHandler.post(() -> {
+ if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mScreenshotView.setChipIntents(
+ imageData, event -> onElementTapped(event));
+ }
+ });
+ } else {
+ mScreenshotView.setChipIntents(
+ imageData, this::onElementTapped);
+ }
+ });
+ }
+ }
+
+ private void onElementTapped(ScreenshotEvent event) {
+ mUiEventLogger.log(event);
+ dismissScreenshot(false);
+ mOnCompleteRunnable.run();
+ }
+
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) {
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_save_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
+ }
+ }
+
+ private void clearScreenshot() {
+ if (mScreenshotView.isAttachedToWindow()) {
+ mWindowManager.removeView(mScreenshotView);
+ }
+
+ mScreenshotView.reset();
+ }
+
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+ }
+
+
+ /**
+ * Updates the window focusability. If the window is already showing, then it updates the
+ * window immediately, otherwise the layout params will be applied when the window is next
+ * shown.
+ */
+ private void setWindowFocusable(boolean focusable) {
+ if (focusable) {
+ mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ } else {
+ mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ }
+ if (mScreenshotView.isAttachedToWindow()) {
+ mWindowManager.updateViewLayout(mScreenshotView, mWindowLayoutParams);
+ }
+ }
+
+ /** Does the aspect ratio of the bitmap with insets removed match the bounds. */
+ private static boolean aspectRatiosMatch(Bitmap bitmap, Insets bitmapInsets,
+ Rect screenBounds) {
+ int insettedWidth = bitmap.getWidth() - bitmapInsets.left - bitmapInsets.right;
+ int insettedHeight = bitmap.getHeight() - bitmapInsets.top - bitmapInsets.bottom;
+
+ if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
+ || bitmap.getHeight() == 0) {
+ Log.e(TAG, String.format(
+ "Provided bitmap and insets create degenerate region: %dx%d %s",
+ bitmap.getWidth(), bitmap.getHeight(), bitmapInsets));
+ return false;
+ }
+
+ float insettedBitmapAspect = ((float) insettedWidth) / insettedHeight;
+ float boundsAspect = ((float) screenBounds.width()) / screenBounds.height();
+
+ boolean matchWithinTolerance = Math.abs(insettedBitmapAspect - boundsAspect) < 0.1f;
+ if (!matchWithinTolerance) {
+ Log.d(TAG, String.format("aspectRatiosMatch: don't match bitmap: %f, bounds: %f",
+ insettedBitmapAspect, boundsAspect));
+ }
+
+ return matchWithinTolerance;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 6d1299ba98ac..db5a4941ca58 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -25,13 +25,6 @@ import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.Picture;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.WindowManager;
@@ -52,179 +45,16 @@ public class ScreenshotNotificationsController {
private final Context mContext;
private final Resources mResources;
private final NotificationManager mNotificationManager;
- private final Notification.BigPictureStyle mNotificationStyle;
-
- private int mIconSize;
- private int mPreviewWidth, mPreviewHeight;
- private Notification.Builder mNotificationBuilder, mPublicNotificationBuilder;
@Inject
ScreenshotNotificationsController(Context context, WindowManager windowManager) {
mContext = context;
mResources = context.getResources();
-
mNotificationManager =
(NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
- mIconSize = mResources.getDimensionPixelSize(
- android.R.dimen.notification_large_icon_height);
-
DisplayMetrics displayMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
-
-
- // determine the optimal preview size
- int panelWidth = 0;
- try {
- panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
- } catch (Resources.NotFoundException e) {
- }
- if (panelWidth <= 0) {
- // includes notification_panel_width==match_parent (-1)
- panelWidth = displayMetrics.widthPixels;
- }
- mPreviewWidth = panelWidth;
- mPreviewHeight = mResources.getDimensionPixelSize(R.dimen.notification_max_height);
-
- // Setup the notification
- mNotificationStyle = new Notification.BigPictureStyle();
- }
-
- /**
- * Resets the notification builders.
- */
- public void reset() {
- // The public notification will show similar info but with the actual screenshot omitted
- mPublicNotificationBuilder =
- new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
- mNotificationBuilder =
- new Notification.Builder(mContext, NotificationChannels.SCREENSHOTS_HEADSUP);
- }
-
- /**
- * Sets the current screenshot bitmap.
- *
- * @param image the bitmap of the current screenshot (used for preview)
- */
- public void setImage(Bitmap image) {
- // Create the large notification icon
- int imageWidth = image.getWidth();
- int imageHeight = image.getHeight();
-
- Paint paint = new Paint();
- ColorMatrix desat = new ColorMatrix();
- desat.setSaturation(0.25f);
- paint.setColorFilter(new ColorMatrixColorFilter(desat));
- Matrix matrix = new Matrix();
- int overlayColor = 0x40FFFFFF;
-
- matrix.setTranslate((mPreviewWidth - imageWidth) / 2f, (mPreviewHeight - imageHeight) / 2f);
-
- Bitmap picture = generateAdjustedHwBitmap(
- image, mPreviewWidth, mPreviewHeight, matrix, paint, overlayColor);
-
- mNotificationStyle.bigPicture(picture.asShared());
-
- // Note, we can't use the preview for the small icon, since it is non-square
- float scale = (float) mIconSize / Math.min(imageWidth, imageHeight);
- matrix.setScale(scale, scale);
- matrix.postTranslate(
- (mIconSize - (scale * imageWidth)) / 2,
- (mIconSize - (scale * imageHeight)) / 2);
- Bitmap icon =
- generateAdjustedHwBitmap(image, mIconSize, mIconSize, matrix, paint, overlayColor);
-
- /**
- * NOTE: The following code prepares the notification builder for updating the
- * notification after the screenshot has been written to disk.
- */
-
- // On the tablet, the large icon makes the notification appear as if it is clickable
- // (and on small devices, the large icon is not shown) so defer showing the large icon
- // until we compose the final post-save notification below.
- mNotificationBuilder.setLargeIcon(icon.asShared());
- // But we still don't set it for the expanded view, allowing the smallIcon to show here.
- mNotificationStyle.bigLargeIcon((Bitmap) null);
- }
-
- /**
- * Shows a notification to inform the user that a screenshot is currently being saved.
- */
- public void showSavingScreenshotNotification() {
- final long now = System.currentTimeMillis();
-
- mPublicNotificationBuilder
- .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
- .setSmallIcon(R.drawable.stat_notify_image)
- .setCategory(Notification.CATEGORY_PROGRESS)
- .setWhen(now)
- .setShowWhen(true)
- .setColor(mResources.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- SystemUI.overrideNotificationAppName(mContext, mPublicNotificationBuilder, true);
-
- mNotificationBuilder
- .setContentTitle(mResources.getString(R.string.screenshot_saving_title))
- .setSmallIcon(R.drawable.stat_notify_image)
- .setWhen(now)
- .setShowWhen(true)
- .setColor(mResources.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setStyle(mNotificationStyle)
- .setPublicVersion(mPublicNotificationBuilder.build());
- mNotificationBuilder.setFlag(Notification.FLAG_NO_CLEAR, true);
- SystemUI.overrideNotificationAppName(mContext, mNotificationBuilder, true);
-
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
- mNotificationBuilder.build());
- }
-
- /**
- * Shows a notification with the saved screenshot and actions that can be taken with it.
- *
- * @param actionData SavedImageData struct with image URI and actions
- */
- public void showScreenshotActionsNotification(
- 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(actionData.uri, "image/png");
- launchIntent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
- final long now = System.currentTimeMillis();
-
- // Update the text and the icon for the existing notification
- mPublicNotificationBuilder
- .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
- .setContentText(mResources.getString(R.string.screenshot_saved_text))
- .setContentIntent(PendingIntent
- .getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE))
- .setWhen(now)
- .setAutoCancel(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color));
- mNotificationBuilder
- .setContentTitle(mResources.getString(R.string.screenshot_saved_title))
- .setContentText(mResources.getString(R.string.screenshot_saved_text))
- .setContentIntent(PendingIntent
- .getActivity(mContext, 0, launchIntent, PendingIntent.FLAG_IMMUTABLE))
- .setWhen(now)
- .setAutoCancel(true)
- .setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setPublicVersion(mPublicNotificationBuilder.build())
- .setFlag(Notification.FLAG_NO_CLEAR, false);
-
- mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT,
- mNotificationBuilder.build());
}
/**
@@ -263,31 +93,4 @@ public class ScreenshotNotificationsController {
.build();
mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT, n);
}
-
- /**
- * Cancels the current screenshot notification.
- */
- public void cancelNotification() {
- mNotificationManager.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- }
-
- /**
- * Generates a new hardware bitmap with specified values, copying the content from the
- * passed in bitmap.
- */
- private Bitmap generateAdjustedHwBitmap(Bitmap bitmap, int width, int height, Matrix matrix,
- Paint paint, int color) {
- Picture picture = new Picture();
- Canvas canvas = picture.beginRecording(width, height);
- canvas.drawColor(color);
- canvas.drawBitmap(bitmap, matrix, paint);
- picture.endRecording();
- return Bitmap.createBitmap(picture);
- }
-
- static void cancelScreenshotNotification(Context context) {
- final NotificationManager nm =
- (NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
- nm.cancel(SystemMessageProto.SystemMessage.NOTE_GLOBAL_SCREENSHOT);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
index 07a92460f3ea..c793b5b9639e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSelectorView.java
@@ -26,8 +26,11 @@ import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
+import java.util.function.Consumer;
+
/**
* Draws a selection rectangle while taking screenshot
*/
@@ -36,6 +39,8 @@ public class ScreenshotSelectorView extends View {
private Rect mSelectionRect;
private final Paint mPaintSelection, mPaintBackground;
+ private Consumer<Rect> mOnScreenshotSelected;
+
public ScreenshotSelectorView(Context context) {
this(context, null);
}
@@ -46,14 +51,54 @@ public class ScreenshotSelectorView extends View {
mPaintBackground.setAlpha(160);
mPaintSelection = new Paint(Color.TRANSPARENT);
mPaintSelection.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+
+ setOnTouchListener((v, event) -> {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ startSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ updateSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_UP:
+ setVisibility(View.GONE);
+ final Rect rect = getSelectionRect();
+ if (mOnScreenshotSelected != null
+ && rect != null
+ && rect.width() != 0 && rect.height() != 0) {
+ mOnScreenshotSelected.accept(rect);
+ }
+ stopSelection();
+ return true;
+ }
+ return false;
+ });
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawRect(mLeft, mTop, mRight, mBottom, mPaintBackground);
+ if (mSelectionRect != null) {
+ canvas.drawRect(mSelectionRect, mPaintSelection);
+ }
+ }
+
+ void setOnScreenshotSelected(Consumer<Rect> onScreenshotSelected) {
+ mOnScreenshotSelected = onScreenshotSelected;
+ }
+
+ void stop() {
+ if (getSelectionRect() != null) {
+ stopSelection();
+ }
}
- public void startSelection(int x, int y) {
+ private void startSelection(int x, int y) {
mStartPoint = new Point(x, y);
mSelectionRect = new Rect(x, y, x, y);
}
- public void updateSelection(int x, int y) {
+ private void updateSelection(int x, int y) {
if (mSelectionRect != null) {
mSelectionRect.left = Math.min(mStartPoint.x, x);
mSelectionRect.right = Math.max(mStartPoint.x, x);
@@ -63,20 +108,12 @@ public class ScreenshotSelectorView extends View {
}
}
- public Rect getSelectionRect() {
+ private Rect getSelectionRect() {
return mSelectionRect;
}
- public void stopSelection() {
+ private void stopSelection() {
mStartPoint = null;
mSelectionRect = null;
}
-
- @Override
- public void draw(Canvas canvas) {
- canvas.drawRect(mLeft, mTop, mRight, mBottom, mPaintBackground);
- if (mSelectionRect != null) {
- canvas.drawRect(mSelectionRect, mPaintSelection);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
new file mode 100644
index 000000000000..3383f80cd2b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -0,0 +1,597 @@
+/*
+ * 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.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static java.util.Objects.requireNonNull;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Insets;
+import android.graphics.Outline;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.HorizontalScrollView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.shared.system.QuickStepContract;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * Handles the visual elements and animations for the screenshot flow.
+ */
+public class ScreenshotView extends FrameLayout implements
+ ViewTreeObserver.OnComputeInternalInsetsListener {
+
+ private static final String TAG = "ScreenshotView";
+
+ private static final long SCREENSHOT_FLASH_IN_DURATION_MS = 133;
+ private static final long SCREENSHOT_FLASH_OUT_DURATION_MS = 217;
+ // delay before starting to fade in dismiss button
+ private static final long SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS = 200;
+ private static final long SCREENSHOT_TO_CORNER_X_DURATION_MS = 234;
+ private static final long SCREENSHOT_TO_CORNER_Y_DURATION_MS = 500;
+ private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
+ private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
+ private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
+ private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
+ private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
+ private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
+ private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
+ private static final float ROUNDED_CORNER_RADIUS = .05f;
+
+ private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
+
+ private final Resources mResources;
+ private final Interpolator mFastOutSlowIn;
+ private final DisplayMetrics mDisplayMetrics;
+ private final float mCornerSizeX;
+ private final float mDismissDeltaY;
+
+ private int mNavMode;
+ private int mLeftInset;
+ private int mRightInset;
+ private boolean mOrientationPortrait;
+ private boolean mDirectionLTR;
+
+ private ScreenshotSelectorView mScreenshotSelectorView;
+ private ImageView mScreenshotPreview;
+ private ImageView mScreenshotFlash;
+ private ImageView mActionsContainerBackground;
+ private HorizontalScrollView mActionsContainer;
+ private LinearLayout mActionsView;
+ private ImageView mBackgroundProtection;
+ private FrameLayout mDismissButton;
+ private ScreenshotActionChip mShareChip;
+ private ScreenshotActionChip mEditChip;
+ private ScreenshotActionChip mScrollChip;
+
+ private final ArrayList<ScreenshotActionChip> mSmartChips = new ArrayList<>();
+ private PendingInteraction mPendingInteraction;
+
+ private enum PendingInteraction {
+ PREVIEW,
+ EDIT,
+ SHARE
+ }
+
+ public ScreenshotView(Context context) {
+ this(context, null);
+ }
+
+ public ScreenshotView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ScreenshotView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ScreenshotView(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mResources = mContext.getResources();
+
+ mCornerSizeX = mResources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
+ mDismissDeltaY = mResources.getDimensionPixelSize(
+ R.dimen.screenshot_dismissal_height_delta);
+
+ // standard material ease
+ mFastOutSlowIn =
+ AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
+
+ mDisplayMetrics = new DisplayMetrics();
+ mContext.getDisplay().getRealMetrics(mDisplayMetrics);
+ }
+
+ /**
+ * Called to display the scroll action chip when support is detected.
+ *
+ * @param onClick the action to take when the chip is clicked.
+ */
+ public void showScrollChip(Runnable onClick) {
+ mScrollChip.setVisibility(VISIBLE);
+ mScrollChip.setOnClickListener((v) ->
+ onClick.run()
+ // TODO Logging, store event consumer to a field
+ //onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
+ );
+ }
+
+ @Override // ViewTreeObserver.OnComputeInternalInsetsListener
+ public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+ inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+ Region touchRegion = new Region();
+
+ Rect screenshotRect = new Rect();
+ mScreenshotPreview.getBoundsOnScreen(screenshotRect);
+ touchRegion.op(screenshotRect, Region.Op.UNION);
+ Rect actionsRect = new Rect();
+ mActionsContainer.getBoundsOnScreen(actionsRect);
+ touchRegion.op(actionsRect, Region.Op.UNION);
+ Rect dismissRect = new Rect();
+ mDismissButton.getBoundsOnScreen(dismissRect);
+ touchRegion.op(dismissRect, Region.Op.UNION);
+
+ if (QuickStepContract.isGesturalMode(mNavMode)) {
+ // Receive touches in gesture insets such that they don't cause TOUCH_OUTSIDE
+ Rect inset = new Rect(0, 0, mLeftInset, mDisplayMetrics.heightPixels);
+ touchRegion.op(inset, Region.Op.UNION);
+ inset.set(mDisplayMetrics.widthPixels - mRightInset, 0, mDisplayMetrics.widthPixels,
+ mDisplayMetrics.heightPixels);
+ touchRegion.op(inset, Region.Op.UNION);
+ }
+
+ inoutInfo.touchableRegion.set(touchRegion);
+ }
+
+ @Override // View
+ protected void onFinishInflate() {
+ mScreenshotPreview = requireNonNull(findViewById(R.id.global_screenshot_preview));
+ mActionsContainerBackground = requireNonNull(findViewById(
+ R.id.global_screenshot_actions_container_background));
+ mActionsContainer = requireNonNull(findViewById(R.id.global_screenshot_actions_container));
+ mActionsView = requireNonNull(findViewById(R.id.global_screenshot_actions));
+ mBackgroundProtection = requireNonNull(
+ findViewById(R.id.global_screenshot_actions_background));
+ mDismissButton = requireNonNull(findViewById(R.id.global_screenshot_dismiss_button));
+ mScreenshotFlash = requireNonNull(findViewById(R.id.global_screenshot_flash));
+ mScreenshotSelectorView = requireNonNull(findViewById(R.id.global_screenshot_selector));
+ mShareChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_share_chip));
+ mEditChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_edit_chip));
+ mScrollChip = requireNonNull(mActionsContainer.findViewById(R.id.screenshot_scroll_chip));
+
+ mScreenshotPreview.setClipToOutline(true);
+ mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()),
+ ROUNDED_CORNER_RADIUS * view.getWidth());
+ }
+ });
+
+ setFocusable(true);
+ mScreenshotSelectorView.setFocusable(true);
+ mScreenshotSelectorView.setFocusableInTouchMode(true);
+ mActionsContainer.setScrollX(0);
+
+ mNavMode = getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode);
+ mOrientationPortrait =
+ getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
+ mDirectionLTR =
+ getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+
+ setOnApplyWindowInsetsListener((v, insets) -> {
+ if (QuickStepContract.isGesturalMode(mNavMode)) {
+ Insets gestureInsets = insets.getInsets(
+ WindowInsets.Type.systemGestures());
+ mLeftInset = gestureInsets.left;
+ mRightInset = gestureInsets.right;
+ } else {
+ mLeftInset = mRightInset = 0;
+ }
+ return ScreenshotView.this.onApplyWindowInsets(insets);
+ });
+
+ // Get focus so that the key events go to the layout.
+ setFocusableInTouchMode(true);
+ requestFocus();
+ }
+
+ void takePartialScreenshot(Consumer<Rect> onPartialScreenshotSelected) {
+ mScreenshotSelectorView.setOnScreenshotSelected(onPartialScreenshotSelected);
+ mScreenshotSelectorView.setVisibility(View.VISIBLE);
+ mScreenshotSelectorView.requestFocus();
+ }
+
+ void prepareForAnimation(Bitmap bitmap, Rect screenRect, Insets screenInsets) {
+ mScreenshotPreview.setImageDrawable(createScreenDrawable(mResources, bitmap, screenInsets));
+ // make static preview invisible (from gone) so we can query its location on screen
+ mScreenshotPreview.setVisibility(View.INVISIBLE);
+ }
+
+ AnimatorSet createScreenshotDropInAnimation(Rect bounds, boolean showFlash,
+ Consumer<ScreenshotEvent> onElementTapped) {
+ mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mScreenshotPreview.buildLayer();
+
+ Rect previewBounds = new Rect();
+ mScreenshotPreview.getBoundsOnScreen(previewBounds);
+
+ float cornerScale =
+ mCornerSizeX / (mOrientationPortrait ? bounds.width() : bounds.height());
+ final float currentScale = 1 / cornerScale;
+
+ mScreenshotPreview.setScaleX(currentScale);
+ mScreenshotPreview.setScaleY(currentScale);
+
+ mDismissButton.setAlpha(0);
+ mDismissButton.setVisibility(View.VISIBLE);
+
+ AnimatorSet dropInAnimation = new AnimatorSet();
+ ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1);
+ flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS);
+ flashInAnimator.setInterpolator(mFastOutSlowIn);
+ flashInAnimator.addUpdateListener(animation ->
+ mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
+
+ ValueAnimator flashOutAnimator = ValueAnimator.ofFloat(1, 0);
+ flashOutAnimator.setDuration(SCREENSHOT_FLASH_OUT_DURATION_MS);
+ flashOutAnimator.setInterpolator(mFastOutSlowIn);
+ flashOutAnimator.addUpdateListener(animation ->
+ mScreenshotFlash.setAlpha((float) animation.getAnimatedValue()));
+
+ // animate from the current location, to the static preview location
+ final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
+ final PointF finalPos = new PointF(previewBounds.centerX(), previewBounds.centerY());
+
+ ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
+ toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
+ float xPositionPct =
+ SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ float dismissPct =
+ SCREENSHOT_TO_CORNER_DISMISS_DELAY_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ float scalePct =
+ SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ toCorner.addUpdateListener(animation -> {
+ float t = animation.getAnimatedFraction();
+ if (t < scalePct) {
+ float scale = MathUtils.lerp(
+ currentScale, 1, mFastOutSlowIn.getInterpolation(t / scalePct));
+ mScreenshotPreview.setScaleX(scale);
+ mScreenshotPreview.setScaleY(scale);
+ } else {
+ mScreenshotPreview.setScaleX(1);
+ mScreenshotPreview.setScaleY(1);
+ }
+
+ if (t < xPositionPct) {
+ float xCenter = MathUtils.lerp(startPos.x, finalPos.x,
+ mFastOutSlowIn.getInterpolation(t / xPositionPct));
+ mScreenshotPreview.setX(xCenter - mScreenshotPreview.getWidth() / 2f);
+ } else {
+ mScreenshotPreview.setX(finalPos.x - mScreenshotPreview.getWidth() / 2f);
+ }
+ float yCenter = MathUtils.lerp(
+ startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t));
+ mScreenshotPreview.setY(yCenter - mScreenshotPreview.getHeight() / 2f);
+
+ if (t >= dismissPct) {
+ mDismissButton.setAlpha((t - dismissPct) / (1 - dismissPct));
+ float currentX = mScreenshotPreview.getX();
+ float currentY = mScreenshotPreview.getY();
+ mDismissButton.setY(currentY - mDismissButton.getHeight() / 2f);
+ if (mDirectionLTR) {
+ mDismissButton.setX(currentX + mScreenshotPreview.getWidth()
+ - mDismissButton.getWidth() / 2f);
+ } else {
+ mDismissButton.setX(currentX - mDismissButton.getWidth() / 2f);
+ }
+ }
+ });
+
+ toCorner.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mScreenshotPreview.setVisibility(View.VISIBLE);
+ }
+ });
+
+ mScreenshotFlash.setAlpha(0f);
+ mScreenshotFlash.setVisibility(View.VISIBLE);
+
+ if (showFlash) {
+ dropInAnimation.play(flashOutAnimator).after(flashInAnimator);
+ dropInAnimation.play(flashOutAnimator).with(toCorner);
+ } else {
+ dropInAnimation.play(toCorner);
+ }
+
+ dropInAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mDismissButton.setOnClickListener(view ->
+ onElementTapped.accept(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL));
+ mDismissButton.setAlpha(1);
+ float dismissOffset = mDismissButton.getWidth() / 2f;
+ float finalDismissX = mDirectionLTR
+ ? finalPos.x - dismissOffset + bounds.width() * cornerScale / 2f
+ : finalPos.x - dismissOffset - bounds.width() * cornerScale / 2f;
+ mDismissButton.setX(finalDismissX);
+ mDismissButton.setY(
+ finalPos.y - dismissOffset - bounds.height() * cornerScale / 2f);
+ mScreenshotPreview.setScaleX(1);
+ mScreenshotPreview.setScaleY(1);
+ mScreenshotPreview.setX(finalPos.x - bounds.width() * cornerScale / 2f);
+ mScreenshotPreview.setY(finalPos.y - bounds.height() * cornerScale / 2f);
+ requestLayout();
+ createScreenshotActionsShadeAnimation().start();
+ }
+ });
+
+ return dropInAnimation;
+ }
+
+ ValueAnimator createScreenshotActionsShadeAnimation() {
+ // By default the activities won't be able to start immediately; override this to keep
+ // the same behavior as if started from a notification
+ try {
+ ActivityManager.getService().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+
+ ArrayList<ScreenshotActionChip> chips = new ArrayList<>();
+
+ mShareChip.setText(mContext.getString(com.android.internal.R.string.share));
+ mShareChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_share), true);
+ mShareChip.setOnClickListener(v -> {
+ mShareChip.setIsPending(true);
+ mEditChip.setIsPending(false);
+ mPendingInteraction = PendingInteraction.SHARE;
+ });
+ chips.add(mShareChip);
+
+ mEditChip.setText(mContext.getString(R.string.screenshot_edit_label));
+ mEditChip.setIcon(Icon.createWithResource(mContext, R.drawable.ic_screenshot_edit), true);
+ mEditChip.setOnClickListener(v -> {
+ mEditChip.setIsPending(true);
+ mShareChip.setIsPending(false);
+ mPendingInteraction = PendingInteraction.EDIT;
+ });
+ chips.add(mEditChip);
+
+ mScreenshotPreview.setOnClickListener(v -> {
+ mShareChip.setIsPending(false);
+ mEditChip.setIsPending(false);
+ mPendingInteraction = PendingInteraction.PREVIEW;
+ });
+
+ mScrollChip.setText(mContext.getString(R.string.screenshot_scroll_label));
+ mScrollChip.setIcon(Icon.createWithResource(mContext,
+ R.drawable.ic_screenshot_scroll), true);
+ chips.add(mScrollChip);
+
+ // remove the margin from the last chip so that it's correctly aligned with the end
+ LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)
+ mActionsView.getChildAt(0).getLayoutParams();
+ params.setMarginEnd(0);
+ mActionsView.getChildAt(0).setLayoutParams(params);
+
+ ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
+ float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
+ / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
+ mActionsContainer.setAlpha(0f);
+ mActionsContainerBackground.setAlpha(0f);
+ mActionsContainer.setVisibility(View.VISIBLE);
+ mActionsContainerBackground.setVisibility(View.VISIBLE);
+
+ animator.addUpdateListener(animation -> {
+ float t = animation.getAnimatedFraction();
+ mBackgroundProtection.setAlpha(t);
+ float containerAlpha = t < alphaFraction ? t / alphaFraction : 1;
+ mActionsContainer.setAlpha(containerAlpha);
+ mActionsContainerBackground.setAlpha(containerAlpha);
+ float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
+ + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
+ mActionsContainer.setScaleX(containerScale);
+ mActionsContainerBackground.setScaleX(containerScale);
+ for (ScreenshotActionChip chip : chips) {
+ chip.setAlpha(t);
+ chip.setScaleX(1 / containerScale); // invert to keep size of children constant
+ }
+ mActionsContainer.setScrollX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
+ mActionsContainer.setPivotX(mDirectionLTR ? 0 : mActionsContainer.getWidth());
+ mActionsContainerBackground.setPivotX(
+ mDirectionLTR ? 0 : mActionsContainerBackground.getWidth());
+ });
+ return animator;
+ }
+
+ void setChipIntents(ScreenshotController.SavedImageData imageData,
+ Consumer<ScreenshotEvent> onElementTapped) {
+ mShareChip.setPendingIntent(imageData.shareAction.actionIntent,
+ () -> onElementTapped.accept(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED));
+ mEditChip.setPendingIntent(imageData.editAction.actionIntent,
+ () -> onElementTapped.accept(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED));
+ mScreenshotPreview.setOnClickListener(v -> {
+ try {
+ imageData.editAction.actionIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Intent cancelled", e);
+ }
+ onElementTapped.accept(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
+ });
+
+ if (mPendingInteraction != null) {
+ switch (mPendingInteraction) {
+ case PREVIEW:
+ mScreenshotPreview.callOnClick();
+ break;
+ case SHARE:
+ mShareChip.callOnClick();
+ break;
+ case EDIT:
+ mEditChip.callOnClick();
+ break;
+ }
+ } else {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+
+ 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,
+ () -> onElementTapped.accept(
+ ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED));
+ mActionsView.addView(actionChip);
+ mSmartChips.add(actionChip);
+ }
+ }
+ }
+
+
+ AnimatorSet createScreenshotDismissAnimation() {
+ ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
+ alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
+ alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
+ alphaAnim.addUpdateListener(animation -> {
+ setAlpha(1 - animation.getAnimatedFraction());
+ });
+
+ ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
+ yAnim.setInterpolator(mAccelerateInterpolator);
+ yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
+ float screenshotStartY = mScreenshotPreview.getTranslationY();
+ float dismissStartY = mDismissButton.getTranslationY();
+ yAnim.addUpdateListener(animation -> {
+ float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
+ mScreenshotPreview.setTranslationY(screenshotStartY + yDelta);
+ mDismissButton.setTranslationY(dismissStartY + yDelta);
+ mActionsContainer.setTranslationY(yDelta);
+ mActionsContainerBackground.setTranslationY(yDelta);
+ });
+
+ AnimatorSet animSet = new AnimatorSet();
+ animSet.play(yAnim).with(alphaAnim);
+
+ return animSet;
+ }
+
+ void reset() {
+ // Clear any references to the bitmap
+ mScreenshotPreview.setImageDrawable(null);
+ mActionsContainerBackground.setVisibility(View.GONE);
+ mActionsContainer.setVisibility(View.GONE);
+ mBackgroundProtection.setAlpha(0f);
+ mDismissButton.setVisibility(View.GONE);
+ mScreenshotPreview.setVisibility(View.GONE);
+ mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null);
+ mScreenshotPreview.setContentDescription(
+ mContext.getResources().getString(R.string.screenshot_preview_description));
+ mScreenshotPreview.setOnClickListener(null);
+ mShareChip.setOnClickListener(null);
+ mEditChip.setOnClickListener(null);
+ mShareChip.setIsPending(false);
+ mEditChip.setIsPending(false);
+ mPendingInteraction = null;
+ for (ScreenshotActionChip chip : mSmartChips) {
+ mActionsView.removeView(chip);
+ }
+ mSmartChips.clear();
+ setAlpha(1);
+ mDismissButton.setTranslationY(0);
+ mActionsContainer.setTranslationY(0);
+ mActionsContainerBackground.setTranslationY(0);
+ mScreenshotPreview.setTranslationY(0);
+ mScreenshotSelectorView.stop();
+ }
+
+ /**
+ * Create a drawable using the size of the bitmap and insets as the fractional inset parameters.
+ */
+ private static Drawable createScreenDrawable(Resources res, Bitmap bitmap, Insets insets) {
+ int insettedWidth = bitmap.getWidth() - insets.left - insets.right;
+ int insettedHeight = bitmap.getHeight() - insets.top - insets.bottom;
+
+ BitmapDrawable bitmapDrawable = new BitmapDrawable(res, bitmap);
+ if (insettedHeight == 0 || insettedWidth == 0 || bitmap.getWidth() == 0
+ || bitmap.getHeight() == 0) {
+ Log.e(TAG, String.format(
+ "Can't create insetted drawable, using 0 insets "
+ + "bitmap and insets create degenerate region: %dx%d %s",
+ bitmap.getWidth(), bitmap.getHeight(), insets));
+ return bitmapDrawable;
+ }
+
+ InsetDrawable insetDrawable = new InsetDrawable(bitmapDrawable,
+ -1f * insets.left / insettedWidth,
+ -1f * insets.top / insettedHeight,
+ -1f * insets.right / insettedWidth,
+ -1f * insets.bottom / insettedHeight);
+
+ if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
+ // Are any of the insets negative, meaning the bitmap is smaller than the bounds so need
+ // to fill in the background of the drawable.
+ return new LayerDrawable(new Drawable[]{
+ new ColorDrawable(Color.BLACK), insetDrawable});
+ } else {
+ return insetDrawable;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
new file mode 100644
index 000000000000..ea835fa94fe8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureClient.java
@@ -0,0 +1,346 @@
+/*
+ * 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 java.util.Objects.requireNonNull;
+
+import android.annotation.UiContext;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IScrollCaptureCallbacks;
+import android.view.IScrollCaptureConnection;
+import android.view.IWindowManager;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.view.ScrollCaptureViewSupport;
+
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * High level interface to scroll capture API.
+ */
+public class ScrollCaptureClient {
+
+ @VisibleForTesting
+ static final int MATCH_ANY_TASK = ActivityTaskManager.INVALID_TASK_ID;
+
+ private static final String TAG = "ScrollCaptureClient";
+
+ /** Whether to log method names and arguments for most calls */
+ private static final boolean DEBUG_TRACE = false;
+
+ /**
+ * A connection to a remote window. Starts a capture session.
+ */
+ public interface Connection {
+ /**
+ * Session start should be deferred until UI is active because of resource allocation and
+ * potential visible side effects in the target window.
+ *
+ * @param maxBuffers the maximum number of buffers (tiles) that may be in use at one
+ * time, tiles are not cached anywhere so set this to a large enough
+ * number to retain offscreen content until it is no longer needed
+ * @param sessionConsumer listener to receive the session once active
+ */
+ void start(int maxBuffers, Consumer<Session> sessionConsumer);
+
+ /**
+ * Close the connection.
+ */
+ void close();
+ }
+
+ static class CaptureResult {
+ public final Image image;
+ /**
+ * The area requested, in content rect space, relative to scroll-bounds.
+ */
+ public final Rect requested;
+ /**
+ * The actual area captured, in content rect space, relative to scroll-bounds. This may be
+ * cropped or empty depending on available content.
+ */
+ public final Rect captured;
+
+ // Error?
+
+ private CaptureResult(Image image, Rect request, Rect captured) {
+ this.image = image;
+ this.requested = request;
+ this.captured = captured;
+ }
+ }
+
+ /**
+ * Represents the connection to a target window and provides a mechanism for requesting tiles.
+ */
+ interface Session {
+ /**
+ * Request the given horizontal strip. Values are y-coordinates in captured space, relative
+ * to start position.
+ *
+ * @param contentRect the area to capture, in content rect space, relative to scroll-bounds
+ * @param consumer listener to be informed of the result
+ */
+ void requestTile(Rect contentRect, Consumer<CaptureResult> consumer);
+
+ /**
+ * End the capture session, return the target app to original state. The returned
+ * stage must be waited for to complete to allow the target app a chance to restore to
+ * original state before becoming visible.
+ *
+ * @return a stage presenting the session shutdown
+ */
+ void end(Runnable listener);
+
+ int getMaxTileHeight();
+
+ int getMaxTileWidth();
+ }
+
+ private final IWindowManager mWindowManagerService;
+ private IBinder mHostWindowToken;
+
+ @Inject
+ public ScrollCaptureClient(@UiContext Context context, IWindowManager windowManagerService) {
+ requireNonNull(context.getDisplay(), "context must be associated with a Display!");
+ mWindowManagerService = windowManagerService;
+ }
+
+ public void setHostWindowToken(IBinder token) {
+ mHostWindowToken = token;
+ }
+
+ /**
+ * Check for scroll capture support.
+ *
+ * @param displayId id for the display containing the target window
+ * @param consumer receives a connection when available
+ */
+ public void request(int displayId, Consumer<Connection> consumer) {
+ request(displayId, MATCH_ANY_TASK, consumer);
+ }
+
+ /**
+ * Check for scroll capture support.
+ *
+ * @param displayId id for the display containing the target window
+ * @param taskId id for the task containing the target window or {@link #MATCH_ANY_TASK}.
+ * @param consumer receives a connection when available
+ */
+ public void request(int displayId, int taskId, Consumer<Connection> consumer) {
+ try {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "requestScrollCapture(displayId=" + displayId + ", " + mHostWindowToken
+ + ", taskId=" + taskId + ", consumer=" + consumer + ")");
+ }
+ mWindowManagerService.requestScrollCapture(displayId, mHostWindowToken, taskId,
+ new ControllerCallbacks(consumer));
+ } catch (RemoteException e) {
+ Log.e(TAG, "Ignored remote exception", e);
+ }
+ }
+
+ private static class ControllerCallbacks extends IScrollCaptureCallbacks.Stub implements
+ Connection, Session, IBinder.DeathRecipient {
+
+ private IScrollCaptureConnection mConnection;
+ private Consumer<Connection> mConnectionConsumer;
+ private Consumer<Session> mSessionConsumer;
+ private Consumer<CaptureResult> mResultConsumer;
+ private Runnable mShutdownListener;
+
+ private ImageReader mReader;
+ private Rect mScrollBounds;
+ private Rect mRequestRect;
+ private boolean mStarted;
+
+ private ControllerCallbacks(Consumer<Connection> connectionConsumer) {
+ mConnectionConsumer = connectionConsumer;
+ }
+
+ // IScrollCaptureCallbacks
+
+ @Override
+ public void onConnected(IScrollCaptureConnection connection, Rect scrollBounds,
+ Point positionInWindow) throws RemoteException {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "onConnected(connection=" + connection + ", scrollBounds=" + scrollBounds
+ + ", positionInWindow=" + positionInWindow + ")");
+ }
+ mConnection = connection;
+ mConnection.asBinder().linkToDeath(this, 0);
+ mScrollBounds = scrollBounds;
+ mConnectionConsumer.accept(this);
+ mConnectionConsumer = null;
+ }
+
+ @Override
+ public void onUnavailable() throws RemoteException {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "onUnavailable");
+ }
+ // The targeted app does not support scroll capture
+ // or the window could not be found... etc etc.
+ }
+
+ @Override
+ public void onCaptureStarted() {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "onCaptureStarted()");
+ }
+ mSessionConsumer.accept(this);
+ mSessionConsumer = null;
+ }
+
+ @Override
+ public void onCaptureBufferSent(long frameNumber, Rect contentArea) {
+ Image image = null;
+ if (frameNumber != ScrollCaptureViewSupport.NO_FRAME_PRODUCED) {
+ image = mReader.acquireNextImage();
+ }
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "onCaptureBufferSent(frameNumber=" + frameNumber
+ + ", contentArea=" + contentArea + ") image=" + image);
+ }
+ // Save and clear first, since the consumer will likely request the next
+ // tile, otherwise the new consumer will be wiped out.
+ Consumer<CaptureResult> consumer = mResultConsumer;
+ mResultConsumer = null;
+ consumer.accept(new CaptureResult(image, mRequestRect, contentArea));
+ }
+
+ @Override
+ public void onConnectionClosed() {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "onConnectionClosed()");
+ }
+ disconnect();
+ if (mShutdownListener != null) {
+ mShutdownListener.run();
+ mShutdownListener = null;
+ }
+ }
+
+ // Misc
+
+ private void disconnect() {
+ if (mConnection != null) {
+ mConnection.asBinder().unlinkToDeath(this, 0);
+ }
+ mConnection = null;
+ }
+
+ // ScrollCaptureController.Connection
+
+ // -> Error handling: BiConsumer<Session, Throwable> ?
+ @Override
+ public void start(int maxBufferCount, Consumer<Session> sessionConsumer) {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "start(maxBufferCount=" + maxBufferCount
+ + ", sessionConsumer=" + sessionConsumer + ")");
+ }
+ mReader = ImageReader.newInstance(mScrollBounds.width(), mScrollBounds.height(),
+ PixelFormat.RGBA_8888, maxBufferCount, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
+ mSessionConsumer = sessionConsumer;
+ try {
+ mConnection.startCapture(mReader.getSurface());
+ mStarted = true;
+ } catch (RemoteException e) {
+ Log.w(TAG, "should not be happening :-(");
+ // ?
+ //mSessionListener.onError(e);
+ //mSessionListener = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ end(null);
+ }
+
+ // ScrollCaptureController.Session
+
+ @Override
+ public void end(Runnable listener) {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "end(listener=" + listener + ")");
+ }
+ if (mStarted) {
+ mShutdownListener = listener;
+ try {
+ // listener called from onConnectionClosed callback
+ mConnection.endCapture();
+ } catch (RemoteException e) {
+ Log.d(TAG, "Ignored exception from endCapture()", e);
+ disconnect();
+ listener.run();
+ }
+ } else {
+ disconnect();
+ listener.run();
+ }
+ }
+
+ @Override
+ public int getMaxTileHeight() {
+ return mScrollBounds.height();
+ }
+
+ @Override
+ public int getMaxTileWidth() {
+ return mScrollBounds.width();
+ }
+
+ @Override
+ public void requestTile(Rect contentRect, Consumer<CaptureResult> consumer) {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "requestTile(contentRect=" + contentRect + "consumer=" + consumer + ")");
+ }
+ mRequestRect = new Rect(contentRect);
+ mResultConsumer = consumer;
+ try {
+ mConnection.requestImage(mRequestRect);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Caught remote exception from requestImage", e);
+ // ?
+ }
+ }
+
+ /**
+ * The process hosting the window went away abruptly!
+ */
+ @Override
+ public void binderDied() {
+ if (DEBUG_TRACE) {
+ Log.d(TAG, "binderDied()");
+ }
+ disconnect();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
index 5ced40cb1b3b..800d67969f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -16,46 +16,231 @@
package com.android.systemui.screenshot;
-import android.os.IBinder;
-import android.view.IWindowManager;
+import static android.graphics.ColorSpace.Named.SRGB;
-import javax.inject.Inject;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.ColorSpace;
+import android.graphics.Picture;
+import android.graphics.Rect;
+import android.media.ExifInterface;
+import android.media.Image;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
+import android.provider.MediaStore;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.systemui.screenshot.ScrollCaptureClient.Connection;
+import com.android.systemui.screenshot.ScrollCaptureClient.Session;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.sql.Date;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.function.Consumer;
/**
- * Stub
+ * Interaction controller between the UI and ScrollCaptureClient.
*/
public class ScrollCaptureController {
+ private static final String TAG = "ScrollCaptureController";
- public static final int STATUS_A = 0;
- public static final int STATUS_B = 1;
+ public static final int MAX_PAGES = 5;
+ public static final int MAX_HEIGHT = 12000;
- private final IWindowManager mWindowManagerService;
- private StatusListener mListener;
+ private final Connection mConnection;
+ private final Context mContext;
+ private Picture mPicture;
+
+ public ScrollCaptureController(Context context, Connection connection) {
+ mContext = context;
+ mConnection = connection;
+ }
/**
+ * Run scroll capture!
*
- * @param windowManagerService
+ * @param after action to take after the flow is complete
*/
- @Inject
- public ScrollCaptureController(IWindowManager windowManagerService) {
- mWindowManagerService = windowManagerService;
+ public void run(final Runnable after) {
+ mConnection.start(MAX_PAGES, (session) -> startCapture(session, after));
}
- interface StatusListener {
- void onScrollCaptureStatus(boolean available);
- }
+ private void startCapture(Session session, final Runnable after) {
+ Rect requestRect = new Rect(0, 0,
+ session.getMaxTileWidth(), session.getMaxTileHeight());
+ Consumer<ScrollCaptureClient.CaptureResult> consumer =
+ new Consumer<ScrollCaptureClient.CaptureResult>() {
+
+ int mFrameCount = 0;
+
+ @Override
+ public void accept(ScrollCaptureClient.CaptureResult result) {
+ mFrameCount++;
+ boolean emptyFrame = result.captured.height() == 0;
+ if (!emptyFrame) {
+ mPicture = stackBelow(mPicture, result.image, result.captured.width(),
+ result.captured.height());
+ }
+ if (emptyFrame || mFrameCount > MAX_PAGES
+ || requestRect.bottom > MAX_HEIGHT) {
+ Uri uri = null;
+ if (mPicture != null) {
+ // This is probably on a binder thread right now ¯\_(ツ)_/¯
+ uri = writeImage(Bitmap.createBitmap(mPicture));
+ // Release those buffers!
+ mPicture.close();
+ }
+ if (uri != null) {
+ launchViewer(uri);
+ } else {
+ Toast.makeText(mContext, "Failed to create tall screenshot",
+ Toast.LENGTH_SHORT).show();
+ }
+ session.end(after); // end session, close connection, after.run()
+ return;
+ }
+ requestRect.offset(0, session.getMaxTileHeight());
+ session.requestTile(requestRect, /* consumer */ this);
+ }
+ };
+
+ // fire it up!
+ session.requestTile(requestRect, consumer);
+ };
+
/**
+ * Combine the top {@link Picture} with an {@link Image} by appending the image directly
+ * below, creating a result that is the combined height of both.
+ * <p>
+ * Note: no pixel data is transferred here, only a record of drawing commands. Backing
+ * hardware buffers must not be modified/recycled until the picture is
+ * {@link Picture#close closed}.
+ *
+ * @param top the existing picture
+ * @param below the image to append below
+ * @param cropWidth the width of the pixel data to use from the image
+ * @param cropHeight the height of the pixel data to use from the image
*
- * @param window
- * @param listener
+ * @return a new Picture which draws the previous picture with the image below it
*/
- public void getStatus(IBinder window, StatusListener listener) {
- mListener = listener;
-// try {
-// mWindowManagerService.requestScrollCapture(window, new ClientCallbacks());
-// } catch (RemoteException e) {
-// }
+ private static Picture stackBelow(Picture top, Image below, int cropWidth, int cropHeight) {
+ int width = cropWidth;
+ int height = cropHeight;
+ if (top != null) {
+ height += top.getHeight();
+ width = Math.max(width, top.getWidth());
+ }
+ Picture combined = new Picture();
+ Canvas canvas = combined.beginRecording(width, height);
+ int y = 0;
+ if (top != null) {
+ canvas.drawPicture(top, new Rect(0, 0, top.getWidth(), top.getHeight()));
+ y += top.getHeight();
+ }
+ canvas.drawBitmap(Bitmap.wrapHardwareBuffer(
+ below.getHardwareBuffer(), ColorSpace.get(SRGB)), 0, y, null);
+ combined.endRecording();
+ return combined;
}
+ Uri writeImage(Bitmap image) {
+ ContentResolver resolver = mContext.getContentResolver();
+ long mImageTime = System.currentTimeMillis();
+ String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
+ String mImageFileName = String.format("tall_Screenshot_%s.png", imageDate);
+ String mScreenshotId = String.format("Screenshot_%s", UUID.randomUUID());
+ try {
+ // Save the screenshot to the MediaStore
+ final ContentValues values = new ContentValues();
+ values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
+ + File.separator + Environment.DIRECTORY_SCREENSHOTS);
+ values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName);
+ values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
+ values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000);
+ values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000);
+ values.put(
+ MediaStore.MediaColumns.DATE_EXPIRES,
+ (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
+ values.put(MediaStore.MediaColumns.IS_PENDING, 1);
+
+ final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ values);
+ try {
+ try (OutputStream out = resolver.openOutputStream(uri)) {
+ if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
+ throw new IOException("Failed to compress");
+ }
+ }
+
+ // Next, write metadata to help index the screenshot
+ try (ParcelFileDescriptor pfd = resolver.openFile(uri, "rw", null)) {
+ final ExifInterface exif = new ExifInterface(pfd.getFileDescriptor());
+
+ exif.setAttribute(ExifInterface.TAG_SOFTWARE,
+ "Android " + Build.DISPLAY);
+
+ exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH,
+ Integer.toString(image.getWidth()));
+ exif.setAttribute(ExifInterface.TAG_IMAGE_LENGTH,
+ Integer.toString(image.getHeight()));
+
+ final ZonedDateTime time = ZonedDateTime.ofInstant(
+ Instant.ofEpochMilli(mImageTime), ZoneId.systemDefault());
+ exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL,
+ DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss").format(time));
+ exif.setAttribute(ExifInterface.TAG_SUBSEC_TIME_ORIGINAL,
+ DateTimeFormatter.ofPattern("SSS").format(time));
+
+ if (Objects.equals(time.getOffset(), ZoneOffset.UTC)) {
+ exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL, "+00:00");
+ } else {
+ exif.setAttribute(ExifInterface.TAG_OFFSET_TIME_ORIGINAL,
+ DateTimeFormatter.ofPattern("XXX").format(time));
+ }
+ exif.saveAttributes();
+ }
+
+ // Everything went well above, publish it!
+ values.clear();
+ values.put(MediaStore.MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
+ resolver.update(uri, values, null, null);
+ return uri;
+ } catch (Exception e) {
+ resolver.delete(uri, null);
+ throw e;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "unable to save screenshot", e);
+ }
+ return null;
+ }
+
+ void launchViewer(Uri uri) {
+ Intent editIntent = new Intent(Intent.ACTION_VIEW);
+ editIntent.setType("image/png");
+ editIntent.setData(uri);
+ editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivityAsUser(editIntent, UserHandle.CURRENT);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 217235b16ecf..f32529fdaf04 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -16,9 +16,9 @@
package com.android.systemui.screenshot;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_INTENT;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ACTION_TYPE;
-import static com.android.systemui.screenshot.GlobalScreenshot.EXTRA_ID;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
import android.app.ActivityOptions;
import android.app.PendingIntent;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index a043f0f1e50c..4e2283396e25 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -52,7 +52,7 @@ import javax.inject.Inject;
public class TakeScreenshotService extends Service {
private static final String TAG = "TakeScreenshotService";
- private final GlobalScreenshot mScreenshot;
+ private final ScreenshotController mScreenshot;
private final UserManager mUserManager;
private final UiEventLogger mUiEventLogger;
@@ -61,7 +61,7 @@ public class TakeScreenshotService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction()) && mScreenshot != null) {
- mScreenshot.dismissScreenshot("close system dialogs", false);
+ mScreenshot.dismissScreenshot(false);
}
}
};
@@ -125,7 +125,7 @@ public class TakeScreenshotService extends Service {
};
@Inject
- public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager,
+ public TakeScreenshotService(ScreenshotController globalScreenshot, UserManager userManager,
UiEventLogger uiEventLogger) {
mScreenshot = globalScreenshot;
mUserManager = userManager;
@@ -144,7 +144,9 @@ public class TakeScreenshotService extends Service {
@Override
public boolean onUnbind(Intent intent) {
- if (mScreenshot != null) mScreenshot.stopScreenshot();
+ if (mScreenshot != null && !mScreenshot.isDismissing()) {
+ mScreenshot.dismissScreenshot(true);
+ }
unregisterReceiver(mBroadcastReceiver);
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 1bea72aea2ba..72034f84fd30 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -50,6 +50,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import java.util.ArrayList;
+import javax.inject.Inject;
+
public class BrightnessController implements ToggleSlider.Listener {
private static final String TAG = "StatusBar.BrightnessController";
private static final int SLIDER_ANIMATION_DURATION = 3000;
@@ -475,4 +477,20 @@ public class BrightnessController implements ToggleSlider.Listener {
mSliderAnimator.start();
}
+ /** Factory for creating a {@link BrightnessController}. */
+ public static class Factory {
+ private final Context mContext;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+
+ @Inject
+ public Factory(Context context, BroadcastDispatcher broadcastDispatcher) {
+ mContext = context;
+ mBroadcastDispatcher = broadcastDispatcher;
+ }
+
+ /** Create a {@link BrightnessController} */
+ public BrightnessController create(ToggleSlider toggleSlider) {
+ return new BrightnessController(mContext, toggleSlider, mBroadcastDispatcher);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index dcee9fa9e648..5b3763e66307 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -284,7 +284,7 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
default void showAuthenticationDialog(PromptInfo promptInfo,
IBiometricSysuiReceiver receiver,
- @BiometricAuthenticator.Modality int biometricModality,
+ int[] sensorIds, boolean credentialAllowed,
boolean requireConfirmation, int userId, String opPackageName,
long operationId) { }
default void onBiometricAuthenticated() { }
@@ -829,17 +829,18 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
@Override
public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
- @BiometricAuthenticator.Modality int biometricModality, boolean requireConfirmation,
+ int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
int userId, String opPackageName, long operationId) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = promptInfo;
args.arg2 = receiver;
- args.argi1 = biometricModality;
- args.arg3 = requireConfirmation;
- args.argi2 = userId;
- args.arg4 = opPackageName;
- args.arg5 = operationId;
+ args.arg3 = sensorIds; //
+ args.arg4 = credentialAllowed; //
+ args.arg5 = requireConfirmation;
+ args.argi1 = userId;
+ args.arg6 = opPackageName;
+ args.arg7 = operationId;
mHandler.obtainMessage(MSG_BIOMETRIC_SHOW, args)
.sendToTarget();
}
@@ -1264,11 +1265,12 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController<
mCallbacks.get(i).showAuthenticationDialog(
(PromptInfo) someArgs.arg1,
(IBiometricSysuiReceiver) someArgs.arg2,
- someArgs.argi1 /* biometricModality */,
- (boolean) someArgs.arg3 /* requireConfirmation */,
- someArgs.argi2 /* userId */,
- (String) someArgs.arg4 /* opPackageName */,
- (long) someArgs.arg5 /* operationId */);
+ (int[]) someArgs.arg3 /* sensorIds */,
+ (boolean) someArgs.arg4 /* credentialAllowed */,
+ (boolean) someArgs.arg5 /* requireConfirmation */,
+ someArgs.argi1 /* userId */,
+ (String) someArgs.arg6 /* opPackageName */,
+ (long) someArgs.arg7 /* operationId */);
}
someArgs.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index a59ff38896dc..a252a7a12274 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -121,6 +121,7 @@ public class KeyguardIndicationController implements StateListener,
private int mChargingSpeed;
private int mChargingWattage;
private int mBatteryLevel;
+ private boolean mBatteryPresent = true;
private long mChargingTimeRemaining;
private float mDisclosureMaxAlpha;
private String mMessageToShowOnScreenOn;
@@ -389,86 +390,103 @@ public class KeyguardIndicationController implements StateListener,
mWakeLock.setAcquired(false);
}
- if (mVisible) {
- // Walk down a precedence-ordered list of what indication
- // should be shown based on user or device state
- if (mDozing) {
- // When dozing we ignore any text color and use white instead, because
- // colors can be hard to read in low brightness.
- mTextView.setTextColor(Color.WHITE);
- if (!TextUtils.isEmpty(mTransientIndication)) {
- mTextView.switchIndication(mTransientIndication);
- } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
- mTextView.switchIndication(mAlignmentIndication);
- mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
- } else if (mPowerPluggedIn) {
- String indication = computePowerIndication();
- if (animate) {
- animateText(mTextView, indication);
- } else {
- mTextView.switchIndication(indication);
- }
- } else {
- String percentage = NumberFormat.getPercentInstance()
- .format(mBatteryLevel / 100f);
- mTextView.switchIndication(percentage);
- }
- return;
- }
+ if (!mVisible) {
+ return;
+ }
- int userId = KeyguardUpdateMonitor.getCurrentUser();
- String trustGrantedIndication = getTrustGrantedIndication();
- String trustManagedIndication = getTrustManagedIndication();
+ // A few places might need to hide the indication, so always start by making it visible
+ mIndicationArea.setVisibility(View.VISIBLE);
- String powerIndication = null;
- if (mPowerPluggedIn) {
- powerIndication = computePowerIndication();
- }
-
- boolean isError = false;
- if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
- mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
- } else if (!TextUtils.isEmpty(mTransientIndication)) {
- if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
- String indication = mContext.getResources().getString(
- R.string.keyguard_indication_trust_unlocked_plugged_in,
- mTransientIndication, powerIndication);
- mTextView.switchIndication(indication);
- } else {
- mTextView.switchIndication(mTransientIndication);
- }
- isError = mTransientTextIsError;
- } else if (!TextUtils.isEmpty(trustGrantedIndication)
- && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
- if (powerIndication != null) {
- String indication = mContext.getResources().getString(
- R.string.keyguard_indication_trust_unlocked_plugged_in,
- trustGrantedIndication, powerIndication);
- mTextView.switchIndication(indication);
- } else {
- mTextView.switchIndication(trustGrantedIndication);
- }
+ // Walk down a precedence-ordered list of what indication
+ // should be shown based on user or device state
+ if (mDozing) {
+ // When dozing we ignore any text color and use white instead, because
+ // colors can be hard to read in low brightness.
+ mTextView.setTextColor(Color.WHITE);
+ if (!TextUtils.isEmpty(mTransientIndication)) {
+ mTextView.switchIndication(mTransientIndication);
+ } else if (!mBatteryPresent) {
+ // If there is no battery detected, hide the indication and bail
+ mIndicationArea.setVisibility(View.GONE);
} else if (!TextUtils.isEmpty(mAlignmentIndication)) {
mTextView.switchIndication(mAlignmentIndication);
- isError = true;
+ mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
} else if (mPowerPluggedIn) {
- if (DEBUG_CHARGING_SPEED) {
- powerIndication += ", " + (mChargingWattage / 1000) + " mW";
- }
+ String indication = computePowerIndication();
if (animate) {
- animateText(mTextView, powerIndication);
+ animateText(mTextView, indication);
} else {
- mTextView.switchIndication(powerIndication);
+ mTextView.switchIndication(indication);
}
- } else if (!TextUtils.isEmpty(trustManagedIndication)
- && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
- && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
- mTextView.switchIndication(trustManagedIndication);
} else {
- mTextView.switchIndication(mRestingIndication);
+ String percentage = NumberFormat.getPercentInstance()
+ .format(mBatteryLevel / 100f);
+ mTextView.switchIndication(percentage);
}
- mTextView.setTextColor(isError ? Utils.getColorError(mContext)
- : mInitialTextColorState);
+ return;
+ }
+
+ int userId = KeyguardUpdateMonitor.getCurrentUser();
+ String trustGrantedIndication = getTrustGrantedIndication();
+ String trustManagedIndication = getTrustManagedIndication();
+
+ String powerIndication = null;
+ if (mPowerPluggedIn) {
+ powerIndication = computePowerIndication();
+ }
+
+ // Some cases here might need to hide the indication (if the battery is not present)
+ boolean hideIndication = false;
+ boolean isError = false;
+ if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
+ mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
+ } else if (!TextUtils.isEmpty(mTransientIndication)) {
+ if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
+ String indication = mContext.getResources().getString(
+ R.string.keyguard_indication_trust_unlocked_plugged_in,
+ mTransientIndication, powerIndication);
+ mTextView.switchIndication(indication);
+ hideIndication = !mBatteryPresent;
+ } else {
+ mTextView.switchIndication(mTransientIndication);
+ }
+ isError = mTransientTextIsError;
+ } else if (!TextUtils.isEmpty(trustGrantedIndication)
+ && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
+ if (powerIndication != null) {
+ String indication = mContext.getResources().getString(
+ R.string.keyguard_indication_trust_unlocked_plugged_in,
+ trustGrantedIndication, powerIndication);
+ mTextView.switchIndication(indication);
+ hideIndication = !mBatteryPresent;
+ } else {
+ mTextView.switchIndication(trustGrantedIndication);
+ }
+ } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
+ mTextView.switchIndication(mAlignmentIndication);
+ isError = true;
+ hideIndication = !mBatteryPresent;
+ } else if (mPowerPluggedIn) {
+ if (DEBUG_CHARGING_SPEED) {
+ powerIndication += ", " + (mChargingWattage / 1000) + " mW";
+ }
+ if (animate) {
+ animateText(mTextView, powerIndication);
+ } else {
+ mTextView.switchIndication(powerIndication);
+ }
+ hideIndication = !mBatteryPresent;
+ } else if (!TextUtils.isEmpty(trustManagedIndication)
+ && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
+ && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
+ mTextView.switchIndication(trustManagedIndication);
+ } else {
+ mTextView.switchIndication(mRestingIndication);
+ }
+ mTextView.setTextColor(isError ? Utils.getColorError(mContext)
+ : mInitialTextColorState);
+ if (hideIndication) {
+ mIndicationArea.setVisibility(View.GONE);
}
}
@@ -647,6 +665,7 @@ public class KeyguardIndicationController implements StateListener,
pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
pw.println(" mDozing: " + mDozing);
pw.println(" mBatteryLevel: " + mBatteryLevel);
+ pw.println(" mBatteryPresent: " + mBatteryPresent);
pw.println(" mTextView.getText(): " + (mTextView == null ? null : mTextView.getText()));
pw.println(" computePowerIndication(): " + computePowerIndication());
}
@@ -685,6 +704,7 @@ public class KeyguardIndicationController implements StateListener,
mChargingWattage = status.maxChargingWattage;
mChargingSpeed = status.getChargingSpeed(mContext);
mBatteryLevel = status.level;
+ mBatteryPresent = status.present;
try {
mChargingTimeRemaining = mPowerPluggedIn
? mBatteryInfo.computeChargeTimeRemaining() : -1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
deleted file mode 100644
index 1b1a51b8a57b..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaTransferManager.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar;
-
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.RippleDrawable;
-import android.service.notification.StatusBarNotification;
-import android.util.FeatureFlagUtils;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-import com.android.settingslib.media.InfoMediaManager;
-import com.android.settingslib.media.LocalMediaManager;
-import com.android.settingslib.media.MediaDevice;
-import com.android.settingslib.widget.AdaptiveIcon;
-import com.android.systemui.Dependency;
-import com.android.systemui.media.dialog.MediaOutputDialogFactory;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Class for handling MediaTransfer state over a set of notifications.
- */
-public class MediaTransferManager {
- private final Context mContext;
- private final MediaOutputDialogFactory mMediaOutputDialogFactory;
- private MediaDevice mDevice;
- private List<View> mViews = new ArrayList<>();
- private LocalMediaManager mLocalMediaManager;
-
- private static final String TAG = "MediaTransferManager";
-
- private final View.OnClickListener mOnClickHandler = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- if (handleMediaTransfer(view)) {
- return;
- }
- }
-
- private boolean handleMediaTransfer(View view) {
- if (view.findViewById(com.android.internal.R.id.media_seamless) == null) {
- return false;
- }
-
- ViewParent parent = view.getParent();
- StatusBarNotification statusBarNotification =
- getRowForParent(parent).getEntry().getSbn();
- mMediaOutputDialogFactory.create(statusBarNotification.getPackageName(), true);
- return true;
- }
- };
-
- private final LocalMediaManager.DeviceCallback mMediaDeviceCallback =
- new LocalMediaManager.DeviceCallback() {
- @Override
- public void onDeviceListUpdate(List<MediaDevice> devices) {
- MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
- // Check because this can be called several times while changing devices
- if (mDevice == null || !mDevice.equals(currentDevice)) {
- mDevice = currentDevice;
- updateAllChips();
- }
- }
-
- @Override
- public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
- if (mDevice == null || !mDevice.equals(device)) {
- mDevice = device;
- updateAllChips();
- }
- }
- };
-
- public MediaTransferManager(Context context) {
- mContext = context;
- mMediaOutputDialogFactory = Dependency.get(MediaOutputDialogFactory.class);
- LocalBluetoothManager lbm = Dependency.get(LocalBluetoothManager.class);
- InfoMediaManager imm = new InfoMediaManager(mContext, null, null, lbm);
- mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, null);
- }
-
- /**
- * Mark a view as removed. If no views remain the media device listener will be unregistered.
- * @param root
- */
- public void setRemoved(View root) {
- if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)
- || mLocalMediaManager == null || root == null) {
- return;
- }
- View view = root.findViewById(com.android.internal.R.id.media_seamless);
- if (mViews.remove(view)) {
- if (mViews.size() == 0) {
- mLocalMediaManager.unregisterCallback(mMediaDeviceCallback);
- }
- } else {
- Log.e(TAG, "Tried to remove unknown view " + view);
- }
- }
-
- private ExpandableNotificationRow getRowForParent(ViewParent parent) {
- while (parent != null) {
- if (parent instanceof ExpandableNotificationRow) {
- return ((ExpandableNotificationRow) parent);
- }
- parent = parent.getParent();
- }
- return null;
- }
-
- /**
- * apply the action button for MediaTransfer
- *
- * @param root The parent container of the view.
- * @param entry The entry of MediaTransfer action button.
- */
- public void applyMediaTransferView(ViewGroup root, NotificationEntry entry) {
- if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)
- || mLocalMediaManager == null || root == null) {
- return;
- }
-
- View view = root.findViewById(com.android.internal.R.id.media_seamless);
- if (view == null) {
- return;
- }
-
- view.setVisibility(View.VISIBLE);
- view.setOnClickListener(mOnClickHandler);
- if (!mViews.contains(view)) {
- mViews.add(view);
- if (mViews.size() == 1) {
- mLocalMediaManager.registerCallback(mMediaDeviceCallback);
- }
- }
-
- // Initial update
- mLocalMediaManager.startScan();
- mDevice = mLocalMediaManager.getCurrentConnectedDevice();
- updateChip(view);
- }
-
- private void updateAllChips() {
- for (View view : mViews) {
- updateChip(view);
- }
- }
-
- private void updateChip(View view) {
- ExpandableNotificationRow enr = getRowForParent(view.getParent());
- int fgColor = enr.getNotificationHeader().getOriginalIconColor();
- ColorStateList fgTintList = ColorStateList.valueOf(fgColor);
- int bgColor = enr.getCurrentBackgroundTint();
-
- // Update outline color
- LinearLayout viewLayout = (LinearLayout) view;
- RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
- GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
- rect.setStroke(2, fgColor);
- rect.setColor(bgColor);
-
- ImageView iconView = view.findViewById(com.android.internal.R.id.media_seamless_image);
- TextView deviceName = view.findViewById(com.android.internal.R.id.media_seamless_text);
- deviceName.setTextColor(fgTintList);
-
- if (mDevice != null) {
- Drawable icon = mDevice.getIcon();
- iconView.setVisibility(View.VISIBLE);
- iconView.setImageTintList(fgTintList);
-
- if (icon instanceof AdaptiveIcon) {
- AdaptiveIcon aIcon = (AdaptiveIcon) icon;
- aIcon.setBackgroundColor(bgColor);
- iconView.setImageDrawable(aIcon);
- } else {
- iconView.setImageDrawable(icon);
- }
- deviceName.setText(mDevice.getName());
- } else {
- // Reset to default
- iconView.setVisibility(View.GONE);
- deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index 670a65f55844..25c8e7feb9b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -17,18 +17,16 @@
package com.android.systemui.statusbar;
import android.app.Notification;
-import android.content.res.Configuration;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
-import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
-import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.ConversationLayout;
+import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentView;
@@ -67,32 +65,14 @@ public class NotificationHeaderUtil {
private final static ResultApplicator mGreyApplicator = new ResultApplicator() {
@Override
public void apply(View parent, View view, boolean apply, boolean reset) {
- NotificationHeaderView header = (NotificationHeaderView) view;
- ImageView icon = (ImageView) view.findViewById(
- com.android.internal.R.id.icon);
- ImageView expand = (ImageView) view.findViewById(
- com.android.internal.R.id.expand_button);
- applyToChild(icon, apply, header.getOriginalIconColor());
- applyToChild(expand, apply, header.getOriginalNotificationColor());
- }
-
- private void applyToChild(View view, boolean shouldApply, int originalColor) {
- if (originalColor != NotificationHeaderView.NO_COLOR) {
- ImageView imageView = (ImageView) view;
- imageView.getDrawable().mutate();
- if (shouldApply) {
- // lets gray it out
- Configuration config = view.getContext().getResources().getConfiguration();
- boolean inNightMode = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
- == Configuration.UI_MODE_NIGHT_YES;
- int grey = ContrastColorUtil.resolveColor(view.getContext(),
- Notification.COLOR_DEFAULT, inNightMode);
- imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
- } else {
- // lets reset it
- imageView.getDrawable().setColorFilter(originalColor,
- PorterDuff.Mode.SRC_ATOP);
- }
+ CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
+ if (icon != null) {
+ icon.setGrayedOut(apply);
+ }
+ NotificationExpandButton expand =
+ view.findViewById(com.android.internal.R.id.expand_button);
+ if (expand != null) {
+ expand.setGrayedOut(apply);
}
}
};
@@ -178,7 +158,7 @@ public class NotificationHeaderUtil {
private void sanitizeHeaderViews(ExpandableNotificationRow row) {
if (row.isSummaryWithChildren()) {
- sanitizeHeader(row.getNotificationHeader());
+ sanitizeHeader(row.getNotificationViewWrapper().getNotificationHeader());
return;
}
final NotificationContentView layout = row.getPrivateLayout();
@@ -190,7 +170,7 @@ public class NotificationHeaderUtil {
private void sanitizeChild(View child) {
if (child != null) {
ViewGroup header = child.findViewById(
- com.android.internal.R.id.notification_header);
+ com.android.internal.R.id.notification_top_line);
sanitizeHeader(header);
}
}
@@ -275,7 +255,8 @@ public class NotificationHeaderUtil {
}
public void init() {
- mParentView = mParentRow.getNotificationHeader().findViewById(mId);
+ mParentView = mParentRow.getNotificationViewWrapper().getNotificationHeader()
+ .findViewById(mId);
mParentData = mExtractor == null ? null : mExtractor.extractData(mParentRow);
mApply = !mComparator.isEmpty(mParentView);
}
@@ -305,7 +286,7 @@ public class NotificationHeaderUtil {
public void apply(ExpandableNotificationRow row, boolean reset) {
boolean apply = mApply && !reset;
if (row.isSummaryWithChildren()) {
- applyToView(apply, reset, row.getNotificationHeader());
+ applyToView(apply, reset, row.getNotificationViewWrapper().getNotificationHeader());
return;
}
applyToView(apply, reset, row.getPrivateLayout().getContractedChild());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 8d82270c9ca7..3c4830272099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -54,7 +54,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -345,8 +344,7 @@ public class NotificationLockscreenUserManagerImpl implements
return false;
}
boolean exceedsPriorityThreshold;
- if (NotificationUtils.useNewInterruptionModel(mContext)
- && hideSilentNotificationsOnLockscreen()) {
+ if (hideSilentNotificationsOnLockscreen()) {
exceedsPriorityThreshold =
entry.getBucket() == BUCKET_MEDIA_CONTROLS
|| (entry.getBucket() != BUCKET_SILENT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 53179ba4be90..9bd34ad1937f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -26,7 +26,6 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
@@ -43,6 +42,7 @@ import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectio
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.util.Assert;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.ArrayList;
import java.util.HashMap;
@@ -159,7 +159,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
final boolean isBubbleNotificationSuppressedFromShade = mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(ent);
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
+ ent.getKey(), ent.getSbn().getGroupKey());
if (ent.isRowDismissed() || ent.isRowRemoved()
|| isBubbleNotificationSuppressedFromShade
|| mFgsSectionController.hasEntry(ent)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
index 83e51cd43ed2..7ecdc812cbd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ViewTransformationHelper.java
@@ -106,12 +106,8 @@ public class ViewTransformationHelper implements TransformableView,
mViewTransformationAnimation.cancel();
}
mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
- mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- transformTo(notification, animation.getAnimatedFraction());
- }
- });
+ mViewTransformationAnimation.addUpdateListener(
+ animation -> transformTo(notification, animation.getAnimatedFraction()));
mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
@@ -167,12 +163,8 @@ public class ViewTransformationHelper implements TransformableView,
mViewTransformationAnimation.cancel();
}
mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
- mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- transformFrom(notification, animation.getAnimatedFraction());
- }
- });
+ mViewTransformationAnimation.addUpdateListener(
+ animation -> transformFrom(notification, animation.getAnimatedFraction()));
mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
public boolean mCancelled;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
index cee9c70f53eb..efd0519d6608 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.media.MediaDataManager;
@@ -62,6 +61,7 @@ import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 382715a3fb77..45e8098e71d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -32,6 +34,7 @@ import android.view.SyncRtSurfaceTransactionApplier;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.View;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -226,10 +229,27 @@ public class ActivityLaunchAnimator {
}
});
anim.addListener(new AnimatorListenerAdapter() {
+ private boolean mWasCancelled;
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ InteractionJankMonitor.getInstance().begin(CUJ_NOTIFICATION_APP_START);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mWasCancelled = true;
+ }
+
@Override
public void onAnimationEnd(Animator animation) {
setExpandAnimationRunning(false);
invokeCallback(iRemoteAnimationFinishedCallback);
+ if (!mWasCancelled) {
+ InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_APP_START);
+ } else {
+ InteractionJankMonitor.getInstance().cancel(CUJ_NOTIFICATION_APP_START);
+ }
}
});
anim.start();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
index eee9cc683e2b..967524ce308d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -16,9 +16,7 @@
package com.android.systemui.statusbar.notification;
-import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
-import android.util.Log;
import android.util.Property;
import android.view.View;
@@ -34,9 +32,14 @@ public abstract class AnimatableProperty {
public static final AnimatableProperty X = AnimatableProperty.from(View.X,
R.id.x_animator_tag, R.id.x_animator_tag_start_value, R.id.x_animator_tag_end_value);
+
public static final AnimatableProperty Y = AnimatableProperty.from(View.Y,
R.id.y_animator_tag, R.id.y_animator_tag_start_value, R.id.y_animator_tag_end_value);
+ public static final AnimatableProperty TRANSLATION_X = AnimatableProperty.from(
+ View.TRANSLATION_X, R.id.x_animator_tag, R.id.x_animator_tag_start_value,
+ R.id.x_animator_tag_end_value);
+
/**
* Similar to X, however this doesn't allow for any other modifications other than from this
* property. When using X, it's possible that the view is laid out during the animation,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index ddfa18e65ee0..44b9bd26aa38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -46,7 +46,7 @@ class ConversationNotificationProcessor @Inject constructor(
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
else
Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
- entry.ranking.shortcutInfo?.let { shortcutInfo ->
+ entry.ranking.conversationShortcutInfo?.let { shortcutInfo ->
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
shortcutInfo.label?.let { label ->
messagingStyle.conversationTitle = label
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java
index 5794f73a98f4..65e333f14df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java
@@ -47,7 +47,7 @@ public class NotificationChannelHelper {
final String pkg = entry.getSbn().getPackageName();
final int appUid = entry.getSbn().getUid();
if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(pkg)
- || entry.getRanking().getShortcutInfo() == null) {
+ || entry.getRanking().getConversationShortcutInfo() == null) {
return channel;
}
@@ -56,7 +56,7 @@ public class NotificationChannelHelper {
try {
channel.setName(getName(entry));
notificationManager.createConversationNotificationChannelForPackage(
- pkg, appUid, entry.getSbn().getKey(), channel,
+ pkg, appUid, channel,
conversationId);
channel = notificationManager.getConversationNotificationChannel(
context.getOpPackageName(), UserHandle.getUserId(appUid), pkg,
@@ -68,8 +68,8 @@ public class NotificationChannelHelper {
}
private static CharSequence getName(NotificationEntry entry) {
- if (entry.getRanking().getShortcutInfo().getLabel() != null) {
- return entry.getRanking().getShortcutInfo().getLabel().toString();
+ if (entry.getRanking().getConversationShortcutInfo().getLabel() != null) {
+ return entry.getRanking().getConversationShortcutInfo().getLabel().toString();
}
Bundle extras = entry.getSbn().getNotification().extras;
CharSequence nameString = extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE);
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 7d8979ca1129..aef01e9bd811 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -22,10 +22,10 @@ import android.util.Log;
import android.view.View;
import com.android.systemui.DejankUtils;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index e1e77b0723a4..7c3b791aed09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -618,6 +618,7 @@ public class NotificationEntryManager implements
NotificationEntry entry = mPendingNotifications.get(key);
if (entry != null) {
entry.setSbn(notification);
+ entry.setRanking(ranking);
} else {
entry = new NotificationEntry(
notification,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
index ce6013f776af..cd8897ea229d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt
@@ -61,10 +61,8 @@ class NotificationSectionsFeatureManager @Inject constructor(
isFilteringEnabled() && !isMediaControlsEnabled() ->
intArrayOf(BUCKET_HEADS_UP, BUCKET_FOREGROUND_SERVICE, BUCKET_PEOPLE,
BUCKET_ALERTING, BUCKET_SILENT)
- NotificationUtils.useNewInterruptionModel(context) ->
- intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
else ->
- intArrayOf(BUCKET_ALERTING)
+ intArrayOf(BUCKET_ALERTING, BUCKET_SILENT)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
index 1af47dd0f4c0..cbc113ba91bf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.java
@@ -16,12 +16,9 @@
package com.android.systemui.statusbar.notification;
-import static android.provider.Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL;
-
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
-import android.provider.Settings;
import android.view.View;
import android.widget.ImageView;
@@ -76,15 +73,4 @@ public class NotificationUtils {
return (int) (dimensionPixelSize * factor);
}
- /**
- * Returns the value of the new interruption model setting. This result is cached and cannot
- * change except through reboots/process restarts.
- */
- public static boolean useNewInterruptionModel(Context context) {
- if (sUseNewInterruptionModel == null) {
- sUseNewInterruptionModel = Settings.Secure.getInt(context.getContentResolver(),
- NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) != 0;
- }
- return sUseNewInterruptionModel;
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index 0455b0f18afc..29a030f910a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -25,6 +23,9 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
+import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.HashSet;
import java.util.Optional;
@@ -56,6 +57,7 @@ import javax.inject.Inject;
public class BubbleCoordinator implements Coordinator {
private static final String TAG = "BubbleCoordinator";
+ private final Optional<BubblesManager> mBubblesManagerOptional;
private final Optional<Bubbles> mBubblesOptional;
private final NotifCollection mNotifCollection;
private final Set<String> mInterceptedDismissalEntries = new HashSet<>();
@@ -64,8 +66,10 @@ public class BubbleCoordinator implements Coordinator {
@Inject
public BubbleCoordinator(
+ Optional<BubblesManager> bubblesManagerOptional,
Optional<Bubbles> bubblesOptional,
NotifCollection notifCollection) {
+ mBubblesManagerOptional = bubblesManagerOptional;
mBubblesOptional = bubblesOptional;
mNotifCollection = notifCollection;
}
@@ -75,8 +79,8 @@ public class BubbleCoordinator implements Coordinator {
mNotifPipeline = pipeline;
mNotifPipeline.addNotificationDismissInterceptor(mDismissInterceptor);
mNotifPipeline.addFinalizeFilter(mNotifFilter);
- if (mBubblesOptional.isPresent()) {
- mBubblesOptional.get().addNotifCallback(mNotifCallback);
+ if (mBubblesManagerOptional.isPresent()) {
+ mBubblesManagerOptional.get().addNotifCallback(mNotifCallback);
}
}
@@ -85,7 +89,8 @@ public class BubbleCoordinator implements Coordinator {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
return mBubblesOptional.isPresent()
- && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(entry);
+ && mBubblesOptional.get().isBubbleNotificationSuppressedFromShade(
+ entry.getKey(), entry.getSbn().getGroupKey());
}
};
@@ -102,9 +107,8 @@ public class BubbleCoordinator implements Coordinator {
@Override
public boolean shouldInterceptDismissal(NotificationEntry entry) {
- // for experimental bubbles
- if (mBubblesOptional.isPresent()
- && mBubblesOptional.get().handleDismissalInterception(entry)) {
+ if (mBubblesManagerOptional.isPresent()
+ && mBubblesManagerOptional.get().handleDismissalInterception(entry)) {
mInterceptedDismissalEntries.add(entry.getKey());
return true;
} else {
@@ -119,8 +123,7 @@ public class BubbleCoordinator implements Coordinator {
}
};
- private final BubbleController.NotifCallback mNotifCallback =
- new BubbleController.NotifCallback() {
+ private final BubblesManager.NotifCallback mNotifCallback = new BubblesManager.NotifCallback() {
@Override
public void removeNotification(
NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 318cdb171fa5..b1c6f535ba87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -37,7 +37,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -150,8 +149,7 @@ public class KeyguardCoordinator implements Coordinator {
if (entry == null) {
return false;
}
- if (NotificationUtils.useNewInterruptionModel(mContext)
- && mHideSilentNotificationsOnLockscreen) {
+ if (mHideSilentNotificationsOnLockscreen) {
return mHighPriorityProvider.isHighPriority(entry);
} else {
return entry.getRepresentativeEntry() != null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 133ddfebe84f..6da4d8b70944 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -41,8 +41,6 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class RankingCoordinator implements Coordinator {
- private static final String TAG = "RankingNotificationCoordinator";
-
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
private final NodeController mSilentHeaderController;
@@ -65,7 +63,7 @@ public class RankingCoordinator implements Coordinator {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
pipeline.addPreGroupFilter(mSuspendedFilter);
- pipeline.addPreGroupFilter(mDozingFilter);
+ pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
}
public NotifSectioner getAlertingSectioner() {
@@ -114,10 +112,10 @@ public class RankingCoordinator implements Coordinator {
}
};
- private final NotifFilter mDozingFilter = new NotifFilter("IsDozingFilter") {
+ private final NotifFilter mDndVisualEffectsFilter = new NotifFilter(
+ "DndSuppressingVisualEffects") {
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- // Dozing + DND Settings from Ranking object
if (mStatusBarStateController.isDozing() && entry.shouldSuppressAmbient()) {
return true;
}
@@ -130,7 +128,7 @@ public class RankingCoordinator implements Coordinator {
new StatusBarStateController.StateListener() {
@Override
public void onDozingChanged(boolean isDozing) {
- mDozingFilter.invalidateList();
+ mDndVisualEffectsFilter.invalidateList();
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
index 490989dbb39e..3db544011700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/NotificationGroupManagerLegacy.java
@@ -22,7 +22,6 @@ import android.util.ArraySet;
import android.util.Log;
import com.android.systemui.Dumpable;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -34,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.wm.shell.bubbles.Bubbles;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -64,7 +64,7 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
new ArraySet<>();
private final ArraySet<OnGroupChangeListener> mGroupChangeListeners = new ArraySet<>();
private final Lazy<PeopleNotificationIdentifier> mPeopleNotificationIdentifier;
- private final Optional<Lazy<Bubbles>> mBubblesOptional;
+ private final Optional<Bubbles> mBubblesOptional;
private int mBarState = -1;
private HashMap<String, StatusBarNotification> mIsolatedEntries = new HashMap<>();
private HeadsUpManager mHeadsUpManager;
@@ -74,7 +74,7 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
public NotificationGroupManagerLegacy(
StatusBarStateController statusBarStateController,
Lazy<PeopleNotificationIdentifier> peopleNotificationIdentifier,
- Optional<Lazy<Bubbles>> bubblesOptional) {
+ Optional<Bubbles> bubblesOptional) {
statusBarStateController.addCallback(this);
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
mBubblesOptional = bubblesOptional;
@@ -242,8 +242,9 @@ public class NotificationGroupManagerLegacy implements OnHeadsUpChangedListener,
int childCount = 0;
boolean hasBubbles = false;
for (NotificationEntry entry : group.children.values()) {
- if (mBubblesOptional.isPresent() && !mBubblesOptional.get().get()
- .isBubbleNotificationSuppressedFromShade(entry)) {
+ if (mBubblesOptional.isPresent() && !mBubblesOptional.get()
+ .isBubbleNotificationSuppressedFromShade(
+ entry.getKey(), entry.getSbn().getGroupKey())) {
childCount++;
} else {
hasBubbles = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index 4fff99b482d8..ff55cd60ab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -26,7 +26,6 @@ import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -73,6 +72,7 @@ import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogC
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.wmshell.BubblesManager;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -133,7 +133,7 @@ public interface NotificationsModule {
UserContextProvider contextTracker,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
- Optional<Bubbles> bubblesOptional,
+ Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback) {
return new NotificationGutsManager(
@@ -150,7 +150,7 @@ public interface NotificationsModule {
contextTracker,
builderProvider,
assistantFeedbackController,
- bubblesOptional,
+ bubblesManagerOptional,
uiEventLogger,
onUserInteractionCallback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 13f7a53f5e54..ba45f9a687ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -255,7 +255,7 @@ class IconManager @Inject constructor(
private fun createPeopleAvatar(entry: NotificationEntry): Icon? {
var ic: Icon? = null
- val shortcut = entry.ranking.shortcutInfo
+ val shortcut = entry.ranking.conversationShortcutInfo
if (shortcut != null) {
ic = launcherApps.getShortcutIcon(shortcut)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
index 049b471aa7cb..52b9b0606e81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsController.kt
@@ -17,13 +17,13 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.Bubbles
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.StatusBar
+import com.android.wm.shell.bubbles.Bubbles
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.Optional
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 45a5d1044b9a..8f352ad55041 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.Bubbles
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.FeatureFlags
@@ -41,6 +40,7 @@ import com.android.systemui.statusbar.phone.StatusBar
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.RemoteInputUriController
+import com.android.wm.shell.bubbles.Bubbles
import dagger.Lazy
import java.io.FileDescriptor
import java.io.PrintWriter
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
index 7569c1bdbb73..d0e68bf75373 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.Bubbles
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
@@ -25,6 +24,7 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.StatusBar
+import com.android.wm.shell.bubbles.Bubbles
import java.io.FileDescriptor
import java.io.PrintWriter
import java.util.Optional
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 99b2fcc9d610..cd9ba4e690e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -220,7 +220,7 @@ class PeopleHubDataSourceImpl @Inject constructor(
}
val clickRunnable = Runnable { notificationListener.unsnoozeNotification(key) }
val extras = sbn.notification.extras
- val name = ranking.shortcutInfo?.label
+ val name = ranking.conversationShortcutInfo?.label
?: extras.getCharSequence(Notification.EXTRA_CONVERSATION_TITLE)
?: extras.getCharSequence(Notification.EXTRA_TITLE)
?: return null
@@ -238,9 +238,9 @@ class PeopleHubDataSourceImpl @Inject constructor(
iconFactory: ConversationIconFactory,
sbn: StatusBarNotification
): Drawable? =
- shortcutInfo?.let { shortcutInfo ->
+ conversationShortcutInfo?.let { conversationShortcutInfo ->
iconFactory.getConversationDrawable(
- shortcutInfo,
+ conversationShortcutInfo,
sbn.packageName,
sbn.uid,
channel.isImportantConversation
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 0d92616767f3..691f1f452da8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -103,7 +103,7 @@ class PeopleNotificationIdentifierImpl @Inject constructor(
private val Ranking.personTypeInfo
get() = when {
!isConversation -> TYPE_NON_PERSON
- shortcutInfo == null -> TYPE_PERSON
+ conversationShortcutInfo == null -> TYPE_PERSON
channel?.isImportantConversation == true -> TYPE_IMPORTANT_PERSON
else -> TYPE_FULL_PERSON
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 094e8661d262..10273cbbebad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -33,6 +33,7 @@ import android.view.accessibility.AccessibilityManager;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -750,12 +751,16 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (!mWasCancelled) {
enableAppearDrawing(false);
onAppearAnimationFinished(isAppearing);
+ InteractionJankMonitor.getInstance().end(getCujType(isAppearing));
+ } else {
+ InteractionJankMonitor.getInstance().cancel(getCujType(isAppearing));
}
}
@Override
public void onAnimationStart(Animator animation) {
mWasCancelled = false;
+ InteractionJankMonitor.getInstance().begin(getCujType(isAppearing));
}
@Override
@@ -766,6 +771,18 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mAppearAnimator.start();
}
+ private int getCujType(boolean isAppearing) {
+ if (mIsHeadsUpAnimation) {
+ return isAppearing
+ ? InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR
+ : InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR;
+ } else {
+ return isAppearing
+ ? InteractionJankMonitor.CUJ_NOTIFICATION_ADD
+ : InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE;
+ }
+ }
+
protected void onAppearAnimationFinished(boolean wasAppearing) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d8d412bf2d41..a011d36af11d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -35,6 +35,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.Path;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.AnimationDrawable;
@@ -43,6 +44,7 @@ import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -72,7 +74,6 @@ import com.android.internal.widget.CachingIconView;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -102,12 +103,14 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
+import com.android.systemui.wmshell.BubblesManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -147,6 +150,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private LayoutListener mLayoutListener;
private RowContentBindStage mRowContentBindStage;
private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+ private Optional<BubblesManager> mBubblesManagerOptional;
private int mIconTransformContentShift;
private int mMaxHeadsUpHeightBeforeN;
private int mMaxHeadsUpHeightBeforeP;
@@ -304,7 +308,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
};
- private boolean mForceUnlocked;
private boolean mKeepInParent;
private boolean mRemoved;
private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT =
@@ -326,6 +329,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private boolean mShelfIconVisible;
private boolean mAboveShelf;
private OnUserInteractionCallback mOnUserInteractionCallback;
+ private NotificationGutsManager mNotificationGutsManager;
private boolean mIsLowPriority;
private boolean mIsColorized;
private boolean mUseIncreasedCollapsedHeight;
@@ -410,8 +414,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setIconAnimationRunning(running, l);
}
if (mIsSummaryWithChildren) {
- setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
- setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView());
+ NotificationViewWrapper viewWrapper = mChildrenContainer.getNotificationViewWrapper();
+ if (viewWrapper != null) {
+ setIconAnimationRunningForChild(running, viewWrapper.getIcon());
+ }
+ NotificationViewWrapper lowPriWrapper = mChildrenContainer.getLowPriorityViewWrapper();
+ if (lowPriWrapper != null) {
+ setIconAnimationRunningForChild(running, lowPriWrapper.getIcon());
+ }
List<ExpandableNotificationRow> notificationChildren =
mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
@@ -435,10 +445,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void setIconAnimationRunningForChild(boolean running, View child) {
if (child != null) {
- ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
+ ImageView icon = child.findViewById(com.android.internal.R.id.icon);
setIconRunning(icon, running);
- ImageView rightIcon = (ImageView) child.findViewById(
- com.android.internal.R.id.right_icon);
+ ImageView rightIcon = child.findViewById(com.android.internal.R.id.right_icon);
setIconRunning(rightIcon, running);
}
}
@@ -497,7 +506,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Returns whether this row is considered non-blockable (i.e. it's a non-blockable system notif
- * or is in a whitelist).
+ * or is in an allowList).
*/
public boolean getIsNonblockable() {
// If the SystemNotifAsyncTask hasn't finished running or retrieved a value, we'll try once
@@ -594,7 +603,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public int getOriginalIconColor() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
- return mChildrenContainer.getVisibleHeader().getOriginalIconColor();
+ return mChildrenContainer.getVisibleWrapper().getOriginalIconColor();
}
int color = getShowingLayout().getOriginalIconColor();
if (color != Notification.COLOR_INVALID) {
@@ -1040,22 +1049,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
- public NotificationHeaderView getNotificationHeader() {
+ /**
+ * @return the main notification view wrapper.
+ */
+ public NotificationViewWrapper getNotificationViewWrapper() {
if (mIsSummaryWithChildren) {
- return mChildrenContainer.getHeaderView();
+ return mChildrenContainer.getNotificationViewWrapper();
}
- return mPrivateLayout.getNotificationHeader();
+ return mPrivateLayout.getNotificationViewWrapper();
}
/**
- * @return the currently visible notification header. This can be different from
- * {@link #getNotificationHeader()} in case it is a low-priority group.
+ * @return the currently visible notification view wrapper. This can be different from
+ * {@link #getNotificationViewWrapper()} in case it is a low-priority group.
*/
- public NotificationHeaderView getVisibleNotificationHeader() {
+ public NotificationViewWrapper getVisibleNotificationViewWrapper() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
- return mChildrenContainer.getVisibleHeader();
+ return mChildrenContainer.getVisibleWrapper();
}
- return getShowingLayout().getVisibleNotificationHeader();
+ return getShowingLayout().getVisibleWrapper();
}
public void setLongPressListener(LongPressListener longPressListener) {
@@ -1071,13 +1083,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** The click listener for the bubble button. */
public View.OnClickListener getBubbleClickListener() {
- return new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Dependency.get(Bubbles.class)
- .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
- mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+ return v -> {
+ if (mBubblesManagerOptional.isPresent()) {
+ mBubblesManagerOptional.get()
+ .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
}
+ mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+ };
+ }
+
+ /** The click listener for the snooze button. */
+ public View.OnClickListener getSnoozeClickListener(MenuItem item) {
+ return v -> {
+ mNotificationGutsManager.openGuts(this, 0, 0, item);
};
}
@@ -1147,10 +1165,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
*/
@Nullable
public NotificationMenuRowPlugin createMenu() {
- if (mMenuRow == null) {
+ final boolean removeShelf = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0 /* show shelf by default */) == 1;
+ if (mMenuRow == null || removeShelf) {
return null;
}
-
if (mMenuRow.getMenuView() == null) {
mMenuRow.createMenu(this, mEntry.getSbn());
mMenuRow.setAppName(mAppName);
@@ -1294,16 +1313,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
onAttachedChildrenCountChanged();
}
- public void setForceUnlocked(boolean forceUnlocked) {
- mForceUnlocked = forceUnlocked;
- if (mIsSummaryWithChildren) {
- List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
- for (ExpandableNotificationRow child : notificationChildren) {
- child.setForceUnlocked(forceUnlocked);
- }
- }
- }
-
@Override
public void dismiss(boolean refocusOnDismiss) {
super.dismiss(refocusOnDismiss);
@@ -1422,7 +1431,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
public View getShelfTransformationTarget() {
if (mIsSummaryWithChildren && !shouldShowPublic()) {
- return mChildrenContainer.getVisibleHeader().getIcon();
+ return mChildrenContainer.getVisibleWrapper().getShelfTransformationTarget();
}
return getShowingLayout().getShelfTransformationTarget();
}
@@ -1556,7 +1565,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
FalsingManager falsingManager,
StatusBarStateController statusBarStateController,
PeopleNotificationIdentifier peopleNotificationIdentifier,
- OnUserInteractionCallback onUserInteractionCallback) {
+ OnUserInteractionCallback onUserInteractionCallback,
+ Optional<BubblesManager> bubblesManagerOptional,
+ NotificationGutsManager gutsManager) {
mEntry = entry;
mAppName = appName;
if (mMenuRow == null) {
@@ -1584,6 +1595,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier);
}
mOnUserInteractionCallback = onUserInteractionCallback;
+ mBubblesManagerOptional = bubblesManagerOptional;
+ mNotificationGutsManager = gutsManager;
cacheIsSystemNotification();
}
@@ -1646,19 +1659,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/** Sets the last time the notification being displayed audibly alerted the user. */
public void setLastAudiblyAlertedMs(long lastAudiblyAlertedMs) {
- if (NotificationUtils.useNewInterruptionModel(mContext)) {
- long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
- boolean alertedRecently =
- timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
+ long timeSinceAlertedAudibly = System.currentTimeMillis() - lastAudiblyAlertedMs;
+ boolean alertedRecently = timeSinceAlertedAudibly < RECENTLY_ALERTED_THRESHOLD_MS;
- applyAudiblyAlertedRecently(alertedRecently);
+ applyAudiblyAlertedRecently(alertedRecently);
- removeCallbacks(mExpireRecentlyAlertedFlag);
- if (alertedRecently) {
- long timeUntilNoLongerRecent =
- RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
- postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
- }
+ removeCallbacks(mExpireRecentlyAlertedFlag);
+ if (alertedRecently) {
+ long timeUntilNoLongerRecent = RECENTLY_ALERTED_THRESHOLD_MS - timeSinceAlertedAudibly;
+ postDelayed(mExpireRecentlyAlertedFlag, timeUntilNoLongerRecent);
}
}
@@ -1693,37 +1702,30 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
- mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
+ mPublicLayout = findViewById(R.id.expandedPublic);
+ mPrivateLayout = findViewById(R.id.expanded);
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener);
l.setContainingNotification(this);
}
- mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
- mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
- @Override
- public void onInflate(ViewStub stub, View inflated) {
- mGuts = (NotificationGuts) inflated;
- mGuts.setClipTopAmount(getClipTopAmount());
- mGuts.setActualHeight(getActualHeight());
- mGutsStub = null;
- }
+ mGutsStub = findViewById(R.id.notification_guts_stub);
+ mGutsStub.setOnInflateListener((stub, inflated) -> {
+ mGuts = (NotificationGuts) inflated;
+ mGuts.setClipTopAmount(getClipTopAmount());
+ mGuts.setActualHeight(getActualHeight());
+ mGutsStub = null;
});
- mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub);
- mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() {
+ mChildrenContainerStub = findViewById(R.id.child_container_stub);
+ mChildrenContainerStub.setOnInflateListener((stub, inflated) -> {
+ mChildrenContainer = (NotificationChildrenContainer) inflated;
+ mChildrenContainer.setIsLowPriority(mIsLowPriority);
+ mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
+ mChildrenContainer.onNotificationUpdated();
- @Override
- public void onInflate(ViewStub stub, View inflated) {
- mChildrenContainer = (NotificationChildrenContainer) inflated;
- mChildrenContainer.setIsLowPriority(mIsLowPriority);
- mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
- mChildrenContainer.onNotificationUpdated();
-
- if (mShouldTranslateContents) {
- mTranslateableViews.add(mChildrenContainer);
- }
+ if (mShouldTranslateContents) {
+ mTranslateableViews.add(mChildrenContainer);
}
});
@@ -2183,7 +2185,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isUserLocked() {
- return mUserLocked && !mForceUnlocked;
+ return mUserLocked;
}
public void setUserLocked(boolean userLocked) {
@@ -2303,8 +2305,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void onAttachedChildrenCountChanged() {
mIsSummaryWithChildren = mChildrenContainer != null
&& mChildrenContainer.getNotificationChildCount() > 0;
- if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
- mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
+ if (mIsSummaryWithChildren) {
+ NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper();
+ if (wrapper == null || wrapper.getNotificationHeader() == null) {
+ mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
+ isConversation());
+ }
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
@@ -2414,9 +2420,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* the top.
*/
private void updateContentShiftHeight() {
- NotificationHeaderView notificationHeader = getVisibleNotificationHeader();
- if (notificationHeader != null) {
- CachingIconView icon = notificationHeader.getIcon();
+ NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper();
+ CachingIconView icon = wrapper == null ? null : wrapper.getIcon();
+ if (icon != null) {
mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight();
} else {
mIconTransformContentShift = mContentShift;
@@ -2504,12 +2510,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
.alpha(0f)
.setStartDelay(delay)
.setDuration(duration)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- hiddenView.setVisibility(View.INVISIBLE);
- }
- });
+ .withEndAction(() -> hiddenView.setVisibility(View.INVISIBLE));
}
for (View showView : shownChildren) {
showView.setVisibility(View.VISIBLE);
@@ -2867,7 +2868,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
float x = event.getX();
float y = event.getY();
- NotificationHeaderView header = getVisibleNotificationHeader();
+ NotificationViewWrapper wrapper = getVisibleNotificationViewWrapper();
+ NotificationHeaderView header = wrapper == null ? null : wrapper.getNotificationHeader();
if (header != null && header.isInTouchRect(x - getTranslation(), y)) {
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index c995e324ecfe..cb2af54a25cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -43,8 +43,10 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.wmshell.BubblesManager;
import java.util.List;
+import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Named;
@@ -79,6 +81,7 @@ public class ExpandableNotificationRowController implements NodeController {
private final FalsingManager mFalsingManager;
private final boolean mAllowLongPress;
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+ private final Optional<BubblesManager> mBubblesManagerOptional;
@Inject
public ExpandableNotificationRowController(
@@ -102,7 +105,8 @@ public class ExpandableNotificationRowController implements NodeController {
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
OnUserInteractionCallback onUserInteractionCallback,
FalsingManager falsingManager,
- PeopleNotificationIdentifier peopleNotificationIdentifier) {
+ PeopleNotificationIdentifier peopleNotificationIdentifier,
+ Optional<BubblesManager> bubblesManagerOptional) {
mView = view;
mListContainer = listContainer;
mActivatableNotificationViewController = activatableNotificationViewController;
@@ -125,6 +129,7 @@ public class ExpandableNotificationRowController implements NodeController {
mAllowLongPress = allowLongPress;
mFalsingManager = falsingManager;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+ mBubblesManagerOptional = bubblesManagerOptional;
}
/**
@@ -148,8 +153,9 @@ public class ExpandableNotificationRowController implements NodeController {
mFalsingManager,
mStatusBarStateController,
mPeopleNotificationIdentifier,
- mOnUserInteractionCallback
-
+ mOnUserInteractionCallback,
+ mBubblesManagerOptional,
+ mNotificationGutsManager
);
mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (mAllowLongPress) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 8a644ed4d3ff..79c300782ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -18,12 +18,14 @@ package com.android.systemui.statusbar.notification.row;
import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
+import static android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.res.Resources;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -31,6 +33,7 @@ import android.provider.Settings;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.NotificationHeaderView;
import android.view.View;
@@ -44,7 +47,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.statusbar.MediaTransferManager;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
@@ -173,12 +176,10 @@ public class NotificationContentView extends FrameLayout {
private boolean mIsContentExpandable;
private boolean mRemoteInputVisible;
private int mUnrestrictedContentHeight;
- private MediaTransferManager mMediaTransferManager;
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
mHybridGroupManager = new HybridGroupManager(getContext());
- mMediaTransferManager = new MediaTransferManager(getContext());
mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
mSmartReplyController = Dependency.get(SmartReplyController.class);
initView();
@@ -339,7 +340,6 @@ public class NotificationContentView extends FrameLayout {
? contractedHeader.getPaddingLeft()
: paddingEnd,
contractedHeader.getPaddingBottom());
- contractedHeader.setShowWorkBadgeAtEnd(false);
return true;
}
}
@@ -462,7 +462,7 @@ public class NotificationContentView extends FrameLayout {
mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
if (mContainingNotification != null) {
- applyBubbleAction(mExpandedChild, mContainingNotification.getEntry());
+ applySystemActions(mExpandedChild, mContainingNotification.getEntry());
}
}
@@ -504,7 +504,7 @@ public class NotificationContentView extends FrameLayout {
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
if (mContainingNotification != null) {
- applyBubbleAction(mHeadsUpChild, mContainingNotification.getEntry());
+ applySystemActions(mHeadsUpChild, mContainingNotification.getEntry());
}
}
@@ -1021,6 +1021,10 @@ public class NotificationContentView extends FrameLayout {
mSingleLineView };
}
+ public NotificationViewWrapper getVisibleWrapper() {
+ return getVisibleWrapper(mVisibleType);
+ }
+
public NotificationViewWrapper getVisibleWrapper(int visibleType) {
switch (visibleType) {
case VISIBLE_TYPE_EXPANDED:
@@ -1157,13 +1161,12 @@ public class NotificationContentView extends FrameLayout {
mHeadsUpWrapper.onContentUpdated(row);
}
applyRemoteInputAndSmartReply(entry);
- applyMediaTransfer(entry);
updateLegacy();
mForceSelectNextLayout = true;
mPreviousExpandedRemoteInputIntent = null;
mPreviousHeadsUpRemoteInputIntent = null;
- applyBubbleAction(mExpandedChild, entry);
- applyBubbleAction(mHeadsUpChild, entry);
+ applySystemActions(mExpandedChild, entry);
+ applySystemActions(mHeadsUpChild, entry);
}
private void updateAllSingleLineViews() {
@@ -1185,22 +1188,6 @@ public class NotificationContentView extends FrameLayout {
}
}
- private void applyMediaTransfer(final NotificationEntry entry) {
- if (!entry.isMediaNotification()) {
- return;
- }
-
- View bigContentView = mExpandedChild;
- if (bigContentView != null && (bigContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView, entry);
- }
-
- View smallContentView = mContractedChild;
- if (smallContentView != null && (smallContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView, entry);
- }
- }
-
/**
* Returns whether the {@link Notification} represented by entry has a free-form remote input.
* Such an input can be used e.g. to implement smart reply buttons - by passing the replies
@@ -1357,6 +1344,14 @@ public class NotificationContentView extends FrameLayout {
NOTIFICATION_BUBBLES, 0) == 1;
}
+ /**
+ * Setup icon buttons provided by System UI.
+ */
+ private void applySystemActions(View layout, NotificationEntry entry) {
+ applySnoozeAction(layout);
+ applyBubbleAction(layout, entry);
+ }
+
private void applyBubbleAction(View layout, NotificationEntry entry) {
if (layout == null || mContainingNotification == null || mPeopleIdentifier == null) {
return;
@@ -1376,8 +1371,8 @@ public class NotificationContentView extends FrameLayout {
&& entry.getBubbleMetadata() != null;
if (showButton) {
Drawable d = mContext.getResources().getDrawable(entry.isBubble()
- ? R.drawable.ic_stop_bubble
- : R.drawable.ic_create_bubble);
+ ? R.drawable.bubble_ic_stop_bubble
+ : R.drawable.bubble_ic_create_bubble);
mContainingNotification.updateNotificationColor();
final int tint = mContainingNotification.getNotificationColor();
d.setTint(tint);
@@ -1404,6 +1399,45 @@ public class NotificationContentView extends FrameLayout {
}
}
+ private void applySnoozeAction(View layout) {
+ if (layout == null || mContainingNotification == null) {
+ return;
+ }
+ ImageView snoozeButton = layout.findViewById(com.android.internal.R.id.snooze_button);
+ View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
+ LinearLayout actionContainerLayout =
+ layout.findViewById(com.android.internal.R.id.actions_container_layout);
+ if (snoozeButton == null || actionContainer == null || actionContainerLayout == null) {
+ return;
+ }
+ final boolean showSnooze = Settings.Secure.getInt(mContext.getContentResolver(),
+ SHOW_NOTIFICATION_SNOOZE, 0) == 1;
+ if (!showSnooze) {
+ snoozeButton.setVisibility(GONE);
+ return;
+ }
+
+ Resources res = mContext.getResources();
+ Drawable snoozeDrawable = res.getDrawable(R.drawable.ic_snooze);
+ mContainingNotification.updateNotificationColor();
+ snoozeDrawable.setTint(mContainingNotification.getNotificationColor());
+ snoozeButton.setImageDrawable(snoozeDrawable);
+
+ final NotificationSnooze snoozeGuts = (NotificationSnooze) LayoutInflater.from(mContext)
+ .inflate(R.layout.notification_snooze, null, false);
+ final String snoozeDescription = res.getString(
+ R.string.notification_menu_snooze_description);
+ final NotificationMenuRowPlugin.MenuItem snoozeMenuItem =
+ new NotificationMenuRow.NotificationMenuItem(
+ mContext, snoozeDescription, snoozeGuts, R.drawable.ic_snooze);
+ snoozeButton.setContentDescription(
+ mContext.getResources().getString(R.string.notification_menu_snooze_description));
+ snoozeButton.setOnClickListener(
+ mContainingNotification.getSnoozeClickListener(snoozeMenuItem));
+ snoozeButton.setVisibility(VISIBLE);
+ actionContainer.setVisibility(VISIBLE);
+ }
+
private void applySmartReplyView(
SmartRepliesAndActions smartRepliesAndActions,
NotificationEntry entry) {
@@ -1561,18 +1595,20 @@ public class NotificationContentView extends FrameLayout {
mIsContentExpandable = expandable;
}
- public NotificationHeaderView getNotificationHeader() {
- NotificationHeaderView header = null;
- if (mContractedChild != null) {
- header = mContractedWrapper.getNotificationHeader();
+ /**
+ * @return a view wrapper for one of the inflated states of the notification.
+ */
+ public NotificationViewWrapper getNotificationViewWrapper() {
+ if (mContractedChild != null && mContractedWrapper != null) {
+ return mContractedWrapper;
}
- if (header == null && mExpandedChild != null) {
- header = mExpandedWrapper.getNotificationHeader();
+ if (mExpandedChild != null && mExpandedWrapper != null) {
+ return mExpandedWrapper;
}
- if (header == null && mHeadsUpChild != null) {
- header = mHeadsUpWrapper.getNotificationHeader();
+ if (mHeadsUpChild != null && mHeadsUpWrapper != null) {
+ return mHeadsUpWrapper;
}
- return header;
+ return null;
}
public void showFeedbackIcon(boolean show) {
@@ -1600,11 +1636,6 @@ public class NotificationContentView extends FrameLayout {
}
}
- public NotificationHeaderView getVisibleNotificationHeader() {
- NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
- return wrapper == null ? null : wrapper.getNotificationHeader();
- }
-
public void setContainingNotification(ExpandableNotificationRow containingNotification) {
mContainingNotification = containingNotification;
}
@@ -1662,11 +1693,9 @@ public class NotificationContentView extends FrameLayout {
}
if (mExpandedWrapper != null) {
mExpandedWrapper.setRemoved();
- mMediaTransferManager.setRemoved(mExpandedChild);
}
if (mContractedWrapper != null) {
mContractedWrapper.setRemoved();
- mMediaTransferManager.setRemoved(mContractedChild);
}
if (mHeadsUpWrapper != null) {
mHeadsUpWrapper.setRemoved();
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 07a4a188bc48..e43130f1698b 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
@@ -67,12 +67,12 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Prefs;
import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.wmshell.BubblesManager;
import java.lang.annotation.Retention;
import java.util.Optional;
@@ -94,7 +94,7 @@ public class NotificationConversationInfo extends LinearLayout implements
private OnUserInteractionCallback mOnUserInteractionCallback;
private Handler mMainHandler;
private Handler mBgHandler;
- private Optional<Bubbles> mBubblesOptional;
+ private Optional<BubblesManager> mBubblesManagerOptional;
private String mPackageName;
private String mAppName;
private int mAppUid;
@@ -223,7 +223,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Main Handler mainHandler,
@Background Handler bgHandler,
OnConversationSettingsClickListener onConversationSettingsClickListener,
- Optional<Bubbles> bubblesOptional) {
+ Optional<BubblesManager> bubblesManagerOptional) {
mSelectedAction = -1;
mINotificationManager = iNotificationManager;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -242,12 +242,12 @@ public class NotificationConversationInfo extends LinearLayout implements
mIconFactory = conversationIconFactory;
mUserContext = userContext;
mBubbleMetadata = bubbleMetadata;
- mBubblesOptional = bubblesOptional;
+ mBubblesManagerOptional = bubblesManagerOptional;
mBuilderProvider = builderProvider;
mMainHandler = mainHandler;
mBgHandler = bgHandler;
mShortcutManager = shortcutManager;
- mShortcutInfo = entry.getRanking().getShortcutInfo();
+ mShortcutInfo = entry.getRanking().getConversationShortcutInfo();
if (mShortcutInfo == null) {
throw new IllegalArgumentException("Does not have required information");
}
@@ -641,9 +641,9 @@ public class NotificationConversationInfo extends LinearLayout implements
mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
BUBBLE_PREFERENCE_SELECTED);
}
- if (mBubblesOptional.isPresent()) {
+ if (mBubblesManagerOptional.isPresent()) {
post(() -> {
- mBubblesOptional.get().onUserChangedImportance(mEntry);
+ mBubblesManagerOptional.get().onUserChangedImportance(mEntry);
});
}
}
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 373f20e6ba96..d2cfb2908e9c 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
@@ -47,7 +47,6 @@ import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -67,6 +66,7 @@ import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSav
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.wmshell.BubblesManager;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -117,7 +117,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private final Lazy<StatusBar> mStatusBarLazy;
private final Handler mMainHandler;
private final Handler mBgHandler;
- private final Optional<Bubbles> mBubblesOptional;
+ private final Optional<BubblesManager> mBubblesManagerOptional;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
private final LauncherApps mLauncherApps;
@@ -142,7 +142,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
UserContextProvider contextTracker,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
AssistantFeedbackController assistantFeedbackController,
- Optional<Bubbles> bubblesOptional,
+ Optional<BubblesManager> bubblesManagerOptional,
UiEventLogger uiEventLogger,
OnUserInteractionCallback onUserInteractionCallback) {
mContext = context;
@@ -158,7 +158,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mBuilderProvider = builderProvider;
mChannelEditorDialogController = channelEditorDialogController;
mAssistantFeedbackController = assistantFeedbackController;
- mBubblesOptional = bubblesOptional;
+ mBubblesManagerOptional = bubblesManagerOptional;
mUiEventLogger = uiEventLogger;
mOnUserInteractionCallback = onUserInteractionCallback;
}
@@ -491,7 +491,7 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mMainHandler,
mBgHandler,
onConversationSettingsListener,
- mBubblesOptional);
+ mBubblesManagerOptional);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
index 7a976ac82223..6ff5ed1bceae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInfo.java
@@ -236,6 +236,7 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
NotificationChannel.DEFAULT_CHANNEL_ID)
&& numTotalChannels == 1;
}
+ mIsAutomaticChosen = getAlertingBehavior() == BEHAVIOR_AUTOMATIC;
bindHeader();
bindChannelDetails();
@@ -658,13 +659,14 @@ public class NotificationInfo extends LinearLayout implements NotificationGuts.G
try {
if (mChannelToUpdate != null) {
if (mUnlockImportance) {
- mChannelToUpdate.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ mINotificationManager.unlockNotificationChannel(
+ mPackageName, mAppUid, mChannelToUpdate.getId());
} else {
mChannelToUpdate.setImportance(mNewImportance);
mChannelToUpdate.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+ mINotificationManager.updateNotificationChannelForPackage(
+ mPackageName, mAppUid, mChannelToUpdate);
}
- mINotificationManager.updateNotificationChannelForPackage(
- mPackageName, mAppUid, mChannelToUpdate);
} else {
// For notifications with more than one channel, update notification enabled
// state. If the importance was lowered, we disable notifications.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
index 41f93cceacc7..d58c183f27e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationBigTextTemplateViewWrapper.java
@@ -37,7 +37,7 @@ public class NotificationBigTextTemplateViewWrapper extends NotificationTemplate
}
private void resolveViews(StatusBarNotification notification) {
- mBigtext = (ImageFloatingTextView) mView.findViewById(com.android.internal.R.id.big_text);
+ mBigtext = mView.findViewById(com.android.internal.R.id.big_text);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 3f5867477f16..5aeacaba6f64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -22,8 +22,10 @@ import android.app.Notification;
import android.content.Context;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
+import android.view.NotificationTopLineView;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
@@ -34,19 +36,17 @@ import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
-import com.android.systemui.R;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.CustomInterpolatorTransformation;
import com.android.systemui.statusbar.notification.ImageTransformState;
-import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import java.util.Stack;
/**
- * Wraps a notification header view.
+ * Wraps a notification view which may or may not include a header.
*/
public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
@@ -55,11 +55,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
protected final ViewTransformationHelper mTransformationHelper;
- protected int mColor;
-
private CachingIconView mIcon;
private NotificationExpandButton mExpandButton;
protected NotificationHeaderView mNotificationHeader;
+ protected NotificationTopLineView mNotificationTopLine;
private TextView mHeaderText;
private TextView mAppNameText;
private ImageView mWorkProfileImage;
@@ -69,13 +68,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
- private boolean mShowExpandButtonAtEnd;
protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
super(ctx, view, row);
- mShowExpandButtonAtEnd = ctx.getResources().getBoolean(
- R.bool.config_showNotificationExpandButtonAtEnd)
- || NotificationUtils.useNewInterruptionModel(ctx);
mTransformationHelper = new ViewTransformationHelper();
// we want to avoid that the header clashes with the other text when transforming
@@ -115,18 +110,15 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
+ mNotificationTopLine = mView.findViewById(com.android.internal.R.id.notification_top_line);
mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
mFeedbackIcon = mView.findViewById(com.android.internal.R.id.feedback);
- if (mNotificationHeader != null) {
- mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
- mColor = mNotificationHeader.getOriginalIconColor();
- }
}
private void addFeedbackOnClickListener(ExpandableNotificationRow row) {
View.OnClickListener listener = row.getFeedbackOnClickListener();
- if (mNotificationHeader != null) {
- mNotificationHeader.setFeedbackOnClickListener(listener);
+ if (mNotificationTopLine != null) {
+ mNotificationTopLine.setFeedbackOnClickListener(listener);
}
if (mFeedbackIcon != null) {
mFeedbackIcon.setOnClickListener(listener);
@@ -170,13 +162,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mAppNameText.setTextAppearance(
com.android.internal.R.style
.TextAppearance_DeviceDefault_Notification_Conversation_AppName);
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
layoutParams.setMarginStart(0);
}
if (mIconContainer != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width =
mIconContainer.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.conversation_content_start);
@@ -186,8 +176,7 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
layoutParams.setMarginStart(marginStart * -1);
}
if (mIcon != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams();
layoutParams.setMarginEnd(0);
}
}
@@ -199,21 +188,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
com.android.internal.R.attr.notificationHeaderTextAppearance,
com.android.internal.R.style.TextAppearance_DeviceDefault_Notification_Info);
mAppNameText.setTextAppearance(textAppearance);
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mAppNameText.getLayoutParams();
final int marginStart = mAppNameText.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_app_name_margin_start);
layoutParams.setMarginStart(marginStart);
}
if (mIconContainer != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIconContainer.getLayoutParams();
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.setMarginStart(0);
}
if (mIcon != null) {
- ViewGroup.MarginLayoutParams layoutParams =
- (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) mIcon.getLayoutParams();
final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_header_icon_margin_end);
layoutParams.setMarginEnd(marginEnd);
@@ -273,12 +259,18 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
@Override
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
+ mExpandButton.setOnClickListener(expandable ? onClickListener : null);
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
}
@Override
+ public void setExpanded(boolean expanded) {
+ mExpandButton.setExpanded(expanded);
+ }
+
+ @Override
public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
if (mAudiblyAlertedIcon != null) {
mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
@@ -296,6 +288,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
@Override
+ public CachingIconView getIcon() {
+ return mIcon;
+ }
+
+ @Override
public int getOriginalIconColor() {
return mIcon.getOriginalIconColor();
}
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 4a8e0d583433..159e05589ff7 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
@@ -397,7 +397,7 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
return;
}
- int tintColor = getNotificationHeader().getOriginalIconColor();
+ int tintColor = getOriginalIconColor();
mSeekBarElapsedTime.setTextColor(tintColor);
mSeekBarTotalTime.setTextColor(tintColor);
mSeekBarTotalTime.setShadowLayer(1.5f, 1.5f, 1.5f, mBackgroundColor);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index 14aab9d62dfd..76ec59e0ec28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -140,13 +140,13 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
}
private void resolveTemplateViews(StatusBarNotification notification) {
- mPicture = (ImageView) mView.findViewById(com.android.internal.R.id.right_icon);
+ mPicture = mView.findViewById(com.android.internal.R.id.right_icon);
if (mPicture != null) {
mPicture.setTag(ImageTransformState.ICON_TAG,
notification.getNotification().getLargeIcon());
}
- mTitle = (TextView) mView.findViewById(com.android.internal.R.id.title);
- mText = (TextView) mView.findViewById(com.android.internal.R.id.text);
+ mTitle = mView.findViewById(com.android.internal.R.id.title);
+ mText = mView.findViewById(com.android.internal.R.id.text);
final View progress = mView.findViewById(com.android.internal.R.id.progress);
if (progress instanceof ProgressBar) {
mProgressBar = (ProgressBar) progress;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index 42f5e389d5a8..6920e3f4a7c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -29,7 +29,6 @@ import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
-import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
@@ -38,7 +37,7 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
-import com.android.internal.widget.ConversationLayout;
+import com.android.internal.widget.CachingIconView;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.TransformState;
@@ -67,8 +66,7 @@ public abstract class NotificationViewWrapper implements TransformableView {
} else if ("messaging".equals(v.getTag())) {
return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
} else if ("conversation".equals(v.getTag())) {
- return new NotificationConversationTemplateViewWrapper(ctx, (ConversationLayout) v,
- row);
+ return new NotificationConversationTemplateViewWrapper(ctx, v, row);
}
Class<? extends Notification.Style> style =
row.getEntry().getSbn().getNotification().getNotificationStyle();
@@ -231,6 +229,9 @@ public abstract class NotificationViewWrapper implements TransformableView {
*/
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {}
+ /** Set the expanded state on the view wrapper */
+ public void setExpanded(boolean expanded) {}
+
/**
* @return the notification header if it exists
*/
@@ -241,7 +242,16 @@ public abstract class NotificationViewWrapper implements TransformableView {
/**
* @return the expand button if it exists
*/
- public @Nullable View getExpandButton() {
+ @Nullable
+ public View getExpandButton() {
+ return null;
+ }
+
+ /**
+ * @return the icon if it exists
+ */
+ @Nullable
+ public CachingIconView getIcon() {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index a396305a49b6..00bccfc1a323 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -31,6 +31,7 @@ import android.widget.RemoteViews;
import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.CachingIconView;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationHeaderUtil;
@@ -318,9 +319,8 @@ public class NotificationChildrenContainer extends ViewGroup {
RemoteViews header = builder.makeNotificationHeader();
if (mNotificationHeader == null) {
mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
- final View expandButton = mNotificationHeader.findViewById(
- com.android.internal.R.id.expand_button);
- expandButton.setVisibility(VISIBLE);
+ mNotificationHeader.findViewById(com.android.internal.R.id.expand_button)
+ .setVisibility(VISIBLE);
mNotificationHeader.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
mNotificationHeader, mContainingNotification);
@@ -361,9 +361,8 @@ public class NotificationChildrenContainer extends ViewGroup {
if (mNotificationHeaderLowPriority == null) {
mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(),
this);
- final View expandButton = mNotificationHeaderLowPriority.findViewById(
- com.android.internal.R.id.expand_button);
- expandButton.setVisibility(VISIBLE);
+ mNotificationHeaderLowPriority.findViewById(com.android.internal.R.id.expand_button)
+ .setVisibility(VISIBLE);
mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
mNotificationHeaderLowPriority, mContainingNotification);
@@ -849,8 +848,8 @@ public class NotificationChildrenContainer extends ViewGroup {
public void setChildrenExpanded(boolean childrenExpanded) {
mChildrenExpanded = childrenExpanded;
updateExpansionStates();
- if (mNotificationHeader != null) {
- mNotificationHeader.setExpanded(childrenExpanded);
+ if (mNotificationHeaderWrapper != null) {
+ mNotificationHeaderWrapper.setExpanded(childrenExpanded);
}
final int count = mAttachedChildren.size();
for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -869,12 +868,12 @@ public class NotificationChildrenContainer extends ViewGroup {
return mContainingNotification;
}
- public NotificationHeaderView getHeaderView() {
- return mNotificationHeader;
+ public NotificationViewWrapper getNotificationViewWrapper() {
+ return mNotificationHeaderWrapper;
}
- public NotificationHeaderView getLowPriorityHeaderView() {
- return mNotificationHeaderLowPriority;
+ public NotificationViewWrapper getLowPriorityViewWrapper() {
+ return mNotificationHeaderWrapperLowPriority;
}
@VisibleForTesting
@@ -1224,16 +1223,15 @@ public class NotificationChildrenContainer extends ViewGroup {
public void setShelfIconVisible(boolean iconVisible) {
if (mNotificationHeaderWrapper != null) {
- NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader();
- if (header != null) {
- header.getIcon().setForceHidden(iconVisible);
+ CachingIconView icon = mNotificationHeaderWrapper.getIcon();
+ if (icon != null) {
+ icon.setForceHidden(iconVisible);
}
}
if (mNotificationHeaderWrapperLowPriority != null) {
- NotificationHeaderView header
- = mNotificationHeaderWrapperLowPriority.getNotificationHeader();
- if (header != null) {
- header.getIcon().setForceHidden(iconVisible);
+ CachingIconView icon = mNotificationHeaderWrapperLowPriority.getIcon();
+ if (icon != null) {
+ icon.setForceHidden(iconVisible);
}
}
}
@@ -1254,12 +1252,14 @@ public class NotificationChildrenContainer extends ViewGroup {
}
}
- public NotificationHeaderView getVisibleHeader() {
- NotificationHeaderView header = mNotificationHeader;
+ /**
+ * @return the view wrapper for the currently showing priority.
+ */
+ public NotificationViewWrapper getVisibleWrapper() {
if (showingAsLowPriority()) {
- header = mNotificationHeaderLowPriority;
+ return mNotificationHeaderWrapperLowPriority;
}
- return header;
+ return mNotificationHeaderWrapper;
}
public void onExpansionChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 93204995c5b0..2a2a0b14a2d2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -835,7 +835,13 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!mShouldDrawNotificationBackground) {
return;
}
-
+ final boolean clearUndershelf = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.SHOW_NEW_NOTIF_DISMISS, 0 /* show background by default */) == 1;
+ if (clearUndershelf) {
+ mBackgroundPaint.setColor(Color.TRANSPARENT);
+ invalidate();
+ return;
+ }
// Interpolate between semi-transparent notification panel background color
// and white AOD separator.
float colorInterpolation = MathUtils.smoothStep(0.4f /* start */, 1f /* end */,
@@ -1340,8 +1346,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
private float getAppearStartPosition() {
if (isHeadsUpTransition()) {
- return mHeadsUpInset
- + getFirstVisibleSection().getFirstVisibleChild().getPinnedHeadsUpHeight();
+ final NotificationSection firstVisibleSection = getFirstVisibleSection();
+ final int pinnedHeight = firstVisibleSection != null
+ ? firstVisibleSection.getFirstVisibleChild().getPinnedHeadsUpHeight()
+ : 0;
+ return mHeadsUpInset + pinnedHeight;
}
return getMinExpansionHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 76c5baf6e9f6..3622f1cd3952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
import com.android.systemui.util.UserAwareController;
+import com.android.systemui.util.settings.SecureSettings;
import java.util.ArrayList;
import java.util.Objects;
@@ -60,6 +61,7 @@ public class AutoTileManager implements UserAwareController {
protected final Context mContext;
protected final QSTileHost mHost;
protected final Handler mHandler;
+ protected final SecureSettings mSecureSettings;
protected final AutoAddTracker mAutoTracker;
private final HotspotController mHotspotController;
private final DataSaverController mDataSaverController;
@@ -71,6 +73,7 @@ public class AutoTileManager implements UserAwareController {
public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
QSTileHost host,
@Background Handler handler,
+ SecureSettings secureSettings,
HotspotController hotspotController,
DataSaverController dataSaverController,
ManagedProfileController managedProfileController,
@@ -78,6 +81,7 @@ public class AutoTileManager implements UserAwareController {
CastController castController) {
mContext = context;
mHost = host;
+ mSecureSettings = secureSettings;
mCurrentUser = mHost.getUserContext().getUser();
mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
mHandler = handler;
@@ -170,7 +174,7 @@ public class AutoTileManager implements UserAwareController {
String spec = split[1];
// Populate all the settings. As they may not have been added in other users
AutoAddSetting s = new AutoAddSetting(
- mContext, mHandler, setting, mCurrentUser.getIdentifier(), spec);
+ mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec);
mAutoAddSettingList.add(s);
} else {
Log.w(TAG, "Malformed item in array: " + tile);
@@ -321,13 +325,13 @@ public class AutoTileManager implements UserAwareController {
private final String mSpec;
AutoAddSetting(
- Context context,
+ SecureSettings secureSettings,
Handler handler,
String setting,
int userId,
String tileSpec
) {
- super(context, handler, setting, userId);
+ super(secureSettings, handler, setting, userId);
mSpec = tileSpec;
}
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 af6ac223ada1..2767b7e1627d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -38,9 +38,9 @@ import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.keyguard.dagger.RootView;
import com.android.systemui.DejankUtils;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 1fdf631a858d..5c225e5a3529 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -86,9 +86,14 @@ public class KeyguardClockPositionAlgorithm {
private int mMaxShadeBottom;
/**
- * Minimum distance from the status bar.
+ * Recommended distance from the status bar without the lock icon.
*/
- private int mContainerTopPadding;
+ private int mContainerTopPaddingWithoutLockIcon;
+
+ /**
+ * Recommended distance from the status bar with the lock icon.
+ */
+ private int mContainerTopPaddingWithLockIcon;
/**
* @see NotificationPanelViewController#getExpandedFraction()
@@ -131,24 +136,31 @@ public class KeyguardClockPositionAlgorithm {
public void loadDimens(Resources res) {
mClockNotificationsMargin = res.getDimensionPixelSize(
R.dimen.keyguard_clock_notifications_margin);
+
+ mContainerTopPaddingWithoutLockIcon =
+ res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) / 2;
// Consider the lock icon when determining the minimum top padding between the status bar
// and top of the clock.
- mContainerTopPadding = Math.max(res.getDimensionPixelSize(
- R.dimen.keyguard_clock_top_margin),
- res.getDimensionPixelSize(R.dimen.keyguard_lock_height)
- + res.getDimensionPixelSize(R.dimen.keyguard_lock_padding)
- + res.getDimensionPixelSize(R.dimen.keyguard_clock_lock_margin));
+ mContainerTopPaddingWithLockIcon =
+ Math.max(res.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin),
+ res.getDimensionPixelSize(R.dimen.keyguard_lock_height)
+ + res.getDimensionPixelSize(R.dimen.keyguard_lock_padding)
+ + res.getDimensionPixelSize(R.dimen.keyguard_clock_lock_margin));
mBurnInPreventionOffsetX = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y);
}
- public void setup(int minTopMargin, int maxShadeBottom, int notificationStackHeight,
+ /**
+ * Sets up algorithm values.
+ */
+ public void setup(int statusBarMinHeight, int maxShadeBottom, int notificationStackHeight,
float panelExpansion, int parentHeight, int keyguardStatusHeight, int clockPreferredY,
boolean hasCustomClock, boolean hasVisibleNotifs, float dark, float emptyDragAmount,
- boolean bypassEnabled, int unlockedStackScrollerPadding) {
- mMinTopMargin = minTopMargin + mContainerTopPadding;
+ boolean bypassEnabled, int unlockedStackScrollerPadding, boolean udfpsEnrolled) {
+ mMinTopMargin = statusBarMinHeight + (udfpsEnrolled ? mContainerTopPaddingWithoutLockIcon :
+ mContainerTopPaddingWithLockIcon);
mMaxShadeBottom = maxShadeBottom;
mNotificationStackHeight = notificationStackHeight;
mPanelExpansion = panelExpansion;
@@ -175,8 +187,8 @@ public class KeyguardClockPositionAlgorithm {
}
/**
- * Update lock screen mode for testing different layouts
- */
+ * Update lock screen mode for testing different layouts
+ */
public void onLockScreenModeChanged(int mode) {
mLockScreenMode = mode;
}
@@ -241,6 +253,13 @@ public class KeyguardClockPositionAlgorithm {
clockYDark = MathUtils.lerp(clockYBouncer, clockYDark, shadeExpansion);
float darkAmount = mBypassEnabled && !mHasCustomClock ? 1.0f : mDarkAmount;
+
+ // TODO(b/12836565) - prototyping only adjustment
+ if (mLockScreenMode != KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL) {
+ // This will keep the clock at the top for AOD
+ return (int) (clockY + burnInPreventionOffsetY() + mEmptyDragAmount);
+ }
+
return (int) (MathUtils.lerp(clockY, clockYDark, darkAmount) + mEmptyDragAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
index 77ae059a0cb9..289ff71dcb46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.view.View.GONE;
+
import static com.android.systemui.statusbar.phone.LockIcon.STATE_BIOMETRICS_ERROR;
import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCKED;
import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCK_OPEN;
@@ -502,6 +504,11 @@ public class LockscreenLockIconController {
* @return true if the visibility changed
*/
private boolean updateIconVisibility() {
+ if (mKeyguardUpdateMonitor.isUdfpsEnrolled()) {
+ boolean changed = mLockIcon.getVisibility() == GONE;
+ mLockIcon.setVisibility(GONE);
+ return changed;
+ }
boolean onAodOrDocked = mStatusBarStateController.isDozing() || mDocked;
boolean invisible = onAodOrDocked || mWakeAndUnlockRunning || mShowingLaunchAffordance;
boolean fingerprintOrBypass = mFingerprintUnlock
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index d1c83555c062..ac91b7050ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -19,7 +19,6 @@ import com.android.internal.util.ContrastColorUtil;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
@@ -32,10 +31,14 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.ArrayList;
import java.util.List;
@@ -91,9 +94,7 @@ public class NotificationIconAreaController implements
private boolean mAnimationsEnabled;
private int mAodIconTint;
- private boolean mFullyHidden;
private boolean mAodIconsVisible;
- private boolean mIsPulsing;
private boolean mShowLowPriority = true;
@VisibleForTesting
@@ -158,22 +159,33 @@ public class NotificationIconAreaController implements
}
/**
- * Called by the StatusBar. The StatusBar passes the NotificationIconContainer which holds
- * the aod icons.
+ * Called by the Keyguard*ViewController whose view contains the aod icons.
*/
- void setupAodIcons(@NonNull NotificationIconContainer aodIcons) {
+ public void setupAodIcons(@NonNull NotificationIconContainer aodIcons,
+ int lockScreenMode) {
boolean changed = mAodIcons != null;
if (changed) {
mAodIcons.setAnimationsEnabled(false);
mAodIcons.removeAllViews();
}
mAodIcons = aodIcons;
- mAodIcons.setOnLockScreen(true);
+ mAodIcons.setOnLockScreen(true, lockScreenMode);
updateAodIconsVisibility(false /* animate */);
updateAnimations();
if (changed) {
updateAodNotificationIcons();
}
+ updateIconLayoutParams(mContext);
+ }
+
+ /**
+ * Update position of the view, with optional animation
+ */
+ public void updatePosition(int x, AnimationProperties props, boolean animate) {
+ if (mAodIcons != null) {
+ PropertyAnimator.setProperty(mAodIcons, AnimatableProperty.TRANSLATION_X, x, props,
+ animate);
+ }
}
public void setupShelf(NotificationShelfController notificationShelfController) {
@@ -182,23 +194,31 @@ public class NotificationIconAreaController implements
}
public void onDensityOrFontScaleChanged(Context context) {
+ updateIconLayoutParams(context);
+ }
+
+ private void updateIconLayoutParams(Context context) {
reloadDimens(context);
final FrameLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
View child = mNotificationIcons.getChildAt(i);
child.setLayoutParams(params);
}
- for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
- View child = mShelfIcons.getChildAt(i);
- child.setLayoutParams(params);
- }
for (int i = 0; i < mCenteredIcon.getChildCount(); i++) {
View child = mCenteredIcon.getChildAt(i);
child.setLayoutParams(params);
}
- for (int i = 0; i < mAodIcons.getChildCount(); i++) {
- View child = mAodIcons.getChildAt(i);
- child.setLayoutParams(params);
+ if (mShelfIcons != null) {
+ for (int i = 0; i < mShelfIcons.getChildCount(); i++) {
+ View child = mShelfIcons.getChildAt(i);
+ child.setLayoutParams(params);
+ }
+ }
+ if (mAodIcons != null) {
+ for (int i = 0; i < mAodIcons.getChildCount(); i++) {
+ View child = mAodIcons.getChildAt(i);
+ child.setLayoutParams(params);
+ }
}
}
@@ -299,7 +319,8 @@ public class NotificationIconAreaController implements
|| !entry.isPulseSuppressed())) {
return false;
}
- if (mBubblesOptional.isPresent() && mBubblesOptional.get().isBubbleExpanded(entry)) {
+ if (mBubblesOptional.isPresent()
+ && mBubblesOptional.get().isBubbleExpanded(entry.getKey())) {
return false;
}
return true;
@@ -358,6 +379,9 @@ public class NotificationIconAreaController implements
}
public void updateAodNotificationIcons() {
+ if (mAodIcons == null) {
+ return;
+ }
updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
false /* showAmbient */,
true /* showLowPriority */,
@@ -546,6 +570,9 @@ public class NotificationIconAreaController implements
@Override
public void onDozingChanged(boolean isDozing) {
+ if (mAodIcons == null) {
+ return;
+ }
boolean animate = mDozeParameters.getAlwaysOn()
&& !mDozeParameters.getDisplayNeedsBlanking();
mAodIcons.setDozing(isDozing, animate, 0);
@@ -564,7 +591,9 @@ public class NotificationIconAreaController implements
private void updateAnimations() {
boolean inShade = mStatusBarStateController.getState() == StatusBarState.SHADE;
- mAodIcons.setAnimationsEnabled(mAnimationsEnabled && !inShade);
+ if (mAodIcons != null) {
+ mAodIcons.setAnimationsEnabled(mAnimationsEnabled && !inShade);
+ }
mCenteredIcon.setAnimationsEnabled(mAnimationsEnabled && inShade);
mNotificationIcons.setAnimationsEnabled(mAnimationsEnabled && inShade);
}
@@ -575,6 +604,9 @@ public class NotificationIconAreaController implements
}
public void appearAodIcons() {
+ if (mAodIcons == null) {
+ return;
+ }
if (mDozeParameters.shouldControlScreenOff()) {
mAodIcons.setTranslationY(-mAodIconAppearTranslation);
mAodIcons.setAlpha(0);
@@ -637,6 +669,9 @@ public class NotificationIconAreaController implements
}
private void updateAodIconsVisibility(boolean animate) {
+ if (mAodIcons == null) {
+ return;
+ }
boolean visible = mBypassController.getBypassEnabled()
|| mWakeUpCoordinator.getNotificationsFullyHidden();
if (mStatusBarStateController.getState() != StatusBarState.KEYGUARD) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index bf858520c338..9561851ab28b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -34,6 +34,7 @@ import android.view.animation.Interpolator;
import androidx.collection.ArrayMap;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
@@ -148,6 +149,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
private float mActualPaddingStart = NO_VALUE;
private boolean mDozing;
private boolean mOnLockScreen;
+ private int mLockScreenMode = KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
private boolean mChangingViewPositions;
private int mAddAnimationStartIndex = -1;
private int mCannedAnimationStartIndex = -1;
@@ -453,7 +455,8 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
mFirstVisibleIconState = mIconStates.get(getChildAt(0));
}
- boolean center = mOnLockScreen;
+ boolean center = mOnLockScreen
+ && mLockScreenMode == KeyguardUpdateMonitor.LOCK_SCREEN_MODE_NORMAL;
if (center && translationX < getLayoutEnd()) {
float initialTranslation =
mFirstVisibleIconState == null ? 0 : mFirstVisibleIconState.xTranslation;
@@ -686,8 +689,13 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}
}
- public void setOnLockScreen(boolean onLockScreen) {
+ /**
+ * Set whether the device is on the lockscreen and which lockscreen mode the device is
+ * configured to. Depending on these values, the layout of the AOD icons change.
+ */
+ public void setOnLockScreen(boolean onLockScreen, int lockScreenMode) {
mOnLockScreen = onLockScreen;
+ mLockScreenMode = lockScreenMode;
}
public class IconState extends ViewState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 86d4ac1cb443..231d157322ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -70,12 +70,14 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardClockSwitchController;
import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardStatusViewController;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.DejankUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthController;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
@@ -202,9 +204,6 @@ public class NotificationPanelViewController extends PanelViewController {
private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
private static final Rect EMPTY_RECT = new Rect();
- private static final AnimationProperties
- CLOCK_ANIMATION_PROPERTIES =
- new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
private final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT = AnimatableProperty.from(
"KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
(notificationPanelView, aFloat) -> setKeyguardHeadsUpShowingAmount(aFloat),
@@ -265,6 +264,7 @@ public class NotificationPanelViewController extends PanelViewController {
private final KeyguardBypassController mKeyguardBypassController;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final ConversationNotificationManager mConversationNotificationManager;
+ private final AuthController mAuthController;
private final MediaHierarchyManager mMediaHierarchyManager;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@@ -280,7 +280,7 @@ public class NotificationPanelViewController extends PanelViewController {
private ViewGroup mBigClockContainer;
private QS mQs;
private FrameLayout mQsFrame;
- private KeyguardStatusView mKeyguardStatusView;
+ private KeyguardStatusViewController mKeyguardStatusViewController;
private View mQsNavbarScrim;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
private boolean mAnimateNextPositionUpdate;
@@ -344,7 +344,6 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mIsLaunchTransitionRunning;
private Runnable mLaunchAnimationEndRunnable;
private boolean mOnlyAffordanceInThisMotion;
- private boolean mKeyguardStatusViewAnimating;
private ValueAnimator mQsSizeChangeAnimator;
private boolean mQsScrimEnabled = true;
@@ -444,7 +443,6 @@ public class NotificationPanelViewController extends PanelViewController {
private KeyguardIndicationController mKeyguardIndicationController;
private Consumer<Boolean> mAffordanceLaunchListener;
private int mShelfHeight;
- private Runnable mOnReinflationListener;
private int mDarkIconSize;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
@@ -520,7 +518,8 @@ public class NotificationPanelViewController extends PanelViewController {
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
NotificationGroupManagerLegacy groupManager,
- NotificationIconAreaController notificationIconAreaController) {
+ NotificationIconAreaController notificationIconAreaController,
+ AuthController authController) {
super(view, falsingManager, dozeLog, keyguardStateController,
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -585,6 +584,7 @@ public class NotificationPanelViewController extends PanelViewController {
mLockscreenUserManager = notificationLockscreenUserManager;
mEntryManager = notificationEntryManager;
mConversationNotificationManager = conversationNotificationManager;
+ mAuthController = authController;
mView.setBackgroundColor(Color.TRANSPARENT);
OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
@@ -606,16 +606,8 @@ public class NotificationPanelViewController extends PanelViewController {
private void onFinishInflate() {
loadDimens();
mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
- mKeyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
-
- KeyguardClockSwitchController keyguardClockSwitchController =
- mKeyguardStatusViewComponentFactory
- .build(mKeyguardStatusView)
- .getKeyguardClockSwitchController();
- keyguardClockSwitchController.init();
mBigClockContainer = mView.findViewById(R.id.big_clock_container);
- keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
-
+ updateViewControllers(mView.findViewById(R.id.keyguard_status_view));
mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
NotificationStackScrollLayout stackScrollLayout = mView.findViewById(
R.id.notification_stack_scroller);
@@ -689,11 +681,24 @@ public class NotificationPanelViewController extends PanelViewController {
R.dimen.heads_up_status_bar_padding);
}
+ private void updateViewControllers(KeyguardStatusView keyguardStatusView) {
+ // Re-associate the KeyguardStatusViewController
+ KeyguardStatusViewComponent statusViewComponent =
+ mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
+ mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
+ mKeyguardStatusViewController.init();
+
+ // Re-associate the clock container with the keyguard clock switch.
+ KeyguardClockSwitchController keyguardClockSwitchController =
+ statusViewComponent.getKeyguardClockSwitchController();
+ keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+ }
+
/**
* Returns if there's a custom clock being presented.
*/
public boolean hasCustomClock() {
- return mKeyguardStatusView.hasCustomClock();
+ return mKeyguardStatusViewController.hasCustomClock();
}
private void setStatusBar(StatusBar bar) {
@@ -730,21 +735,16 @@ public class NotificationPanelViewController extends PanelViewController {
private void reInflateViews() {
// Re-inflate the status view group.
- int index = mView.indexOfChild(mKeyguardStatusView);
- mView.removeView(mKeyguardStatusView);
- mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController.injectable(
+ KeyguardStatusView keyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
+ int index = mView.indexOfChild(keyguardStatusView);
+ mView.removeView(keyguardStatusView);
+ keyguardStatusView = (KeyguardStatusView) mInjectionInflationController.injectable(
LayoutInflater.from(mView.getContext())).inflate(
R.layout.keyguard_status_view, mView, false);
- mView.addView(mKeyguardStatusView, index);
+ mView.addView(keyguardStatusView, index);
- // Re-associate the clock container with the keyguard clock switch.
mBigClockContainer.removeAllViews();
- KeyguardClockSwitchController keyguardClockSwitchController =
- mKeyguardStatusViewComponentFactory
- .build(mKeyguardStatusView)
- .getKeyguardClockSwitchController();
- keyguardClockSwitchController.init();
- keyguardClockSwitchController.setBigClockContainer(mBigClockContainer);
+ updateViewControllers(keyguardStatusView);
// Update keyguard bottom area
index = mView.indexOfChild(mKeyguardBottomArea);
@@ -764,11 +764,12 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusBar.onThemeChanged();
}
- setKeyguardStatusViewVisibility(mBarState, false, false);
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ mBarState,
+ false,
+ false,
+ mBarState);
setKeyguardBottomAreaVisibility(mBarState, false);
- if (mOnReinflationListener != null) {
- mOnReinflationListener.run();
- }
}
private void initBottomArea() {
@@ -858,23 +859,24 @@ public class NotificationPanelViewController extends PanelViewController {
} else {
int totalHeight = mView.getHeight();
int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
- int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
+ int clockPreferredY = mKeyguardStatusViewController.getClockPreferredY(totalHeight);
boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
final boolean hasVisibleNotifications = !bypassEnabled
&& mNotificationStackScrollLayoutController.getVisibleNotificationCount() != 0;
- mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
+ mKeyguardStatusViewController.setHasVisibleNotifications(hasVisibleNotifications);
mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
mNotificationStackScrollLayoutController.getIntrinsicContentHeight(),
getExpandedFraction(),
- totalHeight, (int) (mKeyguardStatusView.getHeight() - mShelfHeight / 2.0f
- - mDarkIconSize / 2.0f), clockPreferredY, hasCustomClock(),
+ totalHeight,
+ (int) (mKeyguardStatusViewController.getHeight()
+ - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
+ clockPreferredY, hasCustomClock(),
hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
- bypassEnabled, getUnlockedStackScrollerPadding());
+ bypassEnabled, getUnlockedStackScrollerPadding(),
+ mUpdateMonitor.isUdfpsEnrolled());
mClockPositionAlgorithm.run(mClockPositionResult);
- PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
- mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
- PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
- mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
+ mKeyguardStatusViewController.updatePosition(
+ mClockPositionResult.clockX, mClockPositionResult.clockY, animateClock);
updateNotificationTranslucency();
updateClock();
stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
@@ -910,7 +912,14 @@ public class NotificationPanelViewController extends PanelViewController {
float availableSpace =
mNotificationStackScrollLayoutController.getHeight() - minPadding - shelfSize
- Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
- - mKeyguardStatusView.getLogoutButtonHeight();
+ - mKeyguardStatusViewController.getLogoutButtonHeight();
+
+ if (mUpdateMonitor.isUdfpsEnrolled()) {
+ availableSpace = mNotificationStackScrollLayoutController.getHeight()
+ - minPadding - shelfSize
+ - (mStatusBar.getDisplayHeight() - mAuthController.getUdfpsRegion().top);
+ }
+
int count = 0;
ExpandableView previousView = null;
for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
@@ -1005,9 +1014,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void updateClock() {
- if (!mKeyguardStatusViewAnimating) {
- mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
- }
+ mKeyguardStatusViewController.setAlpha(mClockPositionResult.clockAlpha);
}
public void animateToFullShade(long delay) {
@@ -1605,29 +1612,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusViewAnimating = false;
- mKeyguardStatusView.setVisibility(View.INVISIBLE);
- }
- };
-
- private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusViewAnimating = false;
- mKeyguardStatusView.setVisibility(View.GONE);
- }
- };
-
- private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusViewAnimating = false;
- }
- };
-
private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
@Override
public void run() {
@@ -1705,46 +1689,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
- boolean goingToFullShade) {
- mKeyguardStatusView.animate().cancel();
- mKeyguardStatusViewAnimating = false;
- if ((!keyguardFadingAway && mBarState == KEYGUARD
- && statusBarState != KEYGUARD) || goingToFullShade) {
- mKeyguardStatusViewAnimating = true;
- mKeyguardStatusView.animate().alpha(0f).setStartDelay(0).setDuration(
- 160).setInterpolator(Interpolators.ALPHA_OUT).withEndAction(
- mAnimateKeyguardStatusViewGoneEndRunnable);
- if (keyguardFadingAway) {
- mKeyguardStatusView.animate().setStartDelay(
- mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
- mKeyguardStateController.getShortenedFadingAwayDuration()).start();
- }
- } else if (mBarState == StatusBarState.SHADE_LOCKED
- && statusBarState == KEYGUARD) {
- mKeyguardStatusView.setVisibility(View.VISIBLE);
- mKeyguardStatusViewAnimating = true;
- mKeyguardStatusView.setAlpha(0f);
- mKeyguardStatusView.animate().alpha(1f).setStartDelay(0).setDuration(
- 320).setInterpolator(Interpolators.ALPHA_IN).withEndAction(
- mAnimateKeyguardStatusViewVisibleEndRunnable);
- } else if (statusBarState == KEYGUARD) {
- if (keyguardFadingAway) {
- mKeyguardStatusViewAnimating = true;
- mKeyguardStatusView.animate().alpha(0).translationYBy(
- -getHeight() * 0.05f).setInterpolator(
- Interpolators.FAST_OUT_LINEAR_IN).setDuration(125).setStartDelay(
- 0).withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable).start();
- } else {
- mKeyguardStatusView.setVisibility(View.VISIBLE);
- mKeyguardStatusView.setAlpha(1f);
- }
- } else {
- mKeyguardStatusView.setVisibility(View.GONE);
- mKeyguardStatusView.setAlpha(1f);
- }
- }
-
private void updateQsState() {
mNotificationStackScrollLayoutController.setQsExpanded(mQsExpanded);
mNotificationStackScrollLayoutController.setScrollingEnabled(
@@ -2075,7 +2019,7 @@ public class NotificationPanelViewController extends PanelViewController {
private int getMaxPanelHeightBypass() {
int position =
mClockPositionAlgorithm.getExpandedClockPosition()
- + mKeyguardStatusView.getHeight();
+ + mKeyguardStatusViewController.getHeight();
if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() != 0) {
position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
}
@@ -2156,7 +2100,7 @@ public class NotificationPanelViewController extends PanelViewController {
int
minKeyguardPanelBottom =
mClockPositionAlgorithm.getExpandedClockPosition()
- + mKeyguardStatusView.getHeight()
+ + mKeyguardStatusViewController.getHeight()
+ mNotificationStackScrollLayoutController.getIntrinsicContentHeight();
return Math.max(maxHeight, minKeyguardPanelBottom);
} else {
@@ -2604,7 +2548,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
public void onScreenTurningOn() {
- mKeyguardStatusView.dozeTimeTick();
+ mKeyguardStatusViewController.dozeTimeTick();
}
@Override
@@ -2989,7 +2933,6 @@ public class NotificationPanelViewController extends PanelViewController {
mAnimateNextPositionUpdate = false;
}
mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
- mKeyguardStatusView.setPulsing(pulsing);
}
public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
@@ -3001,14 +2944,14 @@ public class NotificationPanelViewController extends PanelViewController {
public void dozeTimeTick() {
mKeyguardBottomArea.dozeTimeTick();
- mKeyguardStatusView.dozeTimeTick();
+ mKeyguardStatusViewController.dozeTimeTick();
if (mInterpolatedDarkAmount > 0) {
positionClockAndNotifications();
}
}
public void setStatusAccessibilityImportance(int mode) {
- mKeyguardStatusView.setImportantForAccessibility(mode);
+ mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
}
/**
@@ -3068,8 +3011,11 @@ public class NotificationPanelViewController extends PanelViewController {
* security view of the bouncer.
*/
public void onBouncerPreHideAnimation() {
- setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
- false /* goingToFullShade */);
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ mBarState,
+ true /* keyguardFadingAway */,
+ false /* goingToFullShade */,
+ mBarState);
}
/**
@@ -3164,10 +3110,6 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardIndicationController.showTransientIndication(id);
}
- public void setOnReinflationListener(Runnable onReinflationListener) {
- mOnReinflationListener = onReinflationListener;
- }
-
public void setAlpha(float alpha) {
mView.setAlpha(alpha);
}
@@ -3639,7 +3581,11 @@ public class NotificationPanelViewController extends PanelViewController {
int oldState = mBarState;
boolean keyguardShowing = statusBarState == KEYGUARD;
- setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
+ mKeyguardStatusViewController.setKeyguardStatusViewVisibility(
+ statusBarState,
+ keyguardFadingAway,
+ goingToFullShade,
+ mBarState);
setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
mBarState = statusBarState;
@@ -3690,7 +3636,7 @@ public class NotificationPanelViewController extends PanelViewController {
public void onDozeAmountChanged(float linearAmount, float amount) {
mInterpolatedDarkAmount = amount;
mLinearDarkAmount = linearAmount;
- mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
+ mKeyguardStatusViewController.setDarkAmount(mInterpolatedDarkAmount);
mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
positionClockAndNotifications();
}
@@ -3736,9 +3682,10 @@ public class NotificationPanelViewController extends PanelViewController {
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
// Update Clock Pivot
- mKeyguardStatusView.setPivotX(mView.getWidth() / 2);
- mKeyguardStatusView.setPivotY(
- (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mKeyguardStatusView.getClockTextSize());
+ mKeyguardStatusViewController.setPivotX(mView.getWidth() / 2);
+ mKeyguardStatusViewController.setPivotY(
+ (FONT_HEIGHT - CAP_HEIGHT) / 2048f
+ * mKeyguardStatusViewController.getClockTextSize());
// Calculate quick setting heights.
int oldMaxHeight = mQsMaxExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 625489659bd4..38e1d0303a13 100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -52,6 +52,7 @@ import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.privacy.PrivacyItem;
import com.android.systemui.privacy.PrivacyItemController;
import com.android.systemui.privacy.PrivacyType;
+import com.android.systemui.privacy.logging.PrivacyLogger;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.RotationLockTile;
import com.android.systemui.screenrecord.RecordingController;
@@ -147,6 +148,7 @@ public class PhoneStatusBarPolicy
private final SensorPrivacyController mSensorPrivacyController;
private final RecordingController mRecordingController;
private final RingerModeTracker mRingerModeTracker;
+ private final PrivacyLogger mPrivacyLogger;
private boolean mZenVisible;
private boolean mVolumeVisible;
@@ -174,7 +176,8 @@ public class PhoneStatusBarPolicy
@Nullable TelecomManager telecomManager, @DisplayId int displayId,
@Main SharedPreferences sharedPreferences, DateFormatUtil dateFormatUtil,
RingerModeTracker ringerModeTracker,
- PrivacyItemController privacyItemController) {
+ PrivacyItemController privacyItemController,
+ PrivacyLogger privacyLogger) {
mIconController = iconController;
mCommandQueue = commandQueue;
mBroadcastDispatcher = broadcastDispatcher;
@@ -199,6 +202,7 @@ public class PhoneStatusBarPolicy
mUiBgExecutor = uiBgExecutor;
mTelecomManager = telecomManager;
mRingerModeTracker = ringerModeTracker;
+ mPrivacyLogger = privacyLogger;
mSlotCast = resources.getString(com.android.internal.R.string.status_bar_cast);
mSlotHotspot = resources.getString(com.android.internal.R.string.status_bar_hotspot);
@@ -673,14 +677,19 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotCamera, showCamera);
mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
- if (mPrivacyItemController.getAllIndicatorsAvailable()) {
+ if (mPrivacyItemController.getAllIndicatorsAvailable()
+ || mPrivacyItemController.getLocationAvailable()) {
mIconController.setIconVisibility(mSlotLocation, showLocation);
}
+ mPrivacyLogger.logStatusBarIconsVisible(showCamera, showMicrophone, showLocation);
}
@Override
public void onLocationActiveChanged(boolean active) {
- if (!mPrivacyItemController.getAllIndicatorsAvailable()) updateLocationFromController();
+ if (!mPrivacyItemController.getAllIndicatorsAvailable()
+ && !mPrivacyItemController.getLocationAvailable()) {
+ updateLocationFromController();
+ }
}
// Updates the status view based on the current state of location requests.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index af2f3e55c9ce..a930a897c2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -22,13 +22,13 @@ import android.view.ViewTreeObserver;
import android.view.WindowManager;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.wm.shell.bubbles.Bubbles;
import java.util.ArrayList;
import java.util.Optional;
@@ -51,7 +51,7 @@ public class ShadeControllerImpl implements ShadeController {
private final int mDisplayId;
protected final Lazy<StatusBar> mStatusBarLazy;
private final Lazy<AssistManager> mAssistManagerLazy;
- private final Optional<Lazy<Bubbles>> mBubblesOptional;
+ private final Optional<Bubbles> mBubblesOptional;
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
@@ -64,7 +64,7 @@ public class ShadeControllerImpl implements ShadeController {
WindowManager windowManager,
Lazy<StatusBar> statusBarLazy,
Lazy<AssistManager> assistManagerLazy,
- Optional<Lazy<Bubbles>> bubblesOptional
+ Optional<Bubbles> bubblesOptional
) {
mCommandQueue = commandQueue;
mStatusBarStateController = statusBarStateController;
@@ -135,7 +135,7 @@ public class ShadeControllerImpl implements ShadeController {
getStatusBar().getNotificationShadeWindowViewController().cancelExpandHelper();
getStatusBarView().collapsePanel(true /* animate */, delayed, speedUpFactor);
} else if (mBubblesOptional.isPresent()) {
- mBubblesOptional.get().get().collapseStack();
+ mBubblesOptional.get().collapseStack();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 28a22cd6a194..6012ae3f2da6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -144,8 +144,6 @@ import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.BubbleController;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -172,7 +170,7 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
-import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.WindowManagerWrapper;
@@ -229,6 +227,8 @@ import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.volume.VolumeComponent;
+import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.FileDescriptor;
@@ -408,7 +408,7 @@ public class StatusBar extends SystemUI implements DemoMode,
protected NotificationPanelViewController mNotificationPanelViewController;
// settings
- private QSPanel mQSPanel;
+ private QSPanelController mQSPanelController;
KeyguardIndicationController mKeyguardIndicationController;
@@ -648,8 +648,9 @@ public class StatusBar extends SystemUI implements DemoMode,
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
+ private final Optional<BubblesManager> mBubblesManagerOptional;
private final Optional<Bubbles> mBubblesOptional;
- private final BubbleController.BubbleExpandListener mBubbleExpandListener;
+ private final Bubbles.BubbleExpandListener mBubbleExpandListener;
private ActivityIntentHelper mActivityIntentHelper;
private NotificationStackScrollLayoutController mStackScrollerController;
@@ -697,6 +698,7 @@ public class StatusBar extends SystemUI implements DemoMode,
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper,
+ Optional<BubblesManager> bubblesManagerOptional,
Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
@@ -776,6 +778,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
mVibratorHelper = vibratorHelper;
+ mBubblesManagerOptional = bubblesManagerOptional;
mBubblesOptional = bubblesOptional;
mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
@@ -1035,10 +1038,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mStackScrollerController.getNotificationListContainer();
mNotificationLogger.setUpWithContainer(notifListContainer);
- updateAodIconArea();
inflateShelf();
mNotificationIconAreaController.setupShelf(mNotificationShelfController);
- mNotificationPanelViewController.setOnReinflationListener(this::updateAodIconArea);
mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
@@ -1143,8 +1144,8 @@ public class StatusBar extends SystemUI implements DemoMode,
ScrimView scrimBehind = mNotificationShadeWindowView.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = mNotificationShadeWindowView.findViewById(R.id.scrim_in_front);
- ScrimView scrimForBubble = mBubblesOptional.isPresent()
- ? mBubblesOptional.get().getScrimForBubble() : null;
+ ScrimView scrimForBubble = mBubblesManagerOptional.isPresent()
+ ? mBubblesManagerOptional.get().getScrimForBubble() : null;
mScrimController.setScrimVisibleListener(scrimsVisible -> {
mNotificationShadeWindowController.setScrimsVisibility(scrimsVisible);
@@ -1200,8 +1201,8 @@ public class StatusBar extends SystemUI implements DemoMode,
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
- mQSPanel = ((QSFragment) qs).getQsPanel();
- mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
+ mQSPanelController = ((QSFragment) qs).getQSPanelController();
+ mQSPanelController.setBrightnessMirror(mBrightnessMirrorController);
}
});
}
@@ -1270,12 +1271,6 @@ public class StatusBar extends SystemUI implements DemoMode,
ThreadedRenderer.overrideProperty("ambientRatio", String.valueOf(1.5f));
}
- private void updateAodIconArea() {
- mNotificationIconAreaController.setupAodIcons(
- getNotificationShadeWindowView()
- .findViewById(R.id.clock_notification_icon_container));
- }
-
@NonNull
@Override
public Lifecycle getLifecycle() {
@@ -1593,19 +1588,19 @@ public class StatusBar extends SystemUI implements DemoMode,
}
public void addQsTile(ComponentName tile) {
- if (mQSPanel != null && mQSPanel.getHost() != null) {
- mQSPanel.getHost().addTile(tile);
+ if (mQSPanelController != null && mQSPanelController.getHost() != null) {
+ mQSPanelController.getHost().addTile(tile);
}
}
public void remQsTile(ComponentName tile) {
- if (mQSPanel != null && mQSPanel.getHost() != null) {
- mQSPanel.getHost().removeTile(tile);
+ if (mQSPanelController != null && mQSPanelController.getHost() != null) {
+ mQSPanelController.getHost().removeTile(tile);
}
}
public void clickTile(ComponentName tile) {
- mQSPanel.clickTile(tile);
+ mQSPanelController.clickTile(tile);
}
/**
@@ -2197,7 +2192,7 @@ public class StatusBar extends SystemUI implements DemoMode,
if (!mUserSetup) return;
if (subPanel != null) {
- mQSPanel.openDetails(subPanel);
+ mQSPanelController.openDetails(subPanel);
}
mNotificationPanelViewController.expandWithQs();
@@ -2845,7 +2840,7 @@ public class StatusBar extends SystemUI implements DemoMode,
resetUserExpandedStates();
}
else if (DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG.equals(action)) {
- mQSPanel.showDeviceMonitoringDialog();
+ mQSPanelController.showDeviceMonitoringDialog();
}
Trace.endSection();
}
@@ -2936,8 +2931,8 @@ public class StatusBar extends SystemUI implements DemoMode,
*/
void updateResources() {
// Update the quick setting tiles
- if (mQSPanel != null) {
- mQSPanel.updateResources();
+ if (mQSPanelController != null) {
+ mQSPanelController.updateResources();
}
if (mStatusBarWindowController != null) {
@@ -3402,8 +3397,8 @@ public class StatusBar extends SystemUI implements DemoMode,
// Keyguard state has changed, but QS is not listening anymore. Make sure to update the tile
// visibilities so next time we open the panel we know the correct height already.
- if (mQSPanel != null) {
- mQSPanel.refreshAllTiles();
+ if (mQSPanelController != null) {
+ mQSPanelController.refreshAllTiles();
}
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
@@ -3988,7 +3983,8 @@ public class StatusBar extends SystemUI implements DemoMode,
PackageManager pm = mContext.getPackageManager();
ResolveInfo resolveInfo = pm.resolveActivity(emergencyIntent, /*flags=*/0);
if (resolveInfo == null) {
- Log.wtf(TAG, "Couldn't find an app to process the emergency intent.");
+ // TODO(b/171084088) Upgrade log to wtf when we have default app in main branch.
+ Log.d(TAG, "Couldn't find an app to process the emergency intent.");
return;
}
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 256ee2081f41..acca953629c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -48,7 +48,6 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.EventLogTags;
import com.android.systemui.assist.AssistManager;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -76,6 +75,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.wmshell.BubblesManager;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -104,7 +104,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
- private final Optional<Bubbles> mBubblesOptional;
+ private final Optional<BubblesManager> mBubblesManagerOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final GroupMembershipManager mGroupMembershipManager;
@@ -142,7 +142,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
- Optional<Bubbles> bubblesOptional,
+ Optional<BubblesManager> bubblesManagerOptional,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
GroupMembershipManager groupMembershipManager,
@@ -176,7 +176,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
- mBubblesOptional = bubblesOptional;
+ mBubblesManagerOptional = bubblesManagerOptional;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
mGroupMembershipManager = groupMembershipManager;
@@ -360,15 +360,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
collapseOnMainThread();
}
- final int count = getVisibleNotificationsCount();
- final int rank = entry.getRanking().getRank();
NotificationVisibility.NotificationLocation location =
NotificationLogger.getNotificationLocation(entry);
- final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
- rank, count, true, location);
+ final NotificationVisibility nv = NotificationVisibility.obtain(entry.getKey(),
+ entry.getRanking().getRank(), getVisibleNotificationsCount(), true, location);
- // NMS will officially remove notification if the notification has FLAG_AUTO_CANCEL:
- mClickNotifier.onNotificationClick(notificationKey, nv);
// TODO (b/162832756): delete these notification removals when migrating to the new
// pipeline; this is taken care of in {@link NotifCollection#tryRemoveNotification}
@@ -378,23 +374,40 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
if (shouldAutoCancel(entry.getSbn())
|| mRemoteInputManager.isNotificationKeptForRemoteInputHistory(
notificationKey)) {
- // manually call notification removal in order to cancel any lifetime extenders
- removeNotification(row.getEntry());
+ // Immediately remove notification from visually showing.
+ // We have to post the removal to the UI thread for synchronization.
+ mMainThreadHandler.post(() -> {
+ final Runnable removeNotification = () -> {
+ mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK);
+ mClickNotifier.onNotificationClick(entry.getKey(), nv);
+ };
+ if (mPresenter.isCollapsing()) {
+ // To avoid lags we're only performing the remove
+ // after the shade is collapsed
+ mShadeController.addPostCollapseAction(removeNotification);
+ } else {
+ removeNotification.run();
+ }
+ });
}
+ } else {
+ // inform NMS that the notification was clicked
+ mClickNotifier.onNotificationClick(notificationKey, nv);
}
mIsCollapsingToShowActivityOverLockscreen = false;
}
private void expandBubbleStackOnMainThread(NotificationEntry entry) {
- if (!mBubblesOptional.isPresent()) {
+ if (!mBubblesManagerOptional.isPresent()) {
return;
}
if (Looper.getMainLooper().isCurrentThread()) {
- mBubblesOptional.get().expandStackAndSelectBubble(entry);
+ mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
} else {
- mMainThreadHandler.post(() -> mBubblesOptional.get().expandStackAndSelectBubble(entry));
+ mMainThreadHandler.post(
+ () -> mBubblesManagerOptional.get().expandStackAndSelectBubble(entry));
}
}
@@ -565,21 +578,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
return entry.shouldSuppressFullScreenIntent();
}
- private void removeNotification(NotificationEntry entry) {
- // We have to post it to the UI thread for synchronization
- mMainThreadHandler.post(() -> {
- if (mPresenter.isCollapsing()) {
- // To avoid lags we're only performing the remove
- // after the shade was collapsed
- mShadeController.addPostCollapseAction(
- () -> mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK)
- );
- } else {
- mOnUserInteractionCallback.onDismiss(entry, REASON_CLICK);
- }
- });
- }
-
// --------------------- NotificationEntryManager/NotifPipeline methods ------------------------
private int getVisibleNotificationsCount() {
@@ -609,7 +607,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
- private final Optional<Bubbles> mBubblesOptional;
+ private final Optional<BubblesManager> mBubblesManagerOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
private final GroupMembershipManager mGroupMembershipManager;
@@ -646,7 +644,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
- Optional<Bubbles> bubblesOptional,
+ Optional<BubblesManager> bubblesManager,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
GroupMembershipManager groupMembershipManager,
@@ -676,7 +674,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
- mBubblesOptional = bubblesOptional;
+ mBubblesManagerOptional = bubblesManager;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
mGroupMembershipManager = groupMembershipManager;
@@ -732,7 +730,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager,
mKeyguardManager,
mDreamManager,
- mBubblesOptional,
+ mBubblesManagerOptional,
mAssistManagerLazy,
mRemoteInputManager,
mGroupMembershipManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index 6d4099b656cb..13d5bf59fb4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -31,7 +31,6 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.bubbles.Bubbles;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -97,6 +96,8 @@ import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.volume.VolumeComponent;
+import com.android.systemui.wmshell.BubblesManager;
+import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.splitscreen.SplitScreen;
import java.util.Optional;
@@ -155,6 +156,7 @@ public interface StatusBarPhoneModule {
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
VibratorHelper vibratorHelper,
+ Optional<BubblesManager> bubblesManagerOptional,
Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
@@ -233,6 +235,7 @@ public interface StatusBarPhoneModule {
wakefulnessLifecycle,
statusBarStateController,
vibratorHelper,
+ bubblesManagerOptional,
bubblesOptional,
visualStabilityManager,
deviceProvisionedController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 06e4731265e3..08e70a97e0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -97,6 +97,9 @@ public interface BatteryController extends DemoMode, Dumpable,
default void onPowerSaveChanged(boolean isPowerSave) {
}
+ default void onBatteryUnknownStateChanged(boolean isUnknown) {
+ }
+
default void onReverseChanged(boolean isReverse, int level, String name) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 57ac85e1e86d..d8710bf85a6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.policy;
+import static android.os.BatteryManager.EXTRA_PRESENT;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -75,6 +77,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected int mLevel;
protected boolean mPluggedIn;
protected boolean mCharging;
+ private boolean mStateUnknown = false;
private boolean mCharged;
protected boolean mPowerSave;
private boolean mAodPowerSave;
@@ -138,6 +141,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
pw.print(" mPowerSave="); pw.println(mPowerSave);
+ pw.print(" mStateUnknown="); pw.println(mStateUnknown);
}
@Override
@@ -180,6 +184,13 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mWirelessCharging = mCharging && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
== BatteryManager.BATTERY_PLUGGED_WIRELESS;
+ boolean present = intent.getBooleanExtra(EXTRA_PRESENT, true);
+ boolean unknown = !present;
+ if (unknown != mStateUnknown) {
+ mStateUnknown = unknown;
+ fireBatteryUnknownStateChanged();
+ }
+
fireBatteryLevelChanged();
} else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
updatePowerSave();
@@ -328,6 +339,15 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
}
}
+ private void fireBatteryUnknownStateChanged() {
+ synchronized (mChangeCallbacks) {
+ final int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
+ }
+ }
+ }
+
private void firePowerSaveChanged() {
synchronized (mChangeCallbacks) {
final int N = mChangeCallbacks.size();
@@ -346,6 +366,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
String level = args.getString("level");
String plugged = args.getString("plugged");
String powerSave = args.getString("powersave");
+ String present = args.getString("present");
if (level != null) {
mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
}
@@ -356,6 +377,10 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mPowerSave = powerSave.equals("true");
firePowerSaveChanged();
}
+ if (present != null) {
+ mStateUnknown = !present.equals("true");
+ fireBatteryUnknownStateChanged();
+ }
fireBatteryLevelChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
new file mode 100644
index 000000000000..92e5b78f776a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.policy
+
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import com.android.systemui.R
+import com.android.systemui.util.concurrency.DelayableExecutor
+import javax.inject.Inject
+
+/**
+ * Listens for important battery states and sends non-dismissible system notifications if there is a
+ * problem
+ */
+class BatteryStateNotifier @Inject constructor(
+ val controller: BatteryController,
+ val noMan: NotificationManager,
+ val delayableExecutor: DelayableExecutor,
+ val context: Context
+) : BatteryController.BatteryStateChangeCallback {
+ var stateUnknown = false
+
+ fun startListening() {
+ controller.addCallback(this)
+ }
+
+ fun stopListening() {
+ controller.removeCallback(this)
+ }
+
+ override fun onBatteryUnknownStateChanged(isUnknown: Boolean) {
+ stateUnknown = isUnknown
+ if (stateUnknown) {
+ val channel = NotificationChannel("battery_status", "Battery status",
+ NotificationManager.IMPORTANCE_DEFAULT)
+ noMan.createNotificationChannel(channel)
+
+ val intent = Intent(Intent.ACTION_VIEW,
+ Uri.parse(context.getString(R.string.config_batteryStateUnknownUrl)))
+ val pi = PendingIntent.getActivity(context, 0, intent, 0)
+
+ val builder = Notification.Builder(context, channel.id)
+ .setAutoCancel(false)
+ .setContentTitle(
+ context.getString(R.string.battery_state_unknown_notification_title))
+ .setContentText(
+ context.getString(R.string.battery_state_unknown_notification_text))
+ .setSmallIcon(com.android.internal.R.drawable.stat_sys_adb)
+ .setContentIntent(pi)
+ .setAutoCancel(true)
+ .setOngoing(true)
+
+ noMan.notify(TAG, ID, builder.build())
+ } else {
+ scheduleNotificationCancel()
+ }
+ }
+
+ private fun scheduleNotificationCancel() {
+ val r = {
+ if (!stateUnknown) {
+ noMan.cancel(ID)
+ }
+ }
+ delayableExecutor.executeDelayed(r, DELAY_MILLIS)
+ }
+}
+
+private const val TAG = "BatteryStateNotifier"
+private const val ID = 666
+private const val DELAY_MILLIS: Long = 4 * 60 * 60 * 1000 \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index c15560ae9f38..0e10fdd88e00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -26,7 +26,6 @@ import android.os.Handler;
import android.os.Looper;
import android.provider.Settings.Global;
import android.telephony.Annotation;
-import android.telephony.CdmaEriInformation;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.CellSignalStrengthNr;
@@ -591,11 +590,9 @@ public class MobileSignalController extends SignalController<
if (isCarrierNetworkChangeActive()) {
return false;
}
- if (isCdma() && mServiceState != null) {
- final int iconMode = mPhone.getCdmaEriInformation().getEriIconMode();
- return mPhone.getCdmaEriInformation().getEriIconIndex() != CdmaEriInformation.ERI_OFF
- && (iconMode == CdmaEriInformation.ERI_ICON_MODE_NORMAL
- || iconMode == CdmaEriInformation.ERI_ICON_MODE_FLASH);
+ if (isCdma()) {
+ return mPhone.getCdmaEnhancedRoamingIndicatorDisplayNumber()
+ != TelephonyManager.ERI_OFF;
} else {
return mServiceState != null && mServiceState.getRoaming();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 9c3395f9332d..4552026ced4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -18,11 +18,13 @@ package com.android.systemui.statusbar.policy;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
@@ -43,6 +45,7 @@ import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.OnReceiveContentCallback;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
@@ -57,9 +60,6 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
-import androidx.core.view.inputmethod.InputConnectionCompat;
-import androidx.core.view.inputmethod.InputContentInfoCompat;
-
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
@@ -313,6 +313,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mRemoteInputs = remoteInputs;
mRemoteInput = remoteInput;
mEditText.setHint(mRemoteInput.getLabel());
+ mEditText.mSupportedMimeTypes = (remoteInput.getAllowedDataTypes() == null) ? null
+ : remoteInput.getAllowedDataTypes().toArray(new String[0]);
mEntry.editedSuggestionInfo = editedSuggestionInfo;
if (editedSuggestionInfo != null) {
@@ -571,6 +573,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
UserHandle mUser;
+ private String[] mSupportedMimeTypes;
public RemoteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -578,6 +581,36 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mLightBarController = Dependency.get(LightBarController.class);
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ if (mSupportedMimeTypes != null && mSupportedMimeTypes.length > 0) {
+ setOnReceiveContentCallback(mSupportedMimeTypes,
+ new OnReceiveContentCallback<View>() {
+ @Override
+ public boolean onReceiveContent(@NonNull View view,
+ @NonNull Payload payload) {
+ ClipData clip = payload.getClip();
+ if (clip.getItemCount() == 0) {
+ return false;
+ }
+ Uri contentUri = clip.getItemAt(0).getUri();
+ ClipDescription description = clip.getDescription();
+ String mimeType = null;
+ if (description.getMimeTypeCount() > 0) {
+ mimeType = description.getMimeType(0);
+ }
+ if (mimeType != null) {
+ Intent dataIntent = mRemoteInputView
+ .prepareRemoteInputFromData(mimeType, contentUri);
+ mRemoteInputView.sendRemoteInput(dataIntent);
+ }
+ return true;
+ }
+ });
+ }
+ }
+
private void defocusIfNeeded(boolean animate) {
if (mRemoteInputView != null && mRemoteInputView.mEntry.getRow().isChangingPosition()
|| isTemporarilyDetached()) {
@@ -670,36 +703,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- // TODO: Pass RemoteInput data types to allow image insertion.
- // String[] allowedDataTypes = mRemoteInputView.mRemoteInput.getAllowedDataTypes()
- // .toArray(new String[0]);
- // EditorInfoCompat.setContentMimeTypes(outAttrs, allowedDataTypes);
- final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
-
- final InputConnectionCompat.OnCommitContentListener callback =
- new InputConnectionCompat.OnCommitContentListener() {
- @Override
- public boolean onCommitContent(
- InputContentInfoCompat inputContentInfoCompat, int i,
- Bundle bundle) {
- Uri contentUri = inputContentInfoCompat.getContentUri();
- ClipDescription description = inputContentInfoCompat.getDescription();
- String mimeType = null;
- if (description != null && description.getMimeTypeCount() > 0) {
- mimeType = description.getMimeType(0);
- }
- if (mimeType != null) {
- Intent dataIntent = mRemoteInputView.prepareRemoteInputFromData(
- mimeType, contentUri);
- mRemoteInputView.sendRemoteInput(dataIntent);
- }
- return true;
- }
- };
-
- InputConnection ic = inputConnection == null ? null :
- InputConnectionCompat.createWrapper(inputConnection, outAttrs, callback);
-
+ final InputConnection ic = super.onCreateInputConnection(outAttrs);
Context userContext = null;
try {
userContext = mContext.createPackageContextAsUser(
@@ -747,7 +751,6 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} else {
setBackground(null);
}
-
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 79d264ca4577..e8331a176134 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.policy;
+import android.app.admin.DeviceAdminInfo;
+import android.graphics.drawable.Drawable;
+
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
@@ -40,6 +43,15 @@ public interface SecurityController extends CallbackController<SecurityControlle
boolean hasCACertInCurrentUser();
boolean hasCACertInWorkProfile();
void onUserSwitched(int newUserId);
+ /** Whether or not parental controls is enabled */
+ boolean isParentalControlsEnabled();
+ /** DeviceAdminInfo for active admin */
+ DeviceAdminInfo getDeviceAdminInfo();
+ /** Icon for admin */
+ Drawable getIcon(DeviceAdminInfo info);
+ /** Label for admin */
+ CharSequence getLabel(DeviceAdminInfo info);
+
public interface SecurityControllerCallback {
void onStateChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 7e54e8d1c1c3..1d778419cbaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -16,15 +16,19 @@
package com.android.systemui.statusbar.policy;
import android.app.ActivityManager;
+import android.app.admin.DeviceAdminInfo;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IConnectivityManager;
@@ -53,7 +57,10 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.settings.CurrentUserTracker;
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.FileDescriptor;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.concurrent.Executor;
@@ -306,6 +313,50 @@ public class SecurityControllerImpl extends CurrentUserTracker implements Securi
fireCallbacks();
}
+ @Override
+ public boolean isParentalControlsEnabled() {
+ return getProfileOwnerOrDeviceOwnerSupervisionComponent() != null;
+ }
+
+ @Override
+ public DeviceAdminInfo getDeviceAdminInfo() {
+ return getDeviceAdminInfo(getProfileOwnerOrDeviceOwnerComponent());
+ }
+
+ @Override
+ public Drawable getIcon(DeviceAdminInfo info) {
+ return (info == null) ? null : info.loadIcon(mPackageManager);
+ }
+
+ @Override
+ public CharSequence getLabel(DeviceAdminInfo info) {
+ return (info == null) ? null : info.loadLabel(mPackageManager);
+ }
+
+ private ComponentName getProfileOwnerOrDeviceOwnerSupervisionComponent() {
+ UserHandle currentUser = new UserHandle(mCurrentUserId);
+ return mDevicePolicyManager
+ .getProfileOwnerOrDeviceOwnerSupervisionComponent(currentUser);
+ }
+
+ // Returns the ComponentName of the current DO/PO. Right now it only checks the supervision
+ // component but can be changed to check for other DO/POs. This change would make getIcon()
+ // and getLabel() work for all admins.
+ private ComponentName getProfileOwnerOrDeviceOwnerComponent() {
+ return getProfileOwnerOrDeviceOwnerSupervisionComponent();
+ }
+
+ private DeviceAdminInfo getDeviceAdminInfo(ComponentName componentName) {
+ try {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.activityInfo = mPackageManager.getReceiverInfo(componentName,
+ PackageManager.GET_META_DATA);
+ return new DeviceAdminInfo(mContext, resolveInfo);
+ } catch (NameNotFoundException | XmlPullParserException | IOException e) {
+ return null;
+ }
+ }
+
private void refreshCACerts(int userId) {
mBgExecutor.execute(() -> {
Pair<Integer, Boolean> idWithCert = null;
diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
index 1c682e3bb7dc..409d1361223c 100644
--- a/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
+++ b/packages/SystemUI/src/com/android/systemui/toast/ToastUI.java
@@ -129,6 +129,8 @@ public class ToastUI extends SystemUI implements CommandQueue.Callbacks {
mCallback = callback;
mPresenter = new ToastPresenter(context, mIAccessibilityManager, mNotificationManager,
packageName);
+ // Set as trusted overlay so touches can pass through toasts
+ mPresenter.getLayoutParams().setTrustedOverlay();
mToastLogger.logOnShowToast(uid, packageName, text.toString(), token.toString());
mPresenter.show(mToast.getView(), token, windowToken, duration, mToast.getGravity(),
mToast.getXOffset(), mToast.getYOffset(), mToast.getHorizontalMargin(),
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 70bba263ab90..b67574d1c4de 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
public abstract class TunerService {
public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
+ private final Context mContext;
public abstract void clearAll();
public abstract void destroy();
@@ -50,6 +51,10 @@ public abstract class TunerService {
void onTuningChanged(String key, String newValue);
}
+ public TunerService(Context context) {
+ mContext = context;
+ }
+
private static Context userContext(Context context, UserHandle user) {
try {
return context.createPackageContextAsUser(context.getPackageName(), 0, user);
@@ -58,6 +63,11 @@ public abstract class TunerService {
}
}
+ /** Enables or disables the tuner for the supplied user. */
+ public void setTunerEnabled(UserHandle user, boolean enabled) {
+ setTunerEnabled(mContext, user, enabled);
+ }
+
public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
userContext(context, user).getPackageManager().setComponentEnabledSetting(
new ComponentName(context, TunerActivity.class),
@@ -66,6 +76,11 @@ public abstract class TunerService {
PackageManager.DONT_KILL_APP);
}
+ /** Returns true if the tuner is enabled for the supplied user. */
+ public boolean isTunerEnabled(UserHandle user) {
+ return isTunerEnabled(mContext, user);
+ }
+
public static final boolean isTunerEnabled(Context context, UserHandle user) {
return userContext(context, user).getPackageManager().getComponentEnabledSetting(
new ComponentName(context, TunerActivity.class))
@@ -81,6 +96,11 @@ public abstract class TunerService {
}
}
+ /** */
+ public void showResetRequest(UserHandle user, final Runnable onDisabled) {
+ showResetRequest(mContext, user, onDisabled);
+ }
+
public static final void showResetRequest(final Context context, UserHandle user,
final Runnable onDisabled) {
SystemUIDialog dialog = new SystemUIDialog(context);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 22f03e074b06..027c282ba352 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -94,6 +94,7 @@ public class TunerServiceImpl extends TunerService {
DemoModeController demoModeController,
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker) {
+ super(context);
mContext = context;
mContentResolver = mContext.getContentResolver();
mLeakDetector = leakDetector;
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
index df741a0e98ff..89ab23b50cd8 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvGlobalRootComponent.java
@@ -42,6 +42,12 @@ public interface TvGlobalRootComponent extends GlobalRootComponent {
TvGlobalRootComponent build();
}
+ /**
+ * Builder for a WMComponent.
+ */
+ @Override
+ TvWMComponent.Builder getWMComponentBuilder();
+
@Override
TvSysUIComponent.Builder getSysUIComponent();
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
index 334bb013ae69..9621e5f5f1a0 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSysUIComponentModule.java
@@ -19,7 +19,7 @@ package com.android.systemui.tv;
import dagger.Module;
/**
- * Dagger module for including the WMComponent.
+ * Dagger module for including the SysUIComponent.
*/
@Module(subcomponents = {TvSysUIComponent.class})
public abstract class TvSysUIComponentModule {
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
index bde88b1b5533..2c3ea4f452bb 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIBinder.java
@@ -22,7 +22,7 @@ import com.android.systemui.wmshell.TvPipModule;
import dagger.Binds;
import dagger.Module;
-@Module(includes = TvPipModule.class)
+@Module
interface TvSystemUIBinder {
@Binds
GlobalRootComponent bindGlobalRootComponent(TvGlobalRootComponent globalRootComponent);
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index c5bb9c1b6f48..8ffc7cf568ff 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -62,7 +62,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.wmshell.TvWMShellModule;
import javax.inject.Named;
@@ -75,8 +74,7 @@ import dagger.Provides;
* overridden by the System UI implementation.
*/
@Module(includes = {
- QSModule.class,
- TvWMShellModule.class,
+ QSModule.class
},
subcomponents = {
})
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java b/packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java
new file mode 100644
index 000000000000..f678513f2ce6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvWMComponent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.tv;
+
+import com.android.systemui.dagger.WMComponent;
+import com.android.systemui.dagger.WMSingleton;
+import com.android.systemui.wmshell.TvWMShellModule;
+
+import dagger.Subcomponent;
+
+
+/**
+ * Dagger Subcomponent for WindowManager.
+ */
+@WMSingleton
+@Subcomponent(modules = {TvWMShellModule.class})
+public interface TvWMComponent extends WMComponent {
+
+ /**
+ * Builder for a SysUIComponent.
+ */
+ @Subcomponent.Builder
+ interface Builder extends WMComponent.Builder {
+ TvWMComponent build();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 66f8f74c7cab..6b5556b3ea91 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -23,13 +23,19 @@ import android.content.Context;
import android.provider.DeviceConfig;
import android.provider.Settings;
+import com.android.systemui.dagger.SysUISingleton;
+
import java.util.concurrent.Executor;
+import javax.inject.Inject;
+
/**
* Wrapper around DeviceConfig useful for testing.
*/
+@SysUISingleton
public class DeviceConfigProxy {
+ @Inject
public DeviceConfigProxy() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 344f0d2f5506..4b4e1df21bd0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -24,10 +24,8 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
-import com.android.systemui.qs.customize.QSCustomizer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.lang.reflect.InvocationTargetException;
@@ -92,11 +90,6 @@ public class InjectionInflationController {
}
/**
- * Creates the QSFooterImpl.
- */
- QSFooterImpl createQsFooter();
-
- /**
* Creates the NotificationStackScrollLayout.
*/
NotificationStackScrollLayout createNotificationStackScrollLayout();
@@ -110,11 +103,6 @@ public class InjectionInflationController {
* Creates the QuickQSPanel.
*/
QuickQSPanel createQuickQSPanel();
-
- /**
- * Creates the QSCustomizer.
- */
- QSCustomizer createQSCustomizer();
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
index c7aa780fcacb..880d09abeb60 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java
@@ -16,6 +16,8 @@
package com.android.systemui.util;
+import android.content.Context;
+import android.content.res.Resources;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -24,8 +26,8 @@ import android.view.View.OnAttachStateChangeListener;
*
* Implementations should handle setup and teardown related activities inside of
* {@link #onViewAttached()} and {@link #onViewDetached()}. Be sure to call {@link #init()} on
- * any child controllers that this uses. This can be done in {@link init()} if the controllers
- * are injected, or right after creation time of the child controller.
+ * any child controllers that this uses. This can be done in {@link #onInit()} if the
+ * controllers are injected, or right after creation time of the child controller.
*
* Tip: View "attachment" happens top down - parents are notified that they are attached before
* any children. That means that if you call a method on a child controller in
@@ -60,11 +62,18 @@ public abstract class ViewController<T extends View> {
mView = view;
}
- /** Call immediately after constructing Controller in order to handle view lifecycle events. */
+ /**
+ * Call immediately after constructing Controller in order to handle view lifecycle events.
+ *
+ * Generally speaking, you don't want to override this method. Instead, override
+ * {@link #onInit()} as a way to have an run-once idempotent method that you can use for
+ * setup of your ViewController.
+ */
public void init() {
if (mInited) {
return;
}
+ onInit();
mInited = true;
if (mView != null) {
@@ -76,6 +85,22 @@ public abstract class ViewController<T extends View> {
}
/**
+ * Run once when {@link #init()} is called.
+ *
+ * Override this to perform idempotent, one-time setup that your controller needs. It will
+ * be called before {@link #onViewAttached()}.
+ */
+ protected void onInit() {}
+
+ protected Context getContext() {
+ return mView.getContext();
+ }
+
+ protected Resources getResources() {
+ return mView.getResources();
+ }
+
+ /**
* Called when the view is attached and a call to {@link #init()} has been made in either order.
*/
protected abstract void onViewAttached();
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
new file mode 100644
index 000000000000..844f12e0c43e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -0,0 +1,725 @@
+/*
+ * 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.wmshell;
+
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CLICK;
+import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.ZenModeConfig;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.notification.NotificationChannelHelper;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.BubbleCoordinator;
+import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.wm.shell.bubbles.BubbleEntry;
+import com.android.wm.shell.bubbles.Bubbles;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.IntConsumer;
+
+/**
+ * The SysUi side bubbles manager which communicate with other SysUi components.
+ */
+@SysUISingleton
+public class BubblesManager implements Dumpable {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesManager" : TAG_BUBBLES;
+
+ private final Context mContext;
+ private final Bubbles mBubbles;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
+ private final ShadeController mShadeController;
+ private final IStatusBarService mBarService;
+ private final INotificationManager mNotificationManager;
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+ private final NotificationGroupManagerLegacy mNotificationGroupManager;
+ private final NotificationEntryManager mNotificationEntryManager;
+ private final NotifPipeline mNotifPipeline;
+
+ private final ScrimView mBubbleScrim;
+ private final Bubbles.SysuiProxy mSysuiProxy;
+ // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
+ private final List<NotifCallback> mCallbacks = new ArrayList<>();
+
+ /**
+ * Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present
+ * which means bubbles feature not support.
+ */
+ @Nullable
+ public static BubblesManager create(Context context,
+ Optional<Bubbles> bubblesOptional,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController,
+ ShadeController shadeController,
+ ConfigurationController configurationController,
+ @Nullable IStatusBarService statusBarService,
+ INotificationManager notificationManager,
+ NotificationInterruptStateProvider interruptionStateProvider,
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManagerLegacy groupManager,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ SysUiState sysUiState,
+ FeatureFlags featureFlags,
+ DumpManager dumpManager) {
+ if (bubblesOptional.isPresent()) {
+ return new BubblesManager(context, bubblesOptional.get(),
+ notificationShadeWindowController, statusBarStateController, shadeController,
+ configurationController, statusBarService, notificationManager,
+ interruptionStateProvider, zenModeController, notifUserManager,
+ groupManager, entryManager, notifPipeline, sysUiState, featureFlags,
+ dumpManager);
+ } else {
+ return null;
+ }
+ }
+
+ @VisibleForTesting
+ BubblesManager(Context context,
+ Bubbles bubbles,
+ NotificationShadeWindowController notificationShadeWindowController,
+ StatusBarStateController statusBarStateController,
+ ShadeController shadeController,
+ ConfigurationController configurationController,
+ @Nullable IStatusBarService statusBarService,
+ INotificationManager notificationManager,
+ NotificationInterruptStateProvider interruptionStateProvider,
+ ZenModeController zenModeController,
+ NotificationLockscreenUserManager notifUserManager,
+ NotificationGroupManagerLegacy groupManager,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ SysUiState sysUiState,
+ FeatureFlags featureFlags,
+ DumpManager dumpManager) {
+ mContext = context;
+ mBubbles = bubbles;
+ mNotificationShadeWindowController = notificationShadeWindowController;
+ mShadeController = shadeController;
+ mNotificationManager = notificationManager;
+ mNotificationInterruptStateProvider = interruptionStateProvider;
+ mNotificationGroupManager = groupManager;
+ mNotificationEntryManager = entryManager;
+ mNotifPipeline = notifPipeline;
+
+ mBarService = statusBarService == null
+ ? IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE))
+ : statusBarService;
+
+ mBubbleScrim = new ScrimView(mContext);
+ mBubbleScrim.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ mBubbles.setBubbleScrim(mBubbleScrim);
+
+ if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
+ setupNotifPipeline();
+ } else {
+ setupNEM();
+ }
+
+ dumpManager.registerDumpable(TAG, this);
+
+ statusBarStateController.addCallback(new StatusBarStateController.StateListener() {
+ @Override
+ public void onStateChanged(int newState) {
+ boolean isShade = newState == SHADE;
+ bubbles.onStatusBarStateChanged(isShade);
+ }
+ });
+
+ configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ mBubbles.onConfigChanged(newConfig);
+ }
+
+ @Override
+ public void onUiModeChanged() {
+ mBubbles.updateForThemeChanges();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ mBubbles.updateForThemeChanges();
+ }
+ });
+
+ zenModeController.addCallback(new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ mBubbles.onZenStateChanged();
+ }
+
+ @Override
+ public void onConfigChanged(ZenModeConfig config) {
+ mBubbles.onZenStateChanged();
+ }
+ });
+
+ notifUserManager.addUserChangedListener(
+ new NotificationLockscreenUserManager.UserChangedListener() {
+ @Override
+ public void onUserChanged(int userId) {
+ mBubbles.onUserChanged(userId);
+ }
+ });
+
+ mSysuiProxy = new Bubbles.SysuiProxy() {
+ @Override
+ @Nullable
+ public BubbleEntry getPendingOrActiveEntry(String key) {
+ NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
+ return entry == null ? null : notifToBubbleEntry(entry);
+ }
+
+ @Override
+ public List<BubbleEntry> getShouldRestoredEntries(ArraySet<String> savedBubbleKeys) {
+ List<BubbleEntry> result = new ArrayList<>();
+ List<NotificationEntry> activeEntries =
+ mNotificationEntryManager.getActiveNotificationsForCurrentUser();
+ for (int i = 0; i < activeEntries.size(); i++) {
+ NotificationEntry entry = activeEntries.get(i);
+ if (savedBubbleKeys.contains(entry.getKey())
+ && mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+ && entry.isBubble()) {
+ result.add(notifToBubbleEntry(entry));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public boolean isNotificationShadeExpand() {
+ return mNotificationShadeWindowController.getPanelExpanded();
+ }
+
+ @Override
+ public boolean shouldBubbleUp(String key) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null) {
+ return mNotificationInterruptStateProvider.shouldBubbleUp(entry);
+ }
+ return false;
+ }
+
+ @Override
+ public void setNotificationInterruption(String key) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null && entry.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
+ entry.setInterruption();
+ }
+ }
+
+ @Override
+ public void requestNotificationShadeTopUi(boolean requestTopUi, String componentTag) {
+ mNotificationShadeWindowController.setRequestTopUi(requestTopUi, componentTag);
+ }
+
+ @Override
+ public void notifyRemoveNotification(String key, int reason) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null) {
+ for (NotifCallback cb : mCallbacks) {
+ cb.removeNotification(entry, getDismissedByUserStats(entry, true), reason);
+ }
+ }
+ }
+
+ @Override
+ public void notifyInvalidateNotifications(String reason) {
+ for (NotifCallback cb : mCallbacks) {
+ cb.invalidateNotifications(reason);
+ }
+ }
+
+ @Override
+ public void notifyMaybeCancelSummary(String key) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null) {
+ for (NotifCallback cb : mCallbacks) {
+ cb.maybeCancelSummary(entry);
+ }
+ }
+ }
+
+ @Override
+ public void removeNotificationEntry(String key) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null) {
+ mNotificationGroupManager.onEntryRemoved(entry);
+ }
+ }
+
+ @Override
+ public void updateNotificationBubbleButton(String key) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null && entry.getRow() != null) {
+ entry.getRow().updateBubbleButton();
+ }
+ }
+
+ @Override
+ public void updateNotificationSuppression(String key) {
+ final NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
+ key);
+ if (entry != null) {
+ mNotificationGroupManager.updateSuppression(entry);
+ }
+ }
+
+ @Override
+ public void onStackExpandChanged(boolean shouldExpand) {
+ sysUiState
+ .setFlag(QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED, shouldExpand)
+ .commitUpdate(mContext.getDisplayId());
+ }
+
+ @Override
+ public void onUnbubbleConversation(String key) {
+ final NotificationEntry entry =
+ mNotificationEntryManager.getPendingOrActiveNotif(key);
+ if (entry != null) {
+ onUserChangedBubble(entry, false /* shouldBubble */);
+ }
+ }
+ };
+ mBubbles.setSysuiProxy(mSysuiProxy);
+ }
+
+ private void setupNEM() {
+ mNotificationEntryManager.addNotificationEntryListener(
+ new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ BubblesManager.this.onEntryAdded(entry);
+ }
+
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ BubblesManager.this.onEntryUpdated(entry);
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
+ boolean removedByUser, int reason) {
+ BubblesManager.this.onEntryRemoved(entry);
+ }
+
+ @Override
+ public void onNotificationRankingUpdated(RankingMap rankingMap) {
+ BubblesManager.this.onRankingUpdate(rankingMap);
+ }
+ });
+
+ // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator
+ mNotificationEntryManager.addNotificationRemoveInterceptor(
+ (key, entry, dismissReason) -> {
+ final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
+ final boolean isUserDismiss = dismissReason == REASON_CANCEL
+ || dismissReason == REASON_CLICK;
+ final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
+ || dismissReason == REASON_APP_CANCEL_ALL;
+ final boolean isSummaryCancel =
+ dismissReason == REASON_GROUP_SUMMARY_CANCELED;
+
+ // Need to check for !appCancel here because the notification may have
+ // previously been dismissed & entry.isRowDismissed would still be true
+ boolean userRemovedNotif =
+ (entry != null && entry.isRowDismissed() && !isAppCancel)
+ || isClearAll || isUserDismiss || isSummaryCancel;
+
+ if (userRemovedNotif) {
+ return handleDismissalInterception(entry);
+ }
+ return false;
+ });
+
+ mNotificationGroupManager.registerGroupChangeListener(
+ new NotificationGroupManagerLegacy.OnGroupChangeListener() {
+ @Override
+ public void onGroupSuppressionChanged(
+ NotificationGroupManagerLegacy.NotificationGroup group,
+ boolean suppressed) {
+ // More notifications could be added causing summary to no longer
+ // be suppressed -- in this case need to remove the key.
+ final String groupKey = group.summary != null
+ ? group.summary.getSbn().getGroupKey()
+ : null;
+ if (!suppressed && groupKey != null
+ && mBubbles.isSummarySuppressed(groupKey)) {
+ mBubbles.removeSuppressedSummary(groupKey);
+ }
+ }
+ });
+
+ addNotifCallback(new NotifCallback() {
+ @Override
+ public void removeNotification(NotificationEntry entry,
+ DismissedByUserStats dismissedByUserStats, int reason) {
+ mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
+ dismissedByUserStats, reason);
+ }
+
+ @Override
+ public void invalidateNotifications(String reason) {
+ mNotificationEntryManager.updateNotifications(reason);
+ }
+
+ @Override
+ public void maybeCancelSummary(NotificationEntry entry) {
+ // Check if removed bubble has an associated suppressed group summary that needs
+ // to be removed now.
+ final String groupKey = entry.getSbn().getGroupKey();
+ if (mBubbles.isSummarySuppressed(groupKey)) {
+ mBubbles.removeSuppressedSummary(groupKey);
+
+ final NotificationEntry summary =
+ mNotificationEntryManager.getActiveNotificationUnfiltered(
+ mBubbles.getSummaryKey(groupKey));
+ if (summary != null) {
+ mNotificationEntryManager.performRemoveNotification(
+ summary.getSbn(),
+ getDismissedByUserStats(summary, false),
+ UNDEFINED_DISMISS_REASON);
+ }
+ }
+
+ // Check if we still need to remove the summary from NoManGroup because the summary
+ // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
+ // For example:
+ // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
+ // 2. User expands bubbles so now their respective notifications in the shade are
+ // hidden, including the group summary
+ // 3. User removes all bubbles
+ // 4. We expect all the removed bubbles AND the summary (note: the summary was
+ // never added to the suppressedSummary list in BubbleData, so we add this check)
+ NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry);
+ if (summary != null) {
+ ArrayList<NotificationEntry> summaryChildren =
+ mNotificationGroupManager.getLogicalChildren(summary.getSbn());
+ boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
+ if (!isSummaryThisNotif && (summaryChildren == null
+ || summaryChildren.isEmpty())) {
+ mNotificationEntryManager.performRemoveNotification(
+ summary.getSbn(),
+ getDismissedByUserStats(summary, false),
+ UNDEFINED_DISMISS_REASON);
+ }
+ }
+ }
+ });
+ }
+
+ private void setupNotifPipeline() {
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ BubblesManager.this.onEntryAdded(entry);
+ }
+
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ BubblesManager.this.onEntryUpdated(entry);
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry,
+ @NotifCollection.CancellationReason int reason) {
+ BubblesManager.this.onEntryRemoved(entry);
+ }
+
+ @Override
+ public void onRankingUpdate(RankingMap rankingMap) {
+ BubblesManager.this.onRankingUpdate(rankingMap);
+ }
+ });
+ }
+
+ void onEntryAdded(NotificationEntry entry) {
+ if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
+ && entry.isBubble()) {
+ mBubbles.onEntryAdded(notifToBubbleEntry(entry));
+ }
+ }
+
+ void onEntryUpdated(NotificationEntry entry) {
+ mBubbles.onEntryUpdated(notifToBubbleEntry(entry),
+ mNotificationInterruptStateProvider.shouldBubbleUp(entry));
+ }
+
+ void onEntryRemoved(NotificationEntry entry) {
+ mBubbles.onEntryRemoved(notifToBubbleEntry(entry));
+ }
+
+ void onRankingUpdate(RankingMap rankingMap) {
+ mBubbles.onRankingUpdated(rankingMap);
+ }
+
+ /**
+ * Gets the DismissedByUserStats used by {@link NotificationEntryManager}.
+ * Will not be necessary when using the new notification pipeline's {@link NotifCollection}.
+ * Instead, this is taken care of by {@link BubbleCoordinator}.
+ */
+ private DismissedByUserStats getDismissedByUserStats(
+ NotificationEntry entry,
+ boolean isVisible) {
+ return new DismissedByUserStats(
+ DISMISSAL_BUBBLE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(
+ entry.getKey(),
+ entry.getRanking().getRank(),
+ mNotificationEntryManager.getActiveNotificationsCount(),
+ isVisible,
+ NotificationLogger.getNotificationLocation(entry)));
+ }
+
+ /**
+ * Returns the scrim drawn behind the bubble stack. This is managed by {@link ScrimController}
+ * since we want the scrim's appearance and behavior to be identical to that of the notification
+ * shade scrim.
+ */
+ public ScrimView getScrimForBubble() {
+ return mBubbleScrim;
+ }
+
+ /**
+ * We intercept notification entries (including group summaries) dismissed by the user when
+ * there is an active bubble associated with it. We do this so that developers can still
+ * cancel it (and hence the bubbles associated with it).
+ *
+ * @return true if we want to intercept the dismissal of the entry, else false.
+ * @see Bubbles#handleDismissalInterception(BubbleEntry, List, IntConsumer)
+ */
+ public boolean handleDismissalInterception(NotificationEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+
+ List<NotificationEntry> children = entry.getAttachedNotifChildren();
+ List<BubbleEntry> bubbleChildren = null;
+ if (children != null) {
+ bubbleChildren = new ArrayList<>();
+ for (int i = 0; i < children.size(); i++) {
+ bubbleChildren.add(notifToBubbleEntry(children.get(i)));
+ }
+ }
+
+ return mBubbles.handleDismissalInterception(notifToBubbleEntry(entry), bubbleChildren,
+ // TODO : b/171847985 should re-work on notification side to make this more clear.
+ (int i) -> {
+ if (i >= 0) {
+ for (NotifCallback cb : mCallbacks) {
+ cb.removeNotification(children.get(i),
+ getDismissedByUserStats(children.get(i), true),
+ REASON_GROUP_SUMMARY_CANCELED);
+ }
+ } else {
+ mNotificationGroupManager.onEntryRemoved(entry);
+ }
+ });
+ }
+
+ /**
+ * Request the stack expand if needed, then select the specified Bubble as current.
+ * If no bubble exists for this entry, one is created.
+ *
+ * @param entry the notification for the bubble to be selected
+ */
+ public void expandStackAndSelectBubble(NotificationEntry entry) {
+ mBubbles.expandStackAndSelectBubble(notifToBubbleEntry(entry));
+ }
+
+ /** See {@link NotifCallback}. */
+ public void addNotifCallback(NotifCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ /**
+ * When a notification is marked Priority, expand the stack if needed,
+ * then (maybe create and) select the given bubble.
+ *
+ * @param entry the notification for the bubble to show
+ */
+ public void onUserChangedImportance(NotificationEntry entry) {
+ try {
+ int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+ flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+ mBarService.onNotificationBubbleChanged(entry.getKey(), true, flags);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ mShadeController.collapsePanel(true);
+ if (entry.getRow() != null) {
+ entry.getRow().updateBubbleButton();
+ }
+ }
+
+ /**
+ * Called when a user has indicated that an active notification should be shown as a bubble.
+ * <p>
+ * This method will collapse the shade, create the bubble without a flyout or dot, and suppress
+ * the notification from appearing in the shade.
+ *
+ * @param entry the notification to change bubble state for.
+ * @param shouldBubble whether the notification should show as a bubble or not.
+ */
+ public void onUserChangedBubble(@NonNull final NotificationEntry entry, boolean shouldBubble) {
+ NotificationChannel channel = entry.getChannel();
+ final String appPkg = entry.getSbn().getPackageName();
+ final int appUid = entry.getSbn().getUid();
+ if (channel == null || appPkg == null) {
+ return;
+ }
+
+ // Update the state in NotificationManagerService
+ try {
+ int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+ flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE;
+ mBarService.onNotificationBubbleChanged(entry.getKey(), shouldBubble, flags);
+ } catch (RemoteException e) {
+ }
+
+ // Change the settings
+ channel = NotificationChannelHelper.createConversationChannelIfNeeded(mContext,
+ mNotificationManager, entry, channel);
+ channel.setAllowBubbles(shouldBubble);
+ try {
+ int currentPref = mNotificationManager.getBubblePreferenceForPackage(appPkg, appUid);
+ if (shouldBubble && currentPref == BUBBLE_PREFERENCE_NONE) {
+ mNotificationManager.setBubblesAllowed(appPkg, appUid, BUBBLE_PREFERENCE_SELECTED);
+ }
+ mNotificationManager.updateNotificationChannelForPackage(appPkg, appUid, channel);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.getMessage());
+ }
+
+ if (shouldBubble) {
+ mShadeController.collapsePanel(true);
+ if (entry.getRow() != null) {
+ entry.getRow().updateBubbleButton();
+ }
+ }
+ }
+
+ @Override
+ public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
+ mBubbles.dump(fd, pw, args);
+ }
+
+ static BubbleEntry notifToBubbleEntry(NotificationEntry e) {
+ return new BubbleEntry(e.getSbn(), e.getRanking(), e.isClearable(),
+ e.shouldSuppressNotificationDot(), e.shouldSuppressNotificationList(),
+ e.shouldSuppressPeek());
+ }
+
+ /**
+ * Callback for when the BubbleController wants to interact with the notification pipeline to:
+ * - Remove a previously bubbled notification
+ * - Update the notification shade since bubbled notification should/shouldn't be showing
+ */
+ public interface NotifCallback {
+ /**
+ * Called when a bubbled notification that was hidden from the shade is now being removed
+ * This can happen when an app cancels a bubbled notification or when the user dismisses a
+ * bubble.
+ */
+ void removeNotification(@NonNull NotificationEntry entry,
+ @NonNull DismissedByUserStats stats, int reason);
+
+ /**
+ * Called when a bubbled notification has changed whether it should be
+ * filtered from the shade.
+ */
+ void invalidateNotifications(@NonNull String reason);
+
+ /**
+ * Called on a bubbled entry that has been removed when there are no longer
+ * bubbled entries in its group.
+ *
+ * Checks whether its group has any other (non-bubbled) children. If it doesn't,
+ * removes all remnants of the group's summary from the notification pipeline.
+ * TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
+ */
+ void maybeCancelSummary(@NonNull NotificationEntry entry);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
index f55445ca1de3..a59c87652632 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvPipModule.java
@@ -17,16 +17,15 @@
package com.android.systemui.wmshell;
import android.content.Context;
-import android.os.Handler;
-import android.view.LayoutInflater;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsHandler;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipUiEventLogger;
@@ -46,52 +45,61 @@ import dagger.Provides;
*/
@Module
public abstract class TvPipModule {
-
- @SysUISingleton
+ @WMSingleton
@Provides
- static Pip providePipController(Context context,
+ static Optional<Pip> providePip(
+ Context context,
+ PipBoundsState pipBoundsState,
PipBoundsHandler pipBoundsHandler,
PipTaskOrganizer pipTaskOrganizer,
+ PipMediaController pipMediaController,
+ PipNotification pipNotification,
WindowManagerShellWrapper windowManagerShellWrapper) {
- return new PipController(context, pipBoundsHandler, pipTaskOrganizer,
- windowManagerShellWrapper);
+ return Optional.of(
+ new PipController(
+ context,
+ pipBoundsState,
+ pipBoundsHandler,
+ pipTaskOrganizer,
+ pipMediaController,
+ pipNotification,
+ windowManagerShellWrapper));
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipControlsViewController providePipControlsViewContrller(
- PipControlsView pipControlsView, PipController pipController,
- LayoutInflater layoutInflater, Handler handler) {
- return new PipControlsViewController(pipControlsView, pipController, layoutInflater,
- handler);
+ static PipControlsViewController providePipControlsViewController(
+ PipControlsView pipControlsView, PipController pipController) {
+ return new PipControlsViewController(pipControlsView, pipController);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static PipControlsView providePipControlsView(Context context) {
return new PipControlsView(context, null);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static PipNotification providePipNotification(Context context,
- PipController pipController) {
- return new PipNotification(context, pipController);
+ PipMediaController pipMediaController) {
+ return new PipNotification(context, pipMediaController);
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipBoundsHandler providePipBoundsHandler(Context context) {
- return new PipBoundsHandler(context);
+ static PipBoundsHandler providePipBoundsHandler(Context context,
+ PipBoundsState pipBoundsState) {
+ return new PipBoundsHandler(context, pipBoundsState);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static PipBoundsState providePipBoundsState() {
return new PipBoundsState();
}
- @SysUISingleton
+ @WMSingleton
@Provides
static PipTaskOrganizer providePipTaskOrganizer(Context context,
PipBoundsState pipBoundsState,
@@ -100,7 +108,7 @@ public abstract class TvPipModule {
Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
- pipSurfaceTransactionHelper, splitScreenOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer);
+ null /* menuActivityController */, pipSurfaceTransactionHelper, splitScreenOptional,
+ displayController, pipUiEventLogger, shellTaskOrganizer);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
index 56efffc29d85..294c749a2abe 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/TvWMShellModule.java
@@ -20,7 +20,7 @@ import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -40,10 +40,9 @@ import dagger.Provides;
* Provides dependencies from {@link com.android.wm.shell} which could be customized among different
* branches of SystemUI.
*/
-// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
@Module(includes = {WMShellBaseModule.class, TvPipModule.class})
public class TvWMShellModule {
- @SysUISingleton
+ @WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
DisplayController displayController, @Main Executor mainExecutor,
@@ -52,6 +51,8 @@ public class TvWMShellModule {
transactionPool);
}
+ @WMSingleton
+ @Provides
static SplitScreen provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController displayImeController, @Main Handler handler,
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 9281a090fd97..f896891c5039 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -64,15 +64,14 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
+import com.android.wm.shell.ShellDump;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEvents;
import com.android.wm.shell.onehanded.OneHandedGestureHandler.OneHandedGestureEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
-import com.android.wm.shell.pip.phone.PipUtils;
+import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -101,7 +100,6 @@ public final class WMShell extends SystemUI
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
- private final DisplayImeController mDisplayImeController;
private final InputConsumerController mInputConsumerController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final TaskStackChangeListeners mTaskStackChangeListeners;
@@ -111,10 +109,9 @@ public final class WMShell extends SystemUI
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- // Inject the organizer directly in case the optionals aren't loaded to depend on it. There
- // are non-optional windowing features like FULLSCREEN.
- private final ShellTaskOrganizer mShellTaskOrganizer;
private final ProtoTracer mProtoTracer;
+ private final Optional<ShellDump> mShellDump;
+
private boolean mIsSysUiStateValid;
private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
@@ -126,40 +123,34 @@ public final class WMShell extends SystemUI
InputConsumerController inputConsumerController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
TaskStackChangeListeners taskStackChangeListeners,
- DisplayImeController displayImeController,
NavigationModeController navigationModeController,
ScreenLifecycle screenLifecycle,
SysUiState sysUiState,
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- ShellTaskOrganizer shellTaskOrganizer,
- ProtoTracer protoTracer) {
+ ProtoTracer protoTracer,
+ Optional<ShellDump> shellDump) {
super(context);
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
mInputConsumerController = inputConsumerController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTaskStackChangeListeners = taskStackChangeListeners;
- mDisplayImeController = displayImeController;
mNavigationModeController = navigationModeController;
mScreenLifecycle = screenLifecycle;
mSysUiState = sysUiState;
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
- mShellTaskOrganizer = shellTaskOrganizer;
mProtoTracer = protoTracer;
mProtoTracer.add(this);
+ mShellDump = shellDump;
}
@Override
public void start() {
mCommandQueue.addCallback(this);
- // This is to prevent circular init problem by separating registration step out of its
- // constructor. And make sure the initialization of DisplayImeController won't depend on
- // specific feature anymore.
- mDisplayImeController.startMonitorDisplays();
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
@@ -167,9 +158,6 @@ public final class WMShell extends SystemUI
@VisibleForTesting
void initPip(Pip pip) {
- if (!PipUtils.hasSystemFeature(mContext)) {
- return;
- }
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
@Override
public void showPictureInPictureMenu() {
@@ -204,6 +192,7 @@ public final class WMShell extends SystemUI
}
});
+ // TODO: Move this into the shell
// Handle for system task stack changes.
mTaskStackChangeListeners.registerTaskStackListener(
new TaskStackChangeListener() {
@@ -222,8 +211,7 @@ public final class WMShell extends SystemUI
@Override
public void onActivityUnpinned() {
final Pair<ComponentName, Integer> topPipActivityInfo =
- PipUtils.getTopPipActivity(
- mContext, ActivityManager.getService());
+ PipUtils.getTopPipActivity(mContext);
final ComponentName topActivity = topPipActivityInfo.first;
pip.onActivityUnpinned(topActivity);
mInputConsumerController.unregisterInputConsumer();
@@ -420,7 +408,7 @@ public final class WMShell extends SystemUI
return;
}
// Dump WMShell stuff here if no commands were handled
- mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
+ mShellDump.ifPresent((shellDump) -> shellDump.dump(pw));
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
index 09678b5d1772..bdca503f40c1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -18,34 +18,40 @@ package com.android.systemui.wmshell;
import android.app.IActivityManager;
import android.content.Context;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.os.Handler;
-import android.util.DisplayMetrics;
import android.view.IWindowManager;
+import android.view.WindowManager;
import com.android.internal.logging.UiEventLogger;
-import com.android.systemui.bubbles.Bubbles;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.util.DeviceConfigProxy;
+import com.android.wm.shell.ShellDump;
+import com.android.wm.shell.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.AnimationThread;
import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.HandlerExecutor;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
-import com.android.wm.shell.pip.phone.PipMediaController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -59,41 +65,74 @@ import dagger.Provides;
* Provides basic dependencies from {@link com.android.wm.shell}, the dependencies declared here
* should be shared among different branches of SystemUI.
*/
-// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
@Module
public abstract class WMShellBaseModule {
- @SysUISingleton
+
+ @WMSingleton
+ @Provides
+ static ShellInit provideShellInit(DisplayImeController displayImeController,
+ DragAndDropController dragAndDropController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ Optional<SplitScreen> splitScreenOptional) {
+ return new ShellInit(displayImeController,
+ dragAndDropController,
+ shellTaskOrganizer,
+ splitScreenOptional);
+ }
+
+ /**
+ * Note, this is only optional because we currently pass this to the SysUI component scope and
+ * for non-primary users, we may inject a null-optional for that dependency.
+ */
+ @WMSingleton
+ @Provides
+ static Optional<ShellDump> provideShellDump(ShellTaskOrganizer shellTaskOrganizer,
+ Optional<SplitScreen> splitScreenOptional,
+ Optional<Pip> pipOptional,
+ Optional<OneHanded> oneHandedOptional) {
+ return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional,
+ oneHandedOptional));
+ }
+
+ @WMSingleton
@Provides
static TransactionPool provideTransactionPool() {
return new TransactionPool();
}
- @SysUISingleton
+ @WMSingleton
@Provides
static DisplayController provideDisplayController(Context context, @Main Handler handler,
IWindowManager wmService) {
return new DisplayController(context, handler, wmService);
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static DeviceConfigProxy provideDeviceConfigProxy() {
- return new DeviceConfigProxy();
+ static DragAndDropController provideDragAndDropController(Context context,
+ DisplayController displayController) {
+ return new DragAndDropController(context, displayController);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static InputConsumerController provideInputConsumerController() {
return InputConsumerController.getPipInputConsumer();
}
- @SysUISingleton
+ @WMSingleton
@Provides
static FloatingContentCoordinator provideFloatingContentCoordinator() {
return new FloatingContentCoordinator();
}
- @SysUISingleton
+ @WMSingleton
+ @Provides
+ static WindowManagerShellWrapper provideWindowManagerShellWrapper() {
+ return new WindowManagerShellWrapper();
+ }
+
+ @WMSingleton
@Provides
static PipAppOpsListener providePipAppOpsListener(Context context,
IActivityManager activityManager,
@@ -101,76 +140,77 @@ public abstract class WMShellBaseModule {
return new PipAppOpsListener(context, activityManager, pipTouchHandler.getMotionHelper());
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipMediaController providePipMediaController(Context context,
- IActivityManager activityManager) {
- return new PipMediaController(context, activityManager);
+ static PipMediaController providePipMediaController(Context context) {
+ return new PipMediaController(context);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static PipUiEventLogger providePipUiEventLogger(UiEventLogger uiEventLogger,
PackageManager packageManager) {
return new PipUiEventLogger(uiEventLogger, packageManager);
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipSurfaceTransactionHelper providesPipSurfaceTransactionHelper(Context context) {
+ static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
return new PipSurfaceTransactionHelper(context);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static SystemWindows provideSystemWindows(DisplayController displayController,
IWindowManager wmService) {
return new SystemWindows(displayController, wmService);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static SyncTransactionQueue provideSyncTransactionQueue(@Main Handler handler,
TransactionPool pool) {
return new SyncTransactionQueue(pool, handler);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue,
- @Main Handler handler, TransactionPool transactionPool) {
- ShellTaskOrganizer organizer = new ShellTaskOrganizer(syncQueue, transactionPool,
- new HandlerExecutor(handler), AnimationThread.instance().getExecutor());
- organizer.registerOrganizer();
- return organizer;
- }
-
- @SysUISingleton
- @Provides
- static WindowManagerShellWrapper provideWindowManagerShellWrapper() {
- return new WindowManagerShellWrapper();
- }
-
- @SysUISingleton
- @Provides
- static FlingAnimationUtils.Builder provideFlingAnimationUtilsBuilder(
- DisplayMetrics displayMetrics) {
- return new FlingAnimationUtils.Builder(displayMetrics);
+ ShellExecutor mainExecutor, TransactionPool transactionPool) {
+ return new ShellTaskOrganizer(syncQueue, transactionPool,
+ mainExecutor, AnimationThread.instance().getExecutor());
}
@BindsOptionalOf
- abstract Pip optionalPip();
-
- @BindsOptionalOf
abstract SplitScreen optionalSplitScreen();
- @BindsOptionalOf
- abstract Bubbles optionalBubbles();
+ @WMSingleton
+ @Provides
+ static Optional<Bubbles> provideBubbles(Context context,
+ FloatingContentCoordinator floatingContentCoordinator,
+ IStatusBarService statusBarService,
+ WindowManager windowManager,
+ WindowManagerShellWrapper windowManagerShellWrapper,
+ LauncherApps launcherApps,
+ UiEventLogger uiEventLogger,
+ @Main Handler mainHandler,
+ ShellTaskOrganizer organizer) {
+ return Optional.of(BubbleController.create(context, null /* synchronizer */,
+ floatingContentCoordinator, statusBarService, windowManager,
+ windowManagerShellWrapper, launcherApps, uiEventLogger, mainHandler, organizer));
+ }
- @SysUISingleton
+ @WMSingleton
@Provides
static Optional<OneHanded> provideOneHandedController(Context context,
DisplayController displayController) {
return Optional.ofNullable(OneHandedController.create(context, displayController));
}
+
+ @WMSingleton
+ @Provides
+ static ShellExecutor provideMainShellExecutor(@Main Handler handler) {
+ return new HandlerExecutor(handler);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
index 975757a4c259..0f8fb7bbd0ec 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellModule.java
@@ -20,25 +20,26 @@ import android.content.Context;
import android.os.Handler;
import android.view.IWindowManager;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.WMSingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipBoundsHandler;
import com.android.wm.shell.pip.PipBoundsState;
+import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipAppOpsListener;
import com.android.wm.shell.pip.phone.PipController;
-import com.android.wm.shell.pip.phone.PipMediaController;
import com.android.wm.shell.pip.phone.PipMenuActivityController;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreen;
@@ -54,10 +55,9 @@ import dagger.Provides;
* Provides dependencies from {@link com.android.wm.shell} which could be customized among different
* branches of SystemUI.
*/
-// TODO(b/162923491): Move most of these dependencies into WMSingleton scope.
@Module(includes = WMShellBaseModule.class)
public class WMShellModule {
- @SysUISingleton
+ @WMSingleton
@Provides
static DisplayImeController provideDisplayImeController(IWindowManager wmService,
DisplayController displayController, @Main Executor mainExecutor,
@@ -66,25 +66,7 @@ public class WMShellModule {
transactionPool);
}
- @SysUISingleton
- @Provides
- static Pip providePipController(Context context,
- DisplayController displayController,
- PipAppOpsListener pipAppOpsListener,
- PipBoundsHandler pipBoundsHandler,
- PipBoundsState pipBoundsState,
- PipMediaController pipMediaController,
- PipMenuActivityController pipMenuActivityController,
- PipTaskOrganizer pipTaskOrganizer,
- PipTouchHandler pipTouchHandler,
- WindowManagerShellWrapper windowManagerShellWrapper) {
- return new PipController(context, displayController,
- pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController,
- pipMenuActivityController, pipTaskOrganizer, pipTouchHandler,
- windowManagerShellWrapper);
- }
-
- @SysUISingleton
+ @WMSingleton
@Provides
static SplitScreen provideSplitScreen(Context context,
DisplayController displayController, SystemWindows systemWindows,
@@ -95,28 +77,43 @@ public class WMShellModule {
displayImeController, handler, transactionPool, shellTaskOrganizer, syncQueue);
}
- @SysUISingleton
+ @WMSingleton
+ @Provides
+ static Optional<Pip> providePip(Context context, DisplayController displayController,
+ PipAppOpsListener pipAppOpsListener, PipBoundsHandler pipBoundsHandler,
+ PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ PipMenuActivityController pipMenuActivityController, PipTaskOrganizer pipTaskOrganizer,
+ PipTouchHandler pipTouchHandler, WindowManagerShellWrapper windowManagerShellWrapper,
+ ShellExecutor mainExecutor) {
+ return Optional.ofNullable(PipController.create(context, displayController,
+ pipAppOpsListener, pipBoundsHandler, pipBoundsState, pipMediaController,
+ pipMenuActivityController, pipTaskOrganizer, pipTouchHandler,
+ windowManagerShellWrapper, mainExecutor));
+ }
+
+ @WMSingleton
@Provides
static PipBoundsState providePipBoundsState() {
return new PipBoundsState();
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipBoundsHandler providesPipBoundsHandler(Context context) {
- return new PipBoundsHandler(context);
+ static PipBoundsHandler providesPipBoundsHandler(Context context,
+ PipBoundsState pipBoundsState) {
+ return new PipBoundsHandler(context, pipBoundsState);
}
- @SysUISingleton
+ @WMSingleton
@Provides
static PipMenuActivityController providesPipMenuActivityController(Context context,
- PipMediaController pipMediaController, PipTaskOrganizer pipTaskOrganizer) {
- return new PipMenuActivityController(context, pipMediaController, pipTaskOrganizer);
+ PipMediaController pipMediaController, SystemWindows systemWindows) {
+ return new PipMenuActivityController(context, pipMediaController, systemWindows);
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipTouchHandler providesPipTouchHandler(Context context,
+ static PipTouchHandler providePipTouchHandler(Context context,
PipMenuActivityController menuActivityController, PipBoundsHandler pipBoundsHandler,
PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer,
@@ -126,16 +123,17 @@ public class WMShellModule {
pipBoundsState, pipTaskOrganizer, floatingContentCoordinator, pipUiEventLogger);
}
- @SysUISingleton
+ @WMSingleton
@Provides
- static PipTaskOrganizer providesPipTaskOrganizer(Context context,
+ static PipTaskOrganizer providePipTaskOrganizer(Context context,
PipBoundsState pipBoundsState,
PipBoundsHandler pipBoundsHandler,
+ PipMenuActivityController menuActivityController,
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreen> splitScreenOptional, DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer) {
return new PipTaskOrganizer(context, pipBoundsState, pipBoundsHandler,
- pipSurfaceTransactionHelper, splitScreenOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer);
+ menuActivityController, pipSurfaceTransactionHelper, splitScreenOptional,
+ displayController, pipUiEventLogger, shellTaskOrganizer);
}
}