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/KeyguardHostView.java13
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java114
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java19
-rw-r--r--packages/SystemUI/src/com/android/keyguard/PasswordTextView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/CornerHandleView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java187
-rw-r--r--packages/SystemUI/src/com/android/systemui/Interpolators.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/Prefs.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java276
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java282
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java130
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java281
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt187
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt168
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt82
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt126
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt146
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt119
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt (renamed from packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt)14
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java596
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java246
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java126
-rw-r--r--packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java124
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.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/NotifInteractionLog.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java414
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiState.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java78
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetail.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSHost.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java259
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java164
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java220
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/TileLayout.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/QSEditEvent.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java320
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java108
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java127
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java95
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java)177
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java183
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java376
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt146
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java156
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListItem.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java75
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java156
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java313
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java18
221 files changed, 8060 insertions, 3480 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index aa2fe3c7f8fc..57b3761c294f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -16,7 +16,6 @@
package com.android.keyguard;
-import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -31,6 +30,8 @@ import android.util.Log;
import android.view.KeyEvent;
import android.widget.FrameLayout;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -101,7 +102,8 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback {
public static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardViewBase";
- private KeyguardSecurityContainer mSecurityContainer;
+ @VisibleForTesting
+ protected KeyguardSecurityContainer mSecurityContainer;
public KeyguardHostView(Context context) {
this(context, null);
@@ -446,4 +448,11 @@ public class KeyguardHostView extends FrameLayout implements SecurityCallback {
public SecurityMode getCurrentSecurityMode() {
return mSecurityContainer.getCurrentSecurityMode();
}
+
+ /**
+ * When bouncer was visible and is starting to become hidden.
+ */
+ public void onStartingToHide() {
+ mSecurityContainer.onStartingToHide();
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 718bcf16c832..65bf7e6e5025 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -152,6 +152,11 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
mImm.hideSoftInputFromWindow(getWindowToken(), 0);
}
+ @Override
+ public void onStartingToHide() {
+ mImm.hideSoftInputFromWindow(getWindowToken(), 0);
+ }
+
private void updateSwitchImeButton() {
// If there's more than one IME, enable the IME switcher button
final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 5c3d17ce0e2b..b99fb057ee65 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -19,6 +19,7 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.ViewRootImpl.sNewInsetsMode;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static com.android.systemui.DejankUtils.whitelistIpcs;
@@ -30,12 +31,14 @@ import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
+import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -44,6 +47,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -52,6 +56,9 @@ import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.internal.logging.MetricsLogger;
+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.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -63,6 +70,8 @@ import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.InjectionInflationController;
+import java.util.List;
+
public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardSecurityView";
@@ -89,6 +98,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
// How much to scale the default slop by, to avoid accidental drags.
private static final float SLOP_SCALE = 4f;
+ private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
+
private KeyguardSecurityModel mSecurityModel;
private LockPatternUtils mLockPatternUtils;
@@ -114,6 +125,47 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
private boolean mIsDragging;
private float mStartTouchY = -1;
+ private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
+ new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
+
+ private final Rect mInitialBounds = new Rect();
+ private final Rect mFinalBounds = new Rect();
+
+ @Override
+ public void onPrepare(WindowInsetsAnimation animation) {
+ mSecurityViewFlipper.getBoundsOnScreen(mInitialBounds);
+ }
+
+ @Override
+ public WindowInsetsAnimation.Bounds onStart(WindowInsetsAnimation animation,
+ WindowInsetsAnimation.Bounds bounds) {
+ mSecurityViewFlipper.getBoundsOnScreen(mFinalBounds);
+ return bounds;
+ }
+
+ @Override
+ public WindowInsets onProgress(WindowInsets windowInsets,
+ List<WindowInsetsAnimation> list) {
+ int translationY = 0;
+ for (WindowInsetsAnimation animation : list) {
+ if ((animation.getTypeMask() & WindowInsets.Type.ime()) == 0) {
+ continue;
+ }
+ final int paddingBottom = (int) MathUtils.lerp(
+ mInitialBounds.bottom - mFinalBounds.bottom, 0,
+ animation.getInterpolatedFraction());
+ translationY += paddingBottom;
+ }
+ mSecurityViewFlipper.setTranslationY(translationY);
+ return windowInsets;
+ }
+
+ @Override
+ public void onEnd(WindowInsetsAnimation animation) {
+ mSecurityViewFlipper.setTranslationY(0);
+ }
+ };
+
// Used to notify the container when something interesting happens.
public interface SecurityCallback {
public boolean dismiss(boolean authenticated, int targetUserId,
@@ -131,6 +183,44 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
public void onCancelClicked();
}
+ @VisibleForTesting
+ public enum BouncerUiEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Default UiEvent used for variable initialization.")
+ UNKNOWN(0),
+
+ @UiEvent(doc = "Bouncer is dismissed using extended security access.")
+ BOUNCER_DISMISS_EXTENDED_ACCESS(413),
+
+ @UiEvent(doc = "Bouncer is dismissed using biometric.")
+ BOUNCER_DISMISS_BIOMETRIC(414),
+
+ @UiEvent(doc = "Bouncer is dismissed without security access.")
+ BOUNCER_DISMISS_NONE_SECURITY(415),
+
+ @UiEvent(doc = "Bouncer is dismissed using password security.")
+ BOUNCER_DISMISS_PASSWORD(416),
+
+ @UiEvent(doc = "Bouncer is dismissed using sim security access.")
+ BOUNCER_DISMISS_SIM(417),
+
+ @UiEvent(doc = "Bouncer is successfully unlocked using password.")
+ BOUNCER_PASSWORD_SUCCESS(418),
+
+ @UiEvent(doc = "An attempt to unlock bouncer using password has failed.")
+ BOUNCER_PASSWORD_FAILURE(419);
+
+ private final int mId;
+
+ BouncerUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
public KeyguardSecurityContainer(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -162,6 +252,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onResume(reason);
}
+ mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
updateBiometricRetry();
}
@@ -175,6 +266,14 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onPause();
}
+ mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
+ }
+
+ @Override
+ public void onStartingToHide() {
+ if (mCurrentSecuritySelection != SecurityMode.None) {
+ getSecurityView(mCurrentSecuritySelection).onStartingToHide();
+ }
}
@Override
@@ -333,7 +432,9 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
}
- protected void onFinishInflate() {
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
mSecurityViewFlipper = findViewById(R.id.view_flipper);
mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
}
@@ -516,17 +617,21 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
boolean finish = false;
boolean strongAuth = false;
int eventSubtype = -1;
+ BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
} else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
} else if (SecurityMode.None == mCurrentSecuritySelection) {
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (SecurityMode.None == securityMode) {
finish = true; // no security required
eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
} else {
showSecurityScreen(securityMode); // switch to the alternate security view
}
@@ -538,6 +643,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
strongAuth = true;
finish = true;
eventSubtype = BOUNCER_DISMISS_PASSWORD;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
break;
case SimPin:
@@ -548,6 +654,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
+ uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
} else {
showSecurityScreen(securityMode);
}
@@ -572,6 +679,9 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
}
+ if (uiEvent != BouncerUiEvent.UNKNOWN) {
+ sUiEventLogger.log(uiEvent);
+ }
if (finish) {
mSecurityCallback.finish(strongAuth, targetUserId);
}
@@ -677,6 +787,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
+ sUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
+ : BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
}
public void reset() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index 20b1e0d2c822..43cef3acf147 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -159,4 +159,9 @@ public interface KeyguardSecurityView {
default boolean disallowInterceptTouch(MotionEvent event) {
return false;
}
+
+ /**
+ * When bouncer was visible but is being dragged down or dismissed.
+ */
+ default void onStartingToHide() {};
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 367058fa58dd..a96ef91850df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -1301,6 +1301,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private FingerprintManager mFpm;
private FaceManager mFaceManager;
private boolean mFingerprintLockedOut;
+ private TelephonyManager mTelephonyManager;
/**
* When we receive a
@@ -1728,10 +1729,22 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
updateAirplaneModeState();
- TelephonyManager telephony =
+ mTelephonyManager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
- if (telephony != null) {
- telephony.listen(mPhoneStateListener, LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ if (mTelephonyManager != null) {
+ mTelephonyManager.listen(mPhoneStateListener,
+ LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ // Set initial sim states values.
+ for (int slot = 0; slot < mTelephonyManager.getActiveModemCount(); slot++) {
+ int state = mTelephonyManager.getSimState(slot);
+ int[] subIds = mSubscriptionManager.getSubscriptionIds(slot);
+ if (subIds != null) {
+ for (int subId : subIds) {
+ mHandler.obtainMessage(MSG_SIM_STATE_CHANGE, subId, slot, state)
+ .sendToTarget();
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 409ae3f3c7d6..c92174a0d8af 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -164,7 +164,9 @@ public class PasswordTextView extends View {
currentDrawPosition = getPaddingLeft();
}
} else {
- currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+ float maxRight = getWidth() - getPaddingRight() - totalDrawingWidth;
+ float center = getWidth() / 2f - totalDrawingWidth / 2f;
+ currentDrawPosition = center > 0 ? center : maxRight;
}
int length = mTextChars.size();
Rect bounds = getCharBounds();
diff --git a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
index 85ce313670e3..cf7ee3a5753f 100644
--- a/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
+++ b/packages/SystemUI/src/com/android/systemui/CornerHandleView.java
@@ -168,14 +168,14 @@ public class CornerHandleView extends View {
// Attempt to get the bottom corner radius, otherwise fall back on the generic or top
// values. If none are available, use the FALLBACK_RADIUS_DP.
int radius = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.rounded_corner_radius_bottom);
+ com.android.systemui.R.dimen.config_rounded_mask_size_bottom);
if (radius == 0) {
radius = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.rounded_corner_radius);
+ com.android.systemui.R.dimen.config_rounded_mask_size);
}
if (radius == 0) {
radius = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.rounded_corner_radius_top);
+ com.android.systemui.R.dimen.config_rounded_mask_size_top);
}
if (radius == 0) {
radius = (int) convertDpToPixel(FALLBACK_RADIUS_DP, mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b6152dae33d6..0af026eb3509 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -75,7 +75,6 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ChannelEditorDialogController;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
@@ -293,8 +292,6 @@ public class Dependency {
@Inject Lazy<RemoteInputQuickSettingsDisabler> mRemoteInputQuickSettingsDisabler;
@Inject Lazy<BubbleController> mBubbleController;
@Inject Lazy<NotificationEntryManager> mNotificationEntryManager;
- @Inject
- Lazy<NotificationAlertingManager> mNotificationAlertingManager;
@Inject Lazy<SensorPrivacyManager> mSensorPrivacyManager;
@Inject Lazy<AutoHideController> mAutoHideController;
@Inject Lazy<ForegroundServiceNotificationListener> mForegroundServiceNotificationListener;
@@ -493,7 +490,6 @@ public class Dependency {
mRemoteInputQuickSettingsDisabler::get);
mProviders.put(BubbleController.class, mBubbleController::get);
mProviders.put(NotificationEntryManager.class, mNotificationEntryManager::get);
- mProviders.put(NotificationAlertingManager.class, mNotificationAlertingManager::get);
mProviders.put(ForegroundServiceNotificationListener.class,
mForegroundServiceNotificationListener::get);
mProviders.put(ClockManager.class, mClockManager::get);
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 5442299881c0..71ec33e16e0e 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -16,9 +16,6 @@
package com.android.systemui;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
import android.os.HandlerThread;
@@ -27,17 +24,12 @@ import android.os.Trace;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.util.Size;
-import android.view.DisplayInfo;
import android.view.SurfaceHolder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.glwallpaper.EglHelper;
import com.android.systemui.glwallpaper.GLWallpaperRenderer;
import com.android.systemui.glwallpaper.ImageWallpaperRenderer;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.DozeParameters;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -53,16 +45,12 @@ public class ImageWallpaper extends WallpaperService {
// We delayed destroy render context that subsequent render requests have chance to cancel it.
// This is to avoid destroying then recreating render context in a very short time.
private static final int DELAY_FINISH_RENDERING = 1000;
- private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
- private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
- private static final boolean DEBUG = true;
- private final DozeParameters mDozeParameters;
+ private static final boolean DEBUG = false;
private HandlerThread mWorker;
@Inject
- public ImageWallpaper(DozeParameters dozeParameters) {
+ public ImageWallpaper() {
super();
- mDozeParameters = dozeParameters;
}
@Override
@@ -74,7 +62,7 @@ public class ImageWallpaper extends WallpaperService {
@Override
public Engine onCreateEngine() {
- return new GLEngine(this, mDozeParameters);
+ return new GLEngine();
}
@Override
@@ -84,7 +72,7 @@ public class ImageWallpaper extends WallpaperService {
mWorker = null;
}
- class GLEngine extends Engine implements GLWallpaperRenderer.SurfaceProxy, StateListener {
+ class GLEngine extends Engine {
// Surface is rejected if size below a threshold on some devices (ie. 8px on elfin)
// set min to 64 px (CTS covers this), please refer to ag/4867989 for detail.
@VisibleForTesting
@@ -94,40 +82,15 @@ public class ImageWallpaper extends WallpaperService {
private GLWallpaperRenderer mRenderer;
private EglHelper mEglHelper;
- private StatusBarStateController mController;
private final Runnable mFinishRenderingTask = this::finishRendering;
- private boolean mShouldStopTransition;
- private final DisplayInfo mDisplayInfo = new DisplayInfo();
- private final Object mMonitor = new Object();
- @VisibleForTesting
- boolean mIsHighEndGfx;
- private boolean mDisplayNeedsBlanking;
- private boolean mNeedTransition;
private boolean mNeedRedraw;
- // This variable can only be accessed in synchronized block.
- private boolean mWaitingForRendering;
- GLEngine(Context context, DozeParameters dozeParameters) {
- init(dozeParameters);
+ GLEngine() {
}
@VisibleForTesting
- GLEngine(DozeParameters dozeParameters, Handler handler) {
+ GLEngine(Handler handler) {
super(SystemClock::elapsedRealtime, handler);
- init(dozeParameters);
- }
-
- private void init(DozeParameters dozeParameters) {
- mIsHighEndGfx = ActivityManager.isHighEndGfx();
- mDisplayNeedsBlanking = dozeParameters.getDisplayNeedsBlanking();
- mNeedTransition = false;
-
- // We will preserve EGL context when we are in lock screen or aod
- // to avoid janking in following transition, we need to release when back to home.
- mController = Dependency.get(StatusBarStateController.class);
- if (mController != null) {
- mController.addCallback(this /* StateListener */);
- }
}
@Override
@@ -135,9 +98,8 @@ public class ImageWallpaper extends WallpaperService {
mEglHelper = getEglHelperInstance();
// Deferred init renderer because we need to get wallpaper by display context.
mRenderer = getRendererInstance();
- getDisplayContext().getDisplay().getDisplayInfo(mDisplayInfo);
setFixedSizeAllowed(true);
- setOffsetNotificationsEnabled(mNeedTransition);
+ setOffsetNotificationsEnabled(false);
updateSurfaceSize();
}
@@ -146,7 +108,7 @@ public class ImageWallpaper extends WallpaperService {
}
ImageWallpaperRenderer getRendererInstance() {
- return new ImageWallpaperRenderer(getDisplayContext(), this /* SurfaceProxy */);
+ return new ImageWallpaperRenderer(getDisplayContext());
}
private void updateSurfaceSize() {
@@ -157,79 +119,13 @@ public class ImageWallpaper extends WallpaperService {
holder.setFixedSize(width, height);
}
- /**
- * Check if necessary to stop transition with current wallpaper on this device. <br/>
- * This should only be invoked after {@link #onSurfaceCreated(SurfaceHolder)}}
- * is invoked since it needs display context and surface frame size.
- * @return true if need to stop transition.
- */
- @VisibleForTesting
- boolean checkIfShouldStopTransition() {
- int orientation = getDisplayContext().getResources().getConfiguration().orientation;
- Rect frame = getSurfaceHolder().getSurfaceFrame();
- Rect display = new Rect();
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- display.set(0, 0, mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight);
- } else {
- display.set(0, 0, mDisplayInfo.logicalHeight, mDisplayInfo.logicalWidth);
- }
- return mNeedTransition
- && (frame.width() < display.width() || frame.height() < display.height());
- }
-
- @Override
- public void onOffsetsChanged(float xOffset, float yOffset, float xOffsetStep,
- float yOffsetStep, int xPixelOffset, int yPixelOffset) {
- if (mWorker == null) return;
- mWorker.getThreadHandler().post(() -> mRenderer.updateOffsets(xOffset, yOffset));
- }
-
- @Override
- public void onAmbientModeChanged(boolean inAmbientMode, long animationDuration) {
- if (mWorker == null || !mNeedTransition) return;
- final long duration = mShouldStopTransition ? 0 : animationDuration;
- if (DEBUG) {
- Log.d(TAG, "onAmbientModeChanged: inAmbient=" + inAmbientMode
- + ", duration=" + duration
- + ", mShouldStopTransition=" + mShouldStopTransition);
- }
- mWorker.getThreadHandler().post(
- () -> mRenderer.updateAmbientMode(inAmbientMode, duration));
- if (inAmbientMode && animationDuration == 0) {
- // This means that we are transiting from home to aod, to avoid
- // race condition between window visibility and transition,
- // we don't return until the transition is finished. See b/136643341.
- waitForBackgroundRendering();
- }
- }
-
@Override
public boolean shouldZoomOutWallpaper() {
return true;
}
- private void waitForBackgroundRendering() {
- synchronized (mMonitor) {
- try {
- mWaitingForRendering = true;
- for (int patience = 1; mWaitingForRendering; patience++) {
- mMonitor.wait(INTERVAL_WAIT_FOR_RENDERING);
- mWaitingForRendering &= patience < PATIENCE_WAIT_FOR_RENDERING;
- }
- } catch (InterruptedException ex) {
- } finally {
- mWaitingForRendering = false;
- }
- }
- }
-
@Override
public void onDestroy() {
- if (mController != null) {
- mController.removeCallback(this /* StateListener */);
- }
- mController = null;
-
mWorker.getThreadHandler().post(() -> {
mRenderer.finish();
mRenderer = null;
@@ -240,7 +136,6 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onSurfaceCreated(SurfaceHolder holder) {
- mShouldStopTransition = checkIfShouldStopTransition();
if (mWorker == null) return;
mWorker.getThreadHandler().post(() -> {
mEglHelper.init(holder, needSupportWideColorGamut());
@@ -251,32 +146,13 @@ public class ImageWallpaper extends WallpaperService {
@Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (mWorker == null) return;
- mWorker.getThreadHandler().post(() -> {
- mRenderer.onSurfaceChanged(width, height);
- mNeedRedraw = true;
- });
+ mWorker.getThreadHandler().post(() -> mRenderer.onSurfaceChanged(width, height));
}
@Override
public void onSurfaceRedrawNeeded(SurfaceHolder holder) {
if (mWorker == null) return;
- if (DEBUG) {
- Log.d(TAG, "onSurfaceRedrawNeeded: mNeedRedraw=" + mNeedRedraw);
- }
-
- mWorker.getThreadHandler().post(() -> {
- if (mNeedRedraw) {
- drawFrame();
- mNeedRedraw = false;
- }
- });
- }
-
- @Override
- public void onVisibilityChanged(boolean visible) {
- if (DEBUG) {
- Log.d(TAG, "wallpaper visibility changes: " + visible);
- }
+ mWorker.getThreadHandler().post(this::drawFrame);
}
private void drawFrame() {
@@ -285,15 +161,6 @@ public class ImageWallpaper extends WallpaperService {
postRender();
}
- @Override
- public void onStatePostChange() {
- // When back to home, we try to release EGL, which is preserved in lock screen or aod.
- if (mWorker != null && mController.getState() == StatusBarState.SHADE) {
- mWorker.getThreadHandler().post(this::scheduleFinishRendering);
- }
- }
-
- @Override
public void preRender() {
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#preRender");
@@ -330,7 +197,6 @@ public class ImageWallpaper extends WallpaperService {
}
}
- @Override
public void requestRender() {
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#requestRender");
@@ -355,27 +221,13 @@ public class ImageWallpaper extends WallpaperService {
}
}
- @Override
public void postRender() {
// This method should only be invoked from worker thread.
Trace.beginSection("ImageWallpaper#postRender");
- notifyWaitingThread();
scheduleFinishRendering();
Trace.endSection();
}
- private void notifyWaitingThread() {
- synchronized (mMonitor) {
- if (mWaitingForRendering) {
- try {
- mWaitingForRendering = false;
- mMonitor.notify();
- } catch (IllegalMonitorStateException ex) {
- }
- }
- }
- }
-
private void cancelFinishRenderingTask() {
if (mWorker == null) return;
mWorker.getThreadHandler().removeCallbacks(mFinishRenderingTask);
@@ -391,18 +243,11 @@ public class ImageWallpaper extends WallpaperService {
Trace.beginSection("ImageWallpaper#finishRendering");
if (mEglHelper != null) {
mEglHelper.destroyEglSurface();
- if (!needPreserveEglContext()) {
- mEglHelper.destroyEglContext();
- }
+ mEglHelper.destroyEglContext();
}
Trace.endSection();
}
- private boolean needPreserveEglContext() {
- return mNeedTransition && mController != null
- && mController.getState() == StatusBarState.KEYGUARD;
- }
-
private boolean needSupportWideColorGamut() {
return mRenderer.isWcgContent();
}
@@ -411,16 +256,6 @@ public class ImageWallpaper extends WallpaperService {
protected void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
super.dump(prefix, fd, out, args);
out.print(prefix); out.print("Engine="); out.println(this);
- out.print(prefix); out.print("isHighEndGfx="); out.println(mIsHighEndGfx);
- out.print(prefix); out.print("displayNeedsBlanking=");
- out.println(mDisplayNeedsBlanking);
- out.print(prefix); out.print("displayInfo="); out.print(mDisplayInfo);
- out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition);
- out.print(prefix); out.print("mShouldStopTransition=");
- out.println(mShouldStopTransition);
- out.print(prefix); out.print("StatusBarState=");
- out.println(mController != null ? mController.getState() : "null");
-
out.print(prefix); out.print("valid surface=");
out.println(getSurfaceHolder() != null && getSurfaceHolder().getSurface() != null
? getSurfaceHolder().getSurface().isValid()
diff --git a/packages/SystemUI/src/com/android/systemui/Interpolators.java b/packages/SystemUI/src/com/android/systemui/Interpolators.java
index 6923079dd5c4..2eba9521b9e7 100644
--- a/packages/SystemUI/src/com/android/systemui/Interpolators.java
+++ b/packages/SystemUI/src/com/android/systemui/Interpolators.java
@@ -54,6 +54,11 @@ public class Interpolators {
public static final Interpolator PANEL_CLOSE_ACCELERATED
= new PathInterpolator(0.3f, 0, 0.5f, 1);
public static final Interpolator BOUNCE = new BounceInterpolator();
+ /**
+ * For state transitions on the control panel that lives in GlobalActions.
+ */
+ public static final Interpolator CONTROL_STATE = new PathInterpolator(0.4f, 0f, 0.2f,
+ 1.0f);
/**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 6aa2326c388a..87990cd3bffa 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -21,11 +21,25 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import com.android.systemui.settings.CurrentUserContextTracker;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
import java.util.Set;
+/**
+ * A helper class to store simple preferences for SystemUI. Its main use case is things such as
+ * feature education, e.g. "has the user seen this tooltip".
+ *
+ * As of this writing, feature education settings are *intentionally exempted* from backup and
+ * restore because there is not a great way to know which subset of features the user _should_ see
+ * again if, for instance, they are coming from multiple OSes back or switching OEMs.
+ *
+ * NOTE: Clients of this class should take care to pass in the correct user context when querying
+ * settings, otherwise you will always read/write for user 0 which is almost never what you want.
+ * See {@link CurrentUserContextTracker} for a simple way to get the current context
+ */
public final class Prefs {
private Prefs() {} // no instantation
@@ -109,6 +123,8 @@ public final class Prefs {
String HAS_SEEN_BUBBLES_EDUCATION = "HasSeenBubblesOnboarding";
String HAS_SEEN_BUBBLES_MANAGE_EDUCATION = "HasSeenBubblesManageOnboarding";
String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
+ /** Tracks whether the user has seen the onboarding screen for priority conversations */
+ String HAS_SEEN_PRIORITY_ONBOARDING = "HasSeenPriorityOnboarding";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 8df3dd2ad845..7861211e802d 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -54,8 +54,10 @@ import android.graphics.Region;
import android.graphics.drawable.VectorDrawable;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.provider.Settings.Secure;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -298,13 +300,15 @@ 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);
- mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
+ mBroadcastDispatcher.registerReceiver(mUserSwitchIntentReceiver, filter,
+ new HandlerExecutor(mHandler), UserHandle.ALL);
mIsRegistered = true;
} else {
mMainHandler.post(() -> mTunerService.removeTunable(this));
@@ -313,7 +317,7 @@ public class ScreenDecorations extends SystemUI implements Tunable {
mColorInversionSetting.setListening(false);
}
- mBroadcastDispatcher.unregisterReceiver(mIntentReceiver);
+ mBroadcastDispatcher.unregisterReceiver(mUserSwitchIntentReceiver);
mIsRegistered = false;
}
}
@@ -503,17 +507,16 @@ public class ScreenDecorations extends SystemUI implements Tunable {
}
}
- private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+ private final BroadcastReceiver mUserSwitchIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action.equals(Intent.ACTION_USER_SWITCHED)) {
- int newUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
- ActivityManager.getCurrentUser());
- // update color inversion setting to the new user
- mColorInversionSetting.setUserId(newUserId);
- updateColorInversion(mColorInversionSetting.getValue());
+ int newUserId = ActivityManager.getCurrentUser();
+ if (DEBUG) {
+ Log.d(TAG, "UserSwitched newUserId=" + newUserId);
}
+ // update color inversion setting to the new user
+ mColorInversionSetting.setUserId(newUserId);
+ updateColorInversion(mColorInversionSetting.getValue());
}
};
@@ -945,7 +948,12 @@ public class ScreenDecorations extends SystemUI implements Tunable {
int dw = flipped ? lh : lw;
int dh = flipped ? lw : lh;
- mBoundingPath.set(DisplayCutout.pathFromResources(getResources(), dw, dh));
+ Path path = DisplayCutout.pathFromResources(getResources(), dw, dh);
+ if (path != null) {
+ mBoundingPath.set(path);
+ } else {
+ mBoundingPath.reset();
+ }
Matrix m = new Matrix();
transformPhysicalToLogicalCoordinates(mInfo.rotation, dw, dh, m);
mBoundingPath.transform(m);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index cbdae4e6fe63..c84701c9512e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -139,7 +139,7 @@ public class SystemUIApplication extends Application implements
*/
public void startServicesIfNeeded() {
- String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
+ String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}
@@ -150,8 +150,8 @@ public class SystemUIApplication extends Application implements
* <p>This method must only be called from the main thread.</p>
*/
void startSecondaryUserServicesIfNeeded() {
- String[] names =
- getResources().getStringArray(R.array.config_systemUIServiceComponentsPerUser);
+ String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponentsPerUser(
+ getResources());
startServicesIfNeeded(/* metricsPrefix= */ "StartSecondaryServices", names);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index fb40774a1f5c..be82a2d5325b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -18,6 +18,7 @@ package com.android.systemui;
import android.annotation.NonNull;
import android.content.Context;
+import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
@@ -120,6 +121,16 @@ public class SystemUIFactory {
return mRootComponent;
}
+ /** Returns the list of system UI components that should be started. */
+ public String[] getSystemUIServiceComponents(Resources resources) {
+ return resources.getStringArray(R.array.config_systemUIServiceComponents);
+ }
+
+ /** Returns the list of system UI components that should be started per user. */
+ public String[] getSystemUIServiceComponentsPerUser(Resources resources) {
+ return resources.getStringArray(R.array.config_systemUIServiceComponentsPerUser);
+ }
+
/**
* Creates an instance of ScreenshotNotificationSmartActionsProvider.
* This method is overridden in vendor specific implementation of Sys UI.
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 7262f8caac89..73dfd32d03a2 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -16,6 +16,10 @@
package com.android.systemui.accessibility;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+
+import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
+
import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent;
import android.app.RemoteAction;
@@ -23,6 +27,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.Configuration;
import android.graphics.drawable.Icon;
import android.hardware.input.InputManager;
import android.os.Handler;
@@ -30,6 +35,7 @@ import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
import android.view.Display;
import android.view.IWindowManager;
@@ -41,12 +47,15 @@ import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.R;
+import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.Locale;
+
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -56,7 +65,6 @@ import javax.inject.Singleton;
@Singleton
public class SystemActions extends SystemUI {
private static final String TAG = "SystemActions";
- // TODO(b/147916452): add implementation on launcher side to register this action.
/**
* Action ID to go back.
@@ -94,12 +102,6 @@ public class SystemActions extends SystemUI {
AccessibilityService.GLOBAL_ACTION_POWER_DIALOG; // = 6
/**
- * Action ID to toggle docking the current app's window
- */
- private static final int SYSTEM_ACTION_ID_TOGGLE_SPLIT_SCREEN =
- AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN; // = 7
-
- /**
* Action ID to lock the screen
*/
private static final int SYSTEM_ACTION_ID_LOCK_SCREEN =
@@ -112,13 +114,22 @@ public class SystemActions extends SystemUI {
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT; // = 9
/**
- * Action ID to show accessibility menu
+ * Action ID to trigger the accessibility button
*/
- private static final int SYSTEM_ACTION_ID_ACCESSIBILITY_MENU = 10;
+ public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON =
+ AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON; // 11
+
+ /**
+ * Action ID to show accessibility button's menu of services
+ */
+ public static final int SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER =
+ AccessibilityService.GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER; // 12
private Recents mRecents;
private StatusBar mStatusBar;
private SystemActionsBroadcastReceiver mReceiver;
+ private Locale mLocale;
+ private AccessibilityManager mA11yManager;
@Inject
public SystemActions(Context context) {
@@ -126,96 +137,139 @@ public class SystemActions extends SystemUI {
mRecents = Dependency.get(Recents.class);
mStatusBar = Dependency.get(StatusBar.class);
mReceiver = new SystemActionsBroadcastReceiver();
+ mLocale = mContext.getResources().getConfiguration().getLocales().get(0);
+ mA11yManager = (AccessibilityManager) mContext.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
}
@Override
public void start() {
mContext.registerReceiverForAllUsers(mReceiver, mReceiver.createIntentFilter(), null, null);
+ registerActions();
+ }
- // TODO(b/148087487): update the icon used below to a valid one
- RemoteAction actionBack = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_back_label),
- mContext.getString(R.string.accessibility_system_action_back_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_BACK));
- RemoteAction actionHome = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_home_label),
- mContext.getString(R.string.accessibility_system_action_home_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_HOME));
-
- RemoteAction actionRecents = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_recents_label),
- mContext.getString(R.string.accessibility_system_action_recents_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS));
-
- RemoteAction actionNotifications = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_notifications_label),
- mContext.getString(R.string.accessibility_system_action_notifications_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS));
-
- RemoteAction actionQuickSettings = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_quick_settings_label),
- mContext.getString(R.string.accessibility_system_action_quick_settings_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS));
-
- RemoteAction actionPowerDialog = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_power_dialog_label),
- mContext.getString(R.string.accessibility_system_action_power_dialog_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG));
-
- RemoteAction actionToggleSplitScreen = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_toggle_split_screen_label),
- mContext.getString(R.string.accessibility_system_action_toggle_split_screen_label),
- mReceiver.createPendingIntent(
- mContext,
- SystemActionsBroadcastReceiver.INTENT_ACTION_TOGGLE_SPLIT_SCREEN));
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ final Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
+ if (!locale.equals(mLocale)) {
+ mLocale = locale;
+ registerActions();
+ }
+ }
- RemoteAction actionLockScreen = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_lock_screen_label),
- mContext.getString(R.string.accessibility_system_action_lock_screen_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN));
+ private void registerActions() {
+ RemoteAction actionBack = createRemoteAction(
+ R.string.accessibility_system_action_back_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_BACK);
+
+ RemoteAction actionHome = createRemoteAction(
+ R.string.accessibility_system_action_home_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_HOME);
+
+ RemoteAction actionRecents = createRemoteAction(
+ R.string.accessibility_system_action_recents_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS);
+
+ RemoteAction actionNotifications = createRemoteAction(
+ R.string.accessibility_system_action_notifications_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS);
+
+ RemoteAction actionQuickSettings = createRemoteAction(
+ R.string.accessibility_system_action_quick_settings_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS);
+
+ RemoteAction actionPowerDialog = createRemoteAction(
+ R.string.accessibility_system_action_power_dialog_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG);
+
+ RemoteAction actionLockScreen = createRemoteAction(
+ R.string.accessibility_system_action_lock_screen_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN);
+
+ RemoteAction actionTakeScreenshot = createRemoteAction(
+ R.string.accessibility_system_action_screenshot_label,
+ SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT);
+
+ mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
+ mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
+ mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
+ mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
+ mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
+ mA11yManager.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
+ mA11yManager.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
+ mA11yManager.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
+ }
- RemoteAction actionTakeScreenshot = new RemoteAction(
- Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_screenshot_label),
- mContext.getString(R.string.accessibility_system_action_screenshot_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT));
+ /**
+ * Register a system action.
+ * @param actionId the action ID to register.
+ */
+ public void register(int actionId) {
+ int labelId;
+ String intent;
+ switch (actionId) {
+ case SYSTEM_ACTION_ID_BACK:
+ labelId = R.string.accessibility_system_action_back_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_BACK;
+ break;
+ case SYSTEM_ACTION_ID_HOME:
+ labelId = R.string.accessibility_system_action_home_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_HOME;
+ break;
+ case SYSTEM_ACTION_ID_RECENTS:
+ labelId = R.string.accessibility_system_action_recents_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_RECENTS;
+ break;
+ case SYSTEM_ACTION_ID_NOTIFICATIONS:
+ labelId = R.string.accessibility_system_action_notifications_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_NOTIFICATIONS;
+ break;
+ case SYSTEM_ACTION_ID_QUICK_SETTINGS:
+ labelId = R.string.accessibility_system_action_quick_settings_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_QUICK_SETTINGS;
+ break;
+ case SYSTEM_ACTION_ID_POWER_DIALOG:
+ labelId = R.string.accessibility_system_action_power_dialog_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_POWER_DIALOG;
+ break;
+ case SYSTEM_ACTION_ID_LOCK_SCREEN:
+ labelId = R.string.accessibility_system_action_lock_screen_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_LOCK_SCREEN;
+ break;
+ case SYSTEM_ACTION_ID_TAKE_SCREENSHOT:
+ labelId = R.string.accessibility_system_action_screenshot_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_TAKE_SCREENSHOT;
+ break;
+ case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON:
+ labelId = R.string.accessibility_system_action_accessibility_button_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON;
+ break;
+ case SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER:
+ labelId = R.string.accessibility_system_action_accessibility_button_chooser_label;
+ intent = SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER;
+ break;
+ default:
+ return;
+ }
+ mA11yManager.registerSystemAction(createRemoteAction(labelId, intent), actionId);
+ }
- RemoteAction actionAccessibilityMenu = new RemoteAction(
+ private RemoteAction createRemoteAction(int labelId, String intent) {
+ // TODO(b/148087487): update the icon used below to a valid one
+ return new RemoteAction(
Icon.createWithResource(mContext, R.drawable.ic_info),
- mContext.getString(R.string.accessibility_system_action_accessibility_menu_label),
- mContext.getString(R.string.accessibility_system_action_accessibility_menu_label),
- mReceiver.createPendingIntent(
- mContext, SystemActionsBroadcastReceiver.INTENT_ACTION_ACCESSIBILITY_MENU));
-
- AccessibilityManager am = (AccessibilityManager) mContext.getSystemService(
- Context.ACCESSIBILITY_SERVICE);
+ mContext.getString(labelId),
+ mContext.getString(labelId),
+ mReceiver.createPendingIntent(mContext, intent));
+ }
- am.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
- am.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
- am.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
- am.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
- am.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
- am.registerSystemAction(actionPowerDialog, SYSTEM_ACTION_ID_POWER_DIALOG);
- am.registerSystemAction(actionToggleSplitScreen, SYSTEM_ACTION_ID_TOGGLE_SPLIT_SCREEN);
- am.registerSystemAction(actionLockScreen, SYSTEM_ACTION_ID_LOCK_SCREEN);
- am.registerSystemAction(actionTakeScreenshot, SYSTEM_ACTION_ID_TAKE_SCREENSHOT);
- am.registerSystemAction(actionAccessibilityMenu, SYSTEM_ACTION_ID_ACCESSIBILITY_MENU);
+ /**
+ * Unregister a system action.
+ * @param actionId the action ID to unregister.
+ */
+ public void unregister(int actionId) {
+ mA11yManager.unregisterSystemAction(actionId);
}
private void handleBack() {
@@ -264,10 +318,6 @@ public class SystemActions extends SystemUI {
}
}
- private void handleToggleSplitScreen() {
- mStatusBar.toggleSplitScreen();
- }
-
private void handleLockScreen() {
IWindowManager windowManager = WindowManagerGlobal.getWindowManagerService();
@@ -282,15 +332,23 @@ public class SystemActions extends SystemUI {
private void handleTakeScreenshot() {
ScreenshotHelper screenshotHelper = new ScreenshotHelper(mContext);
- screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
- true, true, new Handler(Looper.getMainLooper()), null);
+ screenshotHelper.takeScreenshot(WindowManager.TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ SCREENSHOT_GLOBAL_ACTIONS, new Handler(Looper.getMainLooper()), null);
}
- private void handleAccessibilityMenu() {
+ private void handleAccessibilityButton() {
AccessibilityManager.getInstance(mContext).notifyAccessibilityButtonClicked(
Display.DEFAULT_DISPLAY);
}
+ private void handleAccessibilityButtonChooser() {
+ final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
+ intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
+ mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+ }
+
private class SystemActionsBroadcastReceiver extends BroadcastReceiver {
private static final String INTENT_ACTION_BACK = "SYSTEM_ACTION_BACK";
private static final String INTENT_ACTION_HOME = "SYSTEM_ACTION_HOME";
@@ -298,12 +356,12 @@ public class SystemActions extends SystemUI {
private static final String INTENT_ACTION_NOTIFICATIONS = "SYSTEM_ACTION_NOTIFICATIONS";
private static final String INTENT_ACTION_QUICK_SETTINGS = "SYSTEM_ACTION_QUICK_SETTINGS";
private static final String INTENT_ACTION_POWER_DIALOG = "SYSTEM_ACTION_POWER_DIALOG";
- private static final String INTENT_ACTION_TOGGLE_SPLIT_SCREEN =
- "SYSTEM_ACTION_TOGGLE_SPLIT_SCREEN";
private static final String INTENT_ACTION_LOCK_SCREEN = "SYSTEM_ACTION_LOCK_SCREEN";
private static final String INTENT_ACTION_TAKE_SCREENSHOT = "SYSTEM_ACTION_TAKE_SCREENSHOT";
- private static final String INTENT_ACTION_ACCESSIBILITY_MENU =
- "SYSTEM_ACTION_ACCESSIBILITY_MENU";
+ private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON =
+ "SYSTEM_ACTION_ACCESSIBILITY_BUTTON";
+ private static final String INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER =
+ "SYSTEM_ACTION_ACCESSIBILITY_BUTTON_MENU";
private PendingIntent createPendingIntent(Context context, String intentAction) {
switch (intentAction) {
@@ -313,10 +371,10 @@ public class SystemActions extends SystemUI {
case INTENT_ACTION_NOTIFICATIONS:
case INTENT_ACTION_QUICK_SETTINGS:
case INTENT_ACTION_POWER_DIALOG:
- case INTENT_ACTION_TOGGLE_SPLIT_SCREEN:
case INTENT_ACTION_LOCK_SCREEN:
case INTENT_ACTION_TAKE_SCREENSHOT:
- case INTENT_ACTION_ACCESSIBILITY_MENU: {
+ case INTENT_ACTION_ACCESSIBILITY_BUTTON:
+ case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER: {
Intent intent = new Intent(intentAction);
return PendingIntent.getBroadcast(context, 0, intent, 0);
}
@@ -334,10 +392,10 @@ public class SystemActions extends SystemUI {
intentFilter.addAction(INTENT_ACTION_NOTIFICATIONS);
intentFilter.addAction(INTENT_ACTION_QUICK_SETTINGS);
intentFilter.addAction(INTENT_ACTION_POWER_DIALOG);
- intentFilter.addAction(INTENT_ACTION_TOGGLE_SPLIT_SCREEN);
intentFilter.addAction(INTENT_ACTION_LOCK_SCREEN);
intentFilter.addAction(INTENT_ACTION_TAKE_SCREENSHOT);
- intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_MENU);
+ intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON);
+ intentFilter.addAction(INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER);
return intentFilter;
}
@@ -369,10 +427,6 @@ public class SystemActions extends SystemUI {
handlePowerDialog();
break;
}
- case INTENT_ACTION_TOGGLE_SPLIT_SCREEN: {
- handleToggleSplitScreen();
- break;
- }
case INTENT_ACTION_LOCK_SCREEN: {
handleLockScreen();
break;
@@ -381,8 +435,12 @@ public class SystemActions extends SystemUI {
handleTakeScreenshot();
break;
}
- case INTENT_ACTION_ACCESSIBILITY_MENU: {
- handleAccessibilityMenu();
+ case INTENT_ACTION_ACCESSIBILITY_BUTTON: {
+ handleAccessibilityButton();
+ break;
+ }
+ case INTENT_ACTION_ACCESSIBILITY_BUTTON_CHOOSER: {
+ handleAccessibilityButtonChooser();
break;
}
default:
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
index 251229f42da3..33e6ca49ddd5 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DisplayUtils.java
@@ -84,8 +84,8 @@ public class DisplayUtils {
public static int getCornerRadiusBottom(Context context) {
int radius = 0;
- int resourceId = context.getResources().getIdentifier("rounded_corner_radius_bottom",
- "dimen", "android");
+ int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_bottom",
+ "dimen", "com.android.systemui");
if (resourceId > 0) {
radius = context.getResources().getDimensionPixelSize(resourceId);
}
@@ -103,8 +103,8 @@ public class DisplayUtils {
public static int getCornerRadiusTop(Context context) {
int radius = 0;
- int resourceId = context.getResources().getIdentifier("rounded_corner_radius_top",
- "dimen", "android");
+ int resourceId = context.getResources().getIdentifier("config_rounded_mask_size_top",
+ "dimen", "com.android.systemui");
if (resourceId > 0) {
radius = context.getResources().getDimensionPixelSize(resourceId);
}
@@ -118,8 +118,8 @@ public class DisplayUtils {
private static int getCornerRadiusDefault(Context context) {
int radius = 0;
- int resourceId = context.getResources().getIdentifier("rounded_corner_radius", "dimen",
- "android");
+ int resourceId = context.getResources().getIdentifier("config_rounded_mask_size",
+ "dimen", "com.android.systemui");
if (resourceId > 0) {
radius = context.getResources().getDimensionPixelSize(resourceId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java b/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java
index 2bad7fc9583a..523378e97c94 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/PathSpecCornerPathRenderer.java
@@ -45,8 +45,8 @@ public final class PathSpecCornerPathRenderer extends CornerPathRenderer {
mWidth = DisplayUtils.getWidth(context);
mHeight = DisplayUtils.getHeight(context);
- mBottomCornerRadius = DisplayUtils.getCornerRadiusBottom(context);
- mTopCornerRadius = DisplayUtils.getCornerRadiusTop(context);
+ mBottomCornerRadius = DisplayUtils.getCornerRadiusBottom(context);
+ mTopCornerRadius = DisplayUtils.getCornerRadiusTop(context);
String pathData = context.getResources().getString(R.string.config_rounded_mask);
Path path = PathParser.createPathFromPathData(pathData);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index d8a11d36a335..95bbea15a88c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -17,12 +17,13 @@
package com.android.systemui.biometrics;
import android.content.Context;
+import android.os.UserHandle;
import android.text.InputType;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
+import android.widget.ImeAwareEditText;
import android.widget.TextView;
import com.android.internal.widget.LockPatternChecker;
@@ -38,7 +39,7 @@ public class AuthCredentialPasswordView extends AuthCredentialView
private static final String TAG = "BiometricPrompt/AuthCredentialPasswordView";
private final InputMethodManager mImm;
- private EditText mPasswordField;
+ private ImeAwareEditText mPasswordField;
public AuthCredentialPasswordView(Context context,
AttributeSet attrs) {
@@ -68,16 +69,14 @@ public class AuthCredentialPasswordView extends AuthCredentialView
protected void onAttachedToWindow() {
super.onAttachedToWindow();
+ mPasswordField.setTextOperationUser(UserHandle.of(mUserId));
if (mCredentialType == Utils.CREDENTIAL_PIN) {
mPasswordField.setInputType(
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
}
- // Wait a bit to focus the field so the focusable flag on the window is already set then.
- postDelayed(() -> {
- mPasswordField.requestFocus();
- mImm.showSoftInput(mPasswordField, InputMethodManager.SHOW_IMPLICIT);
- }, 100);
+ mPasswordField.requestFocus();
+ mPasswordField.scheduleShowSoftInput();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
index 8bf259182544..084b791a8dcd 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialView.java
@@ -220,15 +220,18 @@ public abstract class AuthCredentialView extends LinearLayout {
setTextOrHide(mDescriptionView, getDescription(mBiometricPromptBundle));
announceForAccessibility(title);
- final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
- final Drawable image;
- if (isManagedProfile) {
- image = getResources().getDrawable(R.drawable.auth_dialog_enterprise,
- mContext.getTheme());
- } else {
- image = getResources().getDrawable(R.drawable.auth_dialog_lock, mContext.getTheme());
+ if (mIconView != null) {
+ final boolean isManagedProfile = Utils.isManagedProfile(mContext, mEffectiveUserId);
+ final Drawable image;
+ if (isManagedProfile) {
+ image = getResources().getDrawable(R.drawable.auth_dialog_enterprise,
+ mContext.getTheme());
+ } else {
+ image = getResources().getDrawable(R.drawable.auth_dialog_lock,
+ mContext.getTheme());
+ }
+ mIconView.setImageDrawable(image);
}
- mIconView.setImageDrawable(image);
// Only animate this if we're transitioning from a biometric view.
if (mShouldAnimateContents) {
@@ -286,6 +289,7 @@ public abstract class AuthCredentialView extends LinearLayout {
if (matched) {
mClearErrorRunnable.run();
+ mLockPatternUtils.userPresent(mEffectiveUserId);
mCallback.onCredentialMatched(attestation);
} else {
if (timeoutMs > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 74b94e76dfc1..4269605cef12 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -25,7 +25,6 @@ import android.os.Looper
import android.os.Message
import android.os.UserHandle
import android.text.TextUtils
-import android.util.Log
import android.util.SparseArray
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
@@ -34,6 +33,7 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import java.io.FileDescriptor
import java.io.PrintWriter
+import java.lang.IllegalStateException
import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
@@ -60,7 +60,7 @@ private const val DEBUG = true
*
* Use only for IntentFilters with actions and optionally categories. It does not support,
* permissions, schemes, data types, data authorities or priority different than 0.
- * Cannot be used for getting sticky broadcasts.
+ * Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
*/
@Singleton
open class BroadcastDispatcher @Inject constructor (
@@ -189,8 +189,8 @@ open class BroadcastDispatcher @Inject constructor (
data.user.identifier
}
if (userId < UserHandle.USER_ALL) {
- if (DEBUG) Log.w(TAG, "Register receiver for invalid user: $userId")
- return
+ throw IllegalStateException(
+ "Attempting to register receiver for invalid user {$userId}")
}
val uBR = receiversByUser.get(userId, createUBRForUser(userId))
receiversByUser.put(userId, uBR)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 71f2bc09b983..0dbee663c1c9 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -78,6 +78,7 @@ class Bubble implements BubbleViewProvider {
private BubbleViewInfoTask mInflationTask;
private boolean mInflateSynchronously;
+ private boolean mPendingIntentCanceled;
/**
* Presentational info about the flyout.
@@ -90,6 +91,7 @@ class Bubble implements BubbleViewProvider {
}
private FlyoutMessage mFlyoutMessage;
+ private Drawable mBadgedAppIcon;
private Bitmap mBadgedImage;
private int mDotColor;
private Path mDotPath;
@@ -133,6 +135,10 @@ class Bubble implements BubbleViewProvider {
return mBadgedImage;
}
+ public Drawable getBadgedAppIcon() {
+ return mBadgedAppIcon;
+ }
+
@Override
public int getDotColor() {
return mDotColor;
@@ -177,6 +183,14 @@ class Bubble implements BubbleViewProvider {
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.
@@ -239,6 +253,7 @@ class Bubble implements BubbleViewProvider {
mAppName = info.appName;
mFlyoutMessage = info.flyoutMessage;
+ mBadgedAppIcon = info.badgedAppIcon;
mBadgedImage = info.badgedBubbleImage;
mDotColor = info.dotColor;
mDotPath = info.dotPath;
@@ -289,6 +304,13 @@ class Bubble implements BubbleViewProvider {
}
/**
+ * @return if the bubble was ever expanded
+ */
+ boolean getWasAccessed() {
+ return mLastAccessed != 0L;
+ }
+
+ /**
* @return the display id of the virtual display on which bubble contents is drawn.
*/
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 669a86b8a742..013f22203fbc 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -17,6 +17,8 @@
package com.android.systemui.bubbles;
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;
@@ -30,7 +32,6 @@ import static android.view.View.VISIBLE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
-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 static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -43,6 +44,9 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
@@ -50,6 +54,7 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.service.notification.NotificationListenerService;
@@ -83,6 +88,7 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationRemoveInterceptor;
+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;
@@ -103,7 +109,6 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.ArrayList;
-import java.util.HashSet;
import java.util.List;
/**
@@ -119,7 +124,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@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_USER_CHANGED, DISMISS_GROUP_CANCELLED, DISMISS_INVALID_INTENT,
+ DISMISS_OVERFLOW_MAX_REACHED})
@Target({FIELD, LOCAL_VARIABLE, PARAMETER})
@interface DismissReason {}
@@ -133,6 +139,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
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;
private final Context mContext;
private final NotificationEntryManager mNotificationEntryManager;
@@ -157,26 +164,22 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
// Used when ranking updates occur and we check if things should bubble / unbubble
private NotificationListenerService.Ranking mTmpRanking;
- // Saves notification keys of user created "fake" bubbles so that we can allow notifications
- // like these to bubble by default. Doesn't persist across reboots, not a long-term solution.
- private final HashSet<String> mUserCreatedBubbles;
- // If we're auto-bubbling bubbles via a whitelist, we need to track which notifs from that app
- // have been "demoted" back to a notification so that we don't auto-bubbles those again.
- // Doesn't persist across reboots, not a long-term solution.
- private final HashSet<String> mUserBlockedBubbles;
-
// 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 Runnable mOverflowCallback = null;
+ @Nullable private BubbleData.Listener mOverflowListener = null;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private IStatusBarService mBarService;
private SysUiState mSysUiState;
+ // Used to post to main UI thread
+ private Handler mHandler = new Handler();
+
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
@@ -293,11 +296,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
FeatureFlags featureFlags,
DumpManager dumpManager,
FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState) {
+ SysUiState sysUiState,
+ INotificationManager notificationManager) {
this(context, notificationShadeWindowController, statusBarStateController, shadeController,
data, null /* synchronizer */, configurationController, interruptionStateProvider,
zenModeController, notifUserManager, groupManager, entryManager,
- notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState);
+ notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, sysUiState,
+ notificationManager);
}
/**
@@ -319,7 +324,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
FeatureFlags featureFlags,
DumpManager dumpManager,
FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState) {
+ SysUiState sysUiState,
+ INotificationManager notificationManager) {
dumpManager.registerDumpable(TAG, this);
mContext = context;
mShadeController = shadeController;
@@ -327,6 +333,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mNotifUserManager = notifUserManager;
mZenModeController = zenModeController;
mFloatingContentCoordinator = floatingContentCoordinator;
+ mINotificationManager = notificationManager;
mZenModeController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
@@ -402,9 +409,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
});
- mUserCreatedBubbles = new HashSet<>();
- mUserBlockedBubbles = new HashSet<>();
-
mBubbleIconFactory = new BubbleIconFactory(context);
}
@@ -464,11 +468,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
(entry != null && entry.isRowDismissed() && !isAppCancel)
|| isClearAll || isUserDimiss || isSummaryCancel;
- if (userRemovedNotif || isUserCreatedBubble(key)
- || isSummaryOfUserCreatedBubble(entry)) {
+ if (userRemovedNotif) {
return handleDismissalInterception(entry);
}
-
return false;
}
});
@@ -579,8 +581,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mInflateSynchronously = inflateSynchronously;
}
- void setOverflowCallback(Runnable updateOverflow) {
- mOverflowCallback = updateOverflow;
+ void setOverflowListener(BubbleData.Listener listener) {
+ mOverflowListener = listener;
}
/**
@@ -608,6 +610,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
+
+ mStackView.setUnbubbleConversationCallback(notificationEntry ->
+ onUserChangedBubble(notificationEntry, false /* shouldBubble */));
}
}
@@ -664,8 +669,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
mStackView.onThemeChanged();
}
mBubbleIconFactory = new BubbleIconFactory(mContext);
+ // Reload each bubble
for (Bubble b: mBubbleData.getBubbles()) {
- // Reload each bubble
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
+ }
+ for (Bubble b: mBubbleData.getOverflowBubbles()) {
b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
}
}
@@ -736,18 +744,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
*/
public boolean isBubbleNotificationSuppressedFromShade(NotificationEntry entry) {
String key = entry.getKey();
- boolean isBubbleAndSuppressed = mBubbleData.hasBubbleWithKey(key)
- && !mBubbleData.getBubbleWithKey(key).showInShade();
+ 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) || isBubbleAndSuppressed;
+ return (isSummary && isSuppressedSummary) || isSuppressedBubble;
}
void promoteBubbleFromOverflow(Bubble bubble) {
bubble.setInflateSynchronously(mInflateSynchronously);
+ setIsBubble(bubble, /* isBubble */ true);
mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
@@ -757,11 +765,16 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* @param notificationKey the notification key for the bubble to be selected
*/
public void expandStackAndSelectBubble(String notificationKey) {
- Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey);
- if (bubble != null) {
+ Bubble bubble = mBubbleData.getBubbleInStackWithKey(notificationKey);
+ if (bubble == null) {
+ bubble = mBubbleData.getOverflowBubbleWithKey(notificationKey);
+ if (bubble != null) {
+ mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
+ }
+ } else if (bubble.getEntry().isBubble()){
mBubbleData.setSelectedBubble(bubble);
- mBubbleData.setExpanded(true);
}
+ mBubbleData.setExpanded(true);
}
/**
@@ -799,7 +812,21 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
Bubble bubble = mBubbleData.getOrCreateBubble(notif);
bubble.setInflateSynchronously(mInflateSynchronously);
bubble.inflate(
- b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade),
+ b -> {
+ mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade);
+ if (bubble.getBubbleIntent() == null) {
+ return;
+ }
+ bubble.getBubbleIntent().registerCancelListener(pendingIntent -> {
+ if (bubble.getWasAccessed()) {
+ bubble.setPendingIntentCanceled();
+ return;
+ }
+ mHandler.post(
+ () -> removeBubble(bubble.getEntry(),
+ BubbleController.DISMISS_INVALID_INTENT));
+ });
+ },
mContext, mStackView, mBubbleIconFactory);
}
@@ -809,59 +836,44 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
* 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 show as a bubble.
+ * @param entry the notification to change bubble state for.
+ * @param shouldBubble whether the notification should show as a bubble or not.
*/
- public void onUserCreatedBubbleFromNotification(NotificationEntry entry) {
- if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "onUserCreatedBubble: " + entry.getKey());
+ public void onUserChangedBubble(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;
}
- mShadeController.collapsePanel(true);
- entry.setFlagBubble(true);
- updateBubble(entry, true /* suppressFlyout */, false /* showInShade */);
- mUserCreatedBubbles.add(entry.getKey());
- mUserBlockedBubbles.remove(entry.getKey());
- }
- /**
- * Called when a user has indicated that an active notification appearing as a bubble should
- * no longer be shown as a bubble.
- *
- * @param entry the notification to no longer show as a bubble.
- */
- public void onUserDemotedBubbleFromNotification(NotificationEntry entry) {
- if (DEBUG_EXPERIMENTS || DEBUG_BUBBLE_CONTROLLER) {
- Log.d(TAG, "onUserDemotedBubble: " + entry.getKey());
- }
- entry.setFlagBubble(false);
- removeBubble(entry, DISMISS_BLOCKED);
- mUserCreatedBubbles.remove(entry.getKey());
- if (BubbleExperimentConfig.isPackageWhitelistedToAutoBubble(
- mContext, entry.getSbn().getPackageName())) {
- // This package is whitelist but user demoted the bubble, let's save it so we don't
- // auto-bubble for the whitelist again.
- mUserBlockedBubbles.add(entry.getKey());
+ // Update the state in NotificationManagerService
+ try {
+ int flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
+ mBarService.onNotificationBubbleChanged(entry.getKey(), shouldBubble, flags);
+ } catch (RemoteException e) {
}
- }
- /**
- * Whether this bubble was explicitly created by the user via a SysUI affordance.
- */
- boolean isUserCreatedBubble(String key) {
- return mUserCreatedBubbles.contains(key);
- }
+ // 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());
+ }
- boolean isSummaryOfUserCreatedBubble(NotificationEntry entry) {
- if (isSummaryOfBubbles(entry)) {
- List<Bubble> bubbleChildren =
- mBubbleData.getBubblesInGroup(entry.getSbn().getGroupKey());
- for (int i = 0; i < bubbleChildren.size(); i++) {
- // Check if any are user-created (i.e. experimental bubbles)
- if (isUserCreatedBubble(bubbleChildren.get(i).getKey())) {
- return true;
- }
+ if (shouldBubble) {
+ mShadeController.collapsePanel(true);
+ if (entry.getRow() != null) {
+ entry.getRow().updateBubbleButton();
}
}
- return false;
}
/**
@@ -871,43 +883,25 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
*/
@MainThread
void removeBubble(NotificationEntry entry, int reason) {
- if (mBubbleData.hasBubbleWithKey(entry.getKey())) {
+ if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
mBubbleData.notificationEntryRemoved(entry, reason);
}
}
private void onEntryAdded(NotificationEntry entry) {
- boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
- boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
- boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
- mContext, entry, previouslyUserCreated, userBlocked);
-
if (mNotificationInterruptStateProvider.shouldBubbleUp(entry)
- && (canLaunchInActivityView(mContext, entry) || wasAdjusted)) {
- if (wasAdjusted && !previouslyUserCreated) {
- // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
- mUserCreatedBubbles.add(entry.getKey());
- }
+ && canLaunchInActivityView(mContext, entry)) {
updateBubble(entry);
}
}
private void onEntryUpdated(NotificationEntry entry) {
- boolean previouslyUserCreated = mUserCreatedBubbles.contains(entry.getKey());
- boolean userBlocked = mUserBlockedBubbles.contains(entry.getKey());
- boolean wasAdjusted = BubbleExperimentConfig.adjustForExperiments(
- mContext, entry, previouslyUserCreated, userBlocked);
-
boolean shouldBubble = mNotificationInterruptStateProvider.shouldBubbleUp(entry)
- && (canLaunchInActivityView(mContext, entry) || wasAdjusted);
- if (!shouldBubble && mBubbleData.hasBubbleWithKey(entry.getKey())) {
+ && canLaunchInActivityView(mContext, entry);
+ if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
// It was previously a bubble but no longer a bubble -- lets remove it
removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
} else if (shouldBubble) {
- if (wasAdjusted && !previouslyUserCreated) {
- // Gotta treat the auto-bubbled / whitelisted packaged bubbles as usercreated
- mUserCreatedBubbles.add(entry.getKey());
- }
updateBubble(entry);
}
}
@@ -943,7 +937,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
String key = orderedKeys[i];
NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(key);
rankingMap.getRanking(key, mTmpRanking);
- boolean isActiveBubble = mBubbleData.hasBubbleWithKey(key);
+ boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
if (isActiveBubble && !mTmpRanking.canBubble()) {
mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
} else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
@@ -953,14 +947,27 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
}
}
+ private void setIsBubble(Bubble b, boolean isBubble) {
+ if (isBubble) {
+ b.getEntry().getSbn().getNotification().flags |= FLAG_BUBBLE;
+ } else {
+ b.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
+ }
+ try {
+ mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0);
+ } catch (RemoteException e) {
+ // Bad things have happened
+ }
+ }
+
@SuppressWarnings("FieldCanBeLocal")
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@Override
public void applyUpdate(BubbleData.Update update) {
// Update bubbles in overflow.
- if (mOverflowCallback != null) {
- mOverflowCallback.run();
+ if (mOverflowListener != null) {
+ mOverflowListener.applyUpdate(update);
}
// Collapsing? Do this first before remaining steps.
@@ -975,35 +982,37 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
final Bubble bubble = removed.first;
@DismissReason final int reason = removed.second;
mStackView.removeBubble(bubble);
+
// If the bubble is removed for user switching, leave the notification in place.
- if (reason != DISMISS_USER_CHANGED) {
- if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
- && !bubble.showInShade()) {
+ if (reason == DISMISS_USER_CHANGED) {
+ continue;
+ }
+ if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
+ if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey())
+ && (!bubble.showInShade()
+ || reason == DISMISS_NOTIF_CANCEL
+ || reason == DISMISS_GROUP_CANCELLED
+ || reason == DISMISS_OVERFLOW_MAX_REACHED)) {
// The bubble is now gone & the notification is hidden from the shade, so
// time to actually remove it
for (NotifCallback cb : mCallbacks) {
cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
}
} else {
- // Update the flag for SysUI
- bubble.getEntry().getSbn().getNotification().flags &= ~FLAG_BUBBLE;
-
- // Make sure NoMan knows it's not a bubble anymore so anyone querying it
- // will get right result back
- try {
- mBarService.onNotificationBubbleChanged(bubble.getKey(),
- false /* isBubble */);
- } catch (RemoteException e) {
- // Bad things have happened
+ if (bubble.getEntry().isBubble() && bubble.showInShade()) {
+ setIsBubble(bubble, /* isBubble */ false);
+ }
+ if (bubble.getEntry().getRow() != null) {
+ bubble.getEntry().getRow().updateBubbleButton();
}
}
- final String groupKey = bubble.getEntry().getSbn().getGroupKey();
- if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
- // Time to potentially remove the summary
- for (NotifCallback cb : mCallbacks) {
- cb.maybeCancelSummary(bubble.getEntry());
- }
+ }
+ final String groupKey = bubble.getEntry().getSbn().getGroupKey();
+ if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+ // Time to potentially remove the summary
+ for (NotifCallback cb : mCallbacks) {
+ cb.maybeCancelSummary(bubble.getEntry());
}
}
}
@@ -1050,9 +1059,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
mStackView.getExpandedBubble()));
}
- Log.d(TAG, "\n[BubbleData] overflow:");
- Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
- null));
}
}
};
@@ -1071,21 +1077,19 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (entry == null) {
return false;
}
-
- final boolean interceptBubbleDismissal = mBubbleData.hasBubbleWithKey(entry.getKey())
- && entry.isBubble();
- final boolean interceptSummaryDismissal = isSummaryOfBubbles(entry);
-
- if (interceptSummaryDismissal) {
+ if (isSummaryOfBubbles(entry)) {
handleSummaryDismissalInterception(entry);
- } else if (interceptBubbleDismissal) {
- Bubble bubble = mBubbleData.getBubbleWithKey(entry.getKey());
+ } 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 */);
- } else {
- return false;
}
-
// Update the shade
for (NotifCallback cb : mCallbacks) {
cb.invalidateNotifications("BubbleController.handleDismissalInterception");
@@ -1110,15 +1114,15 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
private void handleSummaryDismissalInterception(NotificationEntry summary) {
// current children in the row:
- final List<NotificationEntry> children = summary.getChildren();
+ final List<NotificationEntry> children = summary.getAttachedNotifChildren();
if (children != null) {
for (int i = 0; i < children.size(); i++) {
NotificationEntry child = children.get(i);
- if (mBubbleData.hasBubbleWithKey(child.getKey())) {
+ 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.getBubbleWithKey(child.getKey());
+ Bubble bubbleChild = mBubbleData.getAnyBubbleWithkey(child.getKey());
mNotificationGroupManager.onEntryRemoved(bubbleChild.getEntry());
bubbleChild.setSuppressNotification(true);
bubbleChild.setShowDot(false /* show */);
@@ -1237,7 +1241,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
@Override
public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
- boolean clearedTask) {
+ boolean clearedTask, boolean wasVisible) {
for (Bubble b : mBubbleData.getBubbles()) {
if (b.getDisplayId() == task.displayId) {
expandStackAndSelectBubble(b.getKey());
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 4c149ddd3939..f2b1c031dcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -15,6 +15,7 @@
*/
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;
@@ -73,6 +74,8 @@ public class BubbleData {
@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<>();
@@ -91,10 +94,12 @@ public class BubbleData {
|| addedBubble != null
|| updatedBubble != null
|| !removedBubbles.isEmpty()
+ || addedOverflowBubble != null
+ || removedOverflowBubble != null
|| orderChanged;
}
- void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
+ void bubbleRemoved(Bubble bubbleToRemove, @DismissReason int reason) {
removedBubbles.add(new Pair<>(bubbleToRemove, reason));
}
}
@@ -120,9 +125,10 @@ public class BubbleData {
/** Bubbles that are being loaded but haven't been added to the stack just yet. */
private final List<Bubble> mPendingBubbles;
private Bubble mSelectedBubble;
+ private boolean mShowingOverflow;
private boolean mExpanded;
private final int mMaxBubbles;
- private final int mMaxOverflowBubbles;
+ private int mMaxOverflowBubbles;
// State tracked during an operation -- keeps track of what listener events to dispatch.
private Update mStateChange;
@@ -174,8 +180,16 @@ public class BubbleData {
return mExpanded;
}
- public boolean hasBubbleWithKey(String key) {
- return getBubbleWithKey(key) != null;
+ 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
@@ -205,6 +219,8 @@ public class BubbleData {
Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
}
moveOverflowBubbleToPending(bubble);
+ // Preserve new order for next repack, which sorts by last updated time.
+ bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
bubble.inflate(
b -> {
notificationEntryUpdated(bubble, /* suppressFlyout */
@@ -215,10 +231,13 @@ public class BubbleData {
dispatchPendingChanges();
}
+ void setShowingOverflow(boolean showingOverflow) {
+ mShowingOverflow = showingOverflow;
+ }
+
private void moveOverflowBubbleToPending(Bubble b) {
- // Preserve new order for next repack, which sorts by last updated time.
- b.markUpdatedAt(mTimeSource.currentTimeMillis());
mOverflowBubbles.remove(b);
+ mStateChange.removedOverflowBubble = b;
mPendingBubbles.add(b);
}
@@ -228,15 +247,16 @@ public class BubbleData {
* for that.
*/
Bubble getOrCreateBubble(NotificationEntry entry) {
- Bubble bubble = getBubbleWithKey(entry.getKey());
- if (bubble == null) {
- for (int i = 0; i < mOverflowBubbles.size(); i++) {
- Bubble b = mOverflowBubbles.get(i);
- if (b.getKey().equals(entry.getKey())) {
- moveOverflowBubbleToPending(b);
- b.setEntry(entry);
- return b;
- }
+ String key = entry.getKey();
+ Bubble bubble = getBubbleInStackWithKey(entry.getKey());
+ if (bubble != null) {
+ bubble.setEntry(entry);
+ } else {
+ bubble = getOverflowBubbleWithKey(key);
+ if (bubble != null) {
+ moveOverflowBubbleToPending(bubble);
+ bubble.setEntry(entry);
+ return bubble;
}
// Check for it in pending
for (int i = 0; i < mPendingBubbles.size(); i++) {
@@ -248,8 +268,6 @@ public class BubbleData {
}
bubble = new Bubble(entry, mSuppressionListener);
mPendingBubbles.add(bubble);
- } else {
- bubble.setEntry(entry);
}
return bubble;
}
@@ -264,7 +282,7 @@ public class BubbleData {
Log.d(TAG, "notificationEntryUpdated: " + bubble);
}
mPendingBubbles.remove(bubble); // No longer pending once we're here
- Bubble prevBubble = getBubbleWithKey(bubble.getKey());
+ Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive();
if (prevBubble == null) {
@@ -417,6 +435,20 @@ public class BubbleData {
}
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)) {
+
+ Bubble b = getOverflowBubbleWithKey(key);
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "Cancel overflow bubble: " + b);
+ }
+ mOverflowBubbles.remove(b);
+ mStateChange.bubbleRemoved(b, reason);
+ mStateChange.removedOverflowBubble = b;
+ }
return;
}
Bubble bubbleToRemove = mBubbles.get(indexToRemove);
@@ -448,21 +480,26 @@ public class BubbleData {
}
void overflowBubble(@DismissReason int reason, Bubble bubble) {
- if (reason == BubbleController.DISMISS_AGED
- || reason == BubbleController.DISMISS_USER_GESTURE) {
+ if (bubble.getPendingIntentCanceled()
+ || !(reason == BubbleController.DISMISS_AGED
+ || reason == BubbleController.DISMISS_USER_GESTURE)) {
+ return;
+ }
+ if (DEBUG_BUBBLE_DATA) {
+ Log.d(TAG, "Overflowing: " + bubble);
+ }
+ 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, "Overflowing: " + bubble);
- }
- mOverflowBubbles.add(0, bubble);
- bubble.stopInflation();
- if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
- // Remove oldest bubble.
- if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "Overflow full. Remove: " + mOverflowBubbles.get(
- mOverflowBubbles.size() - 1));
- }
- mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
+ Log.d(TAG, "Overflow full. Remove: " + oldest);
}
+ mOverflowBubbles.remove(oldest);
+ mStateChange.removedOverflowBubble = oldest;
+ mStateChange.bubbleRemoved(oldest, BubbleController.DISMISS_OVERFLOW_MAX_REACHED);
}
}
@@ -513,9 +550,11 @@ public class BubbleData {
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "setSelectedBubbleInternal: " + bubble);
}
- if (Objects.equals(bubble, mSelectedBubble)) {
+ 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);
@@ -559,6 +598,10 @@ public class BubbleData {
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.
if (!mSelectedBubble.isOngoing() && mBubbles.get(0).isOngoing()) {
@@ -739,7 +782,7 @@ public class BubbleData {
/**
* The set of bubbles in row.
*/
- @VisibleForTesting(visibility = PRIVATE)
+ @VisibleForTesting(visibility = PACKAGE)
public List<Bubble> getBubbles() {
return Collections.unmodifiableList(mBubbles);
}
@@ -753,7 +796,17 @@ public class BubbleData {
@VisibleForTesting(visibility = PRIVATE)
@Nullable
- Bubble getBubbleWithKey(String key) {
+ 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)) {
@@ -795,6 +848,15 @@ public class BubbleData {
}
/**
+ * 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) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 93fb6972fad5..bb2365559f74 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -43,7 +43,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -55,14 +54,13 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
-import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.AlphaOptimizedButton;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
*/
-public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
+public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
private enum ActivityViewStatus {
@@ -100,9 +98,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
private int mPointerWidth;
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
- private Rect mTempRect = new Rect();
- private int[] mTempLoc = new int[2];
- private int mExpandedViewTouchSlop;
@Nullable private Bubble mBubble;
@@ -193,7 +188,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
+ " mActivityViewStatus=" + mActivityViewStatus
+ " bubble=" + getBubbleKey());
}
- if (mBubble != null && !mBubbleController.isUserCreatedBubble(mBubble.getKey())) {
+ if (mBubble != null) {
// Must post because this is called from a binder thread.
post(() -> mBubbleController.removeBubble(mBubble.getEntry(),
BubbleController.DISMISS_TASK_FINISHED));
@@ -224,7 +219,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
- mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
}
@Override
@@ -239,7 +233,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
-
mPointerDrawable = new ShapeDrawable(TriangleShape.create(
mPointerWidth, mPointerHeight, true /* pointUp */));
mPointerView.setBackground(mPointerDrawable);
@@ -248,7 +241,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
- mSettingsIcon.setOnClickListener(this);
mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
true /* singleTaskInstance */);
@@ -289,6 +281,19 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
return mBubble != null ? mBubble.getEntry() : null;
}
+ void setManageClickListener(OnClickListener manageClickListener) {
+ findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+ }
+
+ /**
+ * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which
+ * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful
+ * if a view has been added or removed from on top of the ActivityView, such as the manage menu.
+ */
+ void updateObscuredTouchableRegion() {
+ mActivityView.onLocationChanged();
+ }
+
void applyThemeAttrs() {
final TypedArray ta = mContext.obtainStyledAttributes(
new int[] {
@@ -473,51 +478,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
/**
- * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
- * expanded view.
- *
- * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
- */
- boolean intersectingTouchableContent(int rawX, int rawY) {
- mTempRect.setEmpty();
- if (mActivityView != null) {
- mTempLoc = mActivityView.getLocationOnScreen();
- mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
- mTempLoc[1] - mExpandedViewTouchSlop,
- mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
- mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
- }
- if (mTempRect.contains(rawX, rawY)) {
- return true;
- }
- mTempLoc = mSettingsIcon.getLocationOnScreen();
- mTempRect.set(mTempLoc[0],
- mTempLoc[1],
- mTempLoc[0] + mSettingsIcon.getWidth(),
- mTempLoc[1] + mSettingsIcon.getHeight());
- if (mTempRect.contains(rawX, rawY)) {
- return true;
- }
- return false;
- }
-
- @Override
- public void onClick(View view) {
- if (mBubble == null) {
- return;
- }
- int id = view.getId();
- if (id == R.id.settings_button) {
- Intent intent = mBubble.getSettingsIntent();
- mStackView.collapseStack(() -> {
- mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser());
- logBubbleClickEvent(mBubble,
- SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
- });
- }
- }
-
- /**
* Update appearance of the expanded view being displayed.
*/
public void updateView() {
@@ -547,10 +507,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* Position of the manage button displayed in the expanded view. Used for placing user
* education about the manage button.
*/
- public Rect getManageButtonLocationOnScreen() {
- mTempLoc = mSettingsIcon.getLocationOnScreen();
- return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
- mTempLoc[1] + mSettingsIcon.getHeight());
+ public void getManageButtonBoundsOnScreen(Rect rect) {
+ mSettingsIcon.getBoundsOnScreen(rect);
}
/**
@@ -611,26 +569,4 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
return INVALID_DISPLAY;
}
-
- /**
- * Logs bubble UI click event.
- *
- * @param bubble the bubble notification entry that user is interacting with.
- * @param action the user interaction enum.
- */
- private void logBubbleClickEvent(Bubble bubble, int action) {
- StatusBarNotification notification = bubble.getEntry().getSbn();
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- notification.getPackageName(),
- notification.getNotification().getChannelId(),
- notification.getId(),
- mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
- mStackView.getBubbleCount(),
- action,
- mStackView.getNormalizedXPosition(),
- mStackView.getNormalizedYPosition(),
- bubble.showInShade(),
- bubble.isOngoing(),
- false /* isAppForeground (unused) */);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
index 41dbb489c2f6..a888bd57c699 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExperimentConfig.java
@@ -57,16 +57,13 @@ import java.util.List;
public class BubbleExperimentConfig {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
- private static final String SHORTCUT_DUMMY_INTENT = "bubble_experiment_shortcut_intent";
- private static PendingIntent sDummyShortcutIntent;
-
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 = true;
+ 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;
@@ -74,7 +71,7 @@ public class BubbleExperimentConfig {
private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
- private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
+ private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = true;
/**
* When true, if a notification has the information necessary to bubble (i.e. valid
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 37841f24a3cf..de54c353fc85 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -21,6 +21,7 @@ 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.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -60,6 +61,16 @@ public class BubbleOverflowActivity extends Activity {
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() {
+ return false;
+ }
+ }
+
@Inject
public BubbleOverflowActivity(BubbleController controller) {
mBubbleController = controller;
@@ -78,7 +89,7 @@ public class BubbleOverflowActivity extends Activity {
Resources res = getResources();
final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
mRecyclerView.setLayoutManager(
- new GridLayoutManager(getApplicationContext(), columns));
+ new NoScrollGridLayoutManager(getApplicationContext(), columns));
DisplayMetrics displayMetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
@@ -95,11 +106,12 @@ public class BubbleOverflowActivity extends Activity {
mAdapter = new BubbleOverflowAdapter(mOverflowBubbles,
mBubbleController::promoteBubbleFromOverflow, viewWidth, viewHeight);
mRecyclerView.setAdapter(mAdapter);
- onDataChanged(mBubbleController.getOverflowBubbles());
- mBubbleController.setOverflowCallback(() -> {
- onDataChanged(mBubbleController.getOverflowBubbles());
- });
- onThemeChanged();
+
+ mOverflowBubbles.addAll(mBubbleController.getOverflowBubbles());
+ mAdapter.notifyDataSetChanged();
+ setEmptyStateVisibility();
+
+ mBubbleController.setOverflowListener(mDataListener);
}
/**
@@ -126,6 +138,14 @@ public class BubbleOverflowActivity extends Activity {
}
}
+ void setEmptyStateVisibility() {
+ if (mOverflowBubbles.isEmpty()) {
+ mEmptyState.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyState.setVisibility(View.GONE);
+ }
+ }
+
void setBackgroundColor() {
final TypedArray ta = getApplicationContext().obtainStyledAttributes(
new int[]{android.R.attr.colorBackgroundFloating});
@@ -134,22 +154,40 @@ public class BubbleOverflowActivity extends Activity {
findViewById(android.R.id.content).setBackgroundColor(bgColor);
}
- void onDataChanged(List<Bubble> bubbles) {
- mOverflowBubbles.clear();
- mOverflowBubbles.addAll(bubbles);
- mAdapter.notifyDataSetChanged();
+ private final BubbleData.Listener mDataListener = new BubbleData.Listener() {
- if (mOverflowBubbles.isEmpty()) {
- mEmptyState.setVisibility(View.VISIBLE);
- } else {
- mEmptyState.setVisibility(View.GONE);
- }
+ @Override
+ public void applyUpdate(BubbleData.Update update) {
- if (DEBUG_OVERFLOW) {
- Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
- mOverflowBubbles, /*selected*/ null));
+ Bubble toRemove = update.removedOverflowBubble;
+ if (toRemove != null) {
+ if (DEBUG_OVERFLOW) {
+ Log.d(TAG, "remove: " + toRemove);
+ }
+ toRemove.cleanupViews();
+ 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);
+ }
+
+ setEmptyStateVisibility();
+
+ if (DEBUG_OVERFLOW) {
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(
+ mBubbleController.getOverflowBubbles(),
+ null));
+ }
}
- }
+ };
@Override
public void onStart() {
@@ -215,6 +253,7 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V
Bubble b = mBubbles.get(index);
vh.iconView.setRenderedBubble(b);
+ vh.iconView.removeDotSuppressionFlag(BadgedImageView.SuppressionFlag.FLYOUT_VISIBLE);
vh.iconView.setOnClickListener(view -> {
mBubbles.remove(b);
notifyDataSetChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 71dbbbc9da50..366d4a7345af 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,12 +33,14 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
@@ -47,6 +49,7 @@ import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
+import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
@@ -55,6 +58,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -62,6 +66,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
+import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.MainThread;
@@ -83,6 +88,7 @@ import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
@@ -97,6 +103,7 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
@@ -224,7 +231,7 @@ public class BubbleStackView extends FrameLayout {
private int mPointerHeight;
private int mStatusBarHeight;
private int mImeOffset;
- private BubbleViewProvider mExpandedBubble;
+ @Nullable private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded;
/** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -244,6 +251,10 @@ public class BubbleStackView extends FrameLayout {
}
private BubbleController.BubbleExpandListener mExpandListener;
+
+ /** Callback to run when we want to unbubble the given notification's conversation. */
+ private Consumer<NotificationEntry> mUnbubbleConversationCallback;
+
private SysUiState mSysUiState;
private boolean mViewUpdatedRequested = false;
@@ -255,9 +266,7 @@ public class BubbleStackView extends FrameLayout {
private LayoutInflater mInflater;
- // Used for determining view / touch intersection
- int[] mTempLoc = new int[2];
- RectF mTempRect = new RectF();
+ private Rect mTempRect = new Rect();
private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
@@ -374,8 +383,9 @@ public class BubbleStackView extends FrameLayout {
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
mExpandedAnimationController.dismissDraggedOutBubble(
- mExpandedAnimationController.getDraggedOutBubble(),
- BubbleStackView.this::dismissMagnetizedObject);
+ mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
+ mDismissTargetContainer.getHeight() /* translationYBy */,
+ BubbleStackView.this::dismissMagnetizedObject /* after */);
hideDismissTarget();
}
};
@@ -405,7 +415,8 @@ public class BubbleStackView extends FrameLayout {
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- mStackAnimationController.implodeStack(
+ mStackAnimationController.animateStackDismissal(
+ mDismissTargetContainer.getHeight() /* translationYBy */,
() -> {
resetDesaturationAndDarken();
dismissMagnetizedObject();
@@ -442,8 +453,8 @@ public class BubbleStackView extends FrameLayout {
// that means overflow was previously expanded. Set the selected bubble
// internally without going through BubbleData (which would ignore it since it's
// already selected).
+ mBubbleData.setShowingOverflow(true);
setSelectedBubble(clickedBubble);
-
}
} else {
// Otherwise, we either tapped the stack (which means we're collapsed
@@ -469,6 +480,11 @@ public class BubbleStackView extends FrameLayout {
return true;
}
+ // If the manage menu is visible, just hide it.
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ }
+
if (mBubbleData.isExpanded()) {
maybeShowManageEducation(false /* show */);
@@ -625,6 +641,13 @@ public class BubbleStackView extends FrameLayout {
private BubbleManageEducationView mManageEducationView;
private boolean mAnimatingManageEducationAway;
+ private ViewGroup mManageMenu;
+ private ImageView mManageSettingsIcon;
+ private TextView mManageSettingsText;
+ private boolean mShowingManage = false;
+ private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig(
+ SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+
@SuppressLint("ClickableViewAccessibility")
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
@@ -687,6 +710,8 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
+ setUpManageMenu();
+
setUpFlyout();
mFlyoutTransitionSpring.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_LOW)
@@ -836,7 +861,9 @@ public class BubbleStackView extends FrameLayout {
// ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
setOnTouchListener((view, ev) -> {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
- if (mBubbleData.isExpanded()) {
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ } else if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
}
}
@@ -845,6 +872,66 @@ public class BubbleStackView extends FrameLayout {
});
}
+ private void setUpManageMenu() {
+ if (mManageMenu != null) {
+ removeView(mManageMenu);
+ }
+
+ mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate(
+ R.layout.bubble_manage_menu, this, false);
+ mManageMenu.setVisibility(View.INVISIBLE);
+
+ PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
+
+ final TypedArray ta = mContext.obtainStyledAttributes(
+ new int[] {android.R.attr.dialogCornerRadius});
+ final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
+ ta.recycle();
+
+ mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
+ }
+ });
+ mManageMenu.setClipToOutline(true);
+
+ mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ dismissBubbleIfExists(mBubbleData.getSelectedBubble());
+ });
+
+ mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ final Bubble bubble = mBubbleData.getSelectedBubble();
+ if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
+ mUnbubbleConversationCallback.accept(bubble.getEntry());
+ }
+ });
+
+ mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+ view -> {
+ showManageMenu(false /* show */);
+ final Bubble bubble = mBubbleData.getSelectedBubble();
+ if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
+ final Intent intent = bubble.getSettingsIntent();
+ collapseStack(() -> {
+ mContext.startActivityAsUser(
+ intent, bubble.getEntry().getSbn().getUser());
+ logBubbleClickEvent(
+ bubble,
+ SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+ });
+ }
+ });
+
+ mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
+ mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
+ addView(mManageMenu);
+ }
+
private void setUpUserEducation() {
if (mUserEducationView != null) {
removeView(mUserEducationView);
@@ -932,6 +1019,7 @@ public class BubbleStackView extends FrameLayout {
setUpFlyout();
setUpOverflow();
setUpUserEducation();
+ setUpManageMenu();
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -954,8 +1042,13 @@ public class BubbleStackView extends FrameLayout {
mVerticalPosPercentBeforeRotation =
(mStackAnimationController.getStackPosition().y - allowablePos.top)
/ (allowablePos.bottom - allowablePos.top);
+ mVerticalPosPercentBeforeRotation =
+ Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
+
+ mManageMenu.setVisibility(View.INVISIBLE);
+ mShowingManage = false;
}
@Override
@@ -1013,6 +1106,8 @@ public class BubbleStackView extends FrameLayout {
// 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);
@@ -1043,32 +1138,29 @@ public class BubbleStackView extends FrameLayout {
if (mBubbleData.getBubbles().isEmpty()) {
return;
}
- Bubble topBubble = mBubbleData.getBubbles().get(0);
- String appName = topBubble.getAppName();
- Notification notification = topBubble.getEntry().getSbn().getNotification();
- CharSequence titleCharSeq = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
- String titleStr = getResources().getString(R.string.notification_bubble_title);
- if (titleCharSeq != null) {
- titleStr = titleCharSeq.toString();
- }
- int moreCount = mBubbleContainer.getChildCount() - 1;
- // Example: Title from app name.
- String singleDescription = getResources().getString(
- R.string.bubble_content_description_single, titleStr, appName);
+ for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
+ final Bubble bubble = mBubbleData.getBubbles().get(i);
+ final String appName = bubble.getAppName();
+ final Notification notification = bubble.getEntry().getSbn().getNotification();
+ final CharSequence titleCharSeq =
+ notification.extras.getCharSequence(Notification.EXTRA_TITLE);
- // Example: Title from app name and 4 more.
- String stackDescription = getResources().getString(
- R.string.bubble_content_description_stack, titleStr, appName, moreCount);
+ String titleStr = getResources().getString(R.string.notification_bubble_title);
+ if (titleCharSeq != null) {
+ titleStr = titleCharSeq.toString();
+ }
- if (mIsExpanded) {
- // TODO(b/129522932) - update content description for each bubble in expanded view.
- } else {
- // Collapsed stack.
- if (moreCount > 0) {
- mBubbleContainer.setContentDescription(stackDescription);
- } else {
- mBubbleContainer.setContentDescription(singleDescription);
+ 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));
+ }
}
}
}
@@ -1096,6 +1188,12 @@ public class BubbleStackView extends FrameLayout {
mExpandListener = listener;
}
+ /** Sets the function to call to un-bubble the given conversation. */
+ public void setUnbubbleConversationCallback(
+ Consumer<NotificationEntry> unbubbleConversationCallback) {
+ mUnbubbleConversationCallback = unbubbleConversationCallback;
+ }
+
/**
* Whether the stack of bubbles is expanded or not.
*/
@@ -1232,8 +1330,12 @@ public class BubbleStackView extends FrameLayout {
if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) {
return;
}
+ if (bubbleToSelect == null || bubbleToSelect.getKey() != BubbleOverflow.KEY) {
+ mBubbleData.setShowingOverflow(false);
+ }
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
+ updatePointerPosition();
if (mIsExpanded) {
// Make the container of the expanded view transparent before removing the expanded view
@@ -1243,7 +1345,6 @@ public class BubbleStackView extends FrameLayout {
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
previouslySelected.setContentVisibility(false);
updateExpandedBubble();
- updatePointerPosition();
requestUpdate();
logBubbleEvent(previouslySelected,
@@ -1354,15 +1455,14 @@ public class BubbleStackView extends FrameLayout {
mManageEducationView.setAlpha(0);
mManageEducationView.setVisibility(VISIBLE);
mManageEducationView.post(() -> {
- final Rect position =
- mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
+ mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
final int viewHeight = mManageEducationView.getManageViewHeight();
final int inset = getResources().getDimensionPixelSize(
R.dimen.bubbles_manage_education_top_inset);
mManageEducationView.bringToFront();
- mManageEducationView.setManageViewPosition(position.left,
- position.top - viewHeight + inset);
- mManageEducationView.setPointerPosition(position.centerX() - position.left);
+ mManageEducationView.setManageViewPosition(mTempRect.left,
+ mTempRect.top - viewHeight + inset);
+ mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left);
mManageEducationView.animate()
.setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
.setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
@@ -1436,6 +1536,9 @@ public class BubbleStackView extends FrameLayout {
}
private void animateCollapse() {
+ // Hide the menu if it's visible.
+ showManageMenu(false);
+
mIsExpanded = false;
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
@@ -1563,9 +1666,9 @@ public class BubbleStackView extends FrameLayout {
*/
@Override
public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
- // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch
- // events from any location.
- if (mNotificationShadeWindowController.getPanelExpanded()) {
+ // If the notification shade is expanded, or the manage menu is open, we shouldn't let the
+ // ActivityView steal any touch events from any location.
+ if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) {
touchableRegion.setEmpty();
}
}
@@ -1651,17 +1754,19 @@ public class BubbleStackView extends FrameLayout {
private void dismissMagnetizedObject() {
if (mIsExpanded) {
final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
- final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
-
- if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
- mBubbleData.notificationEntryRemoved(
- draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
- }
+ 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.notificationEntryRemoved(
+ bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ }
+ }
+
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
@@ -1905,6 +2010,63 @@ public class BubbleStackView extends FrameLayout {
invalidate();
}
+ private void showManageMenu(boolean show) {
+ mShowingManage = show;
+
+ // This should not happen, since the manage menu is only visible when there's an expanded
+ // bubble. If we end up in this state, just hide the menu immediately.
+ if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+ mManageMenu.setVisibility(View.INVISIBLE);
+ return;
+ }
+
+ // If available, update the manage menu's settings option with the expanded bubble's app
+ // name and icon.
+ if (show && mBubbleData.hasBubbleInStackWithKey(mExpandedBubble.getKey())) {
+ final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
+ mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
+ mManageSettingsText.setText(getResources().getString(
+ R.string.bubbles_app_settings, bubble.getAppName()));
+ }
+
+ mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
+
+ // When the menu is open, it should be at these coordinates. This will make the menu's
+ // bottom left corner match up with the button's bottom left corner.
+ final float targetX = mTempRect.left;
+ final float targetY = mTempRect.bottom - mManageMenu.getHeight();
+
+ if (show) {
+ mManageMenu.setScaleX(0.5f);
+ mManageMenu.setScaleY(0.5f);
+ mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4);
+ mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4);
+ mManageMenu.setAlpha(0f);
+
+ PhysicsAnimator.getInstance(mManageMenu)
+ .spring(DynamicAnimation.ALPHA, 1f)
+ .spring(DynamicAnimation.SCALE_X, 1f)
+ .spring(DynamicAnimation.SCALE_Y, 1f)
+ .spring(DynamicAnimation.TRANSLATION_X, targetX)
+ .spring(DynamicAnimation.TRANSLATION_Y, targetY)
+ .start();
+
+ mManageMenu.setVisibility(View.VISIBLE);
+ } else {
+ PhysicsAnimator.getInstance(mManageMenu)
+ .spring(DynamicAnimation.ALPHA, 0f)
+ .spring(DynamicAnimation.SCALE_X, 0.5f)
+ .spring(DynamicAnimation.SCALE_Y, 0.5f)
+ .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4)
+ .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4)
+ .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+ .start();
+ }
+
+ // Update the AV's obscured touchable region for the new menu visibility state.
+ mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+ }
+
private void updateExpandedBubble() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
@@ -1914,6 +2076,7 @@ public class BubbleStackView extends FrameLayout {
&& mExpandedBubble.getExpandedView() != null) {
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
mExpandedViewContainer.addView(bev);
+ bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
bev.populateExpandedView();
mExpandedViewContainer.setVisibility(VISIBLE);
mExpandedViewContainer.setAlpha(1.0f);
@@ -2076,10 +2239,32 @@ public class BubbleStackView extends FrameLayout {
View child = mBubbleContainer.getChildAt(i);
if (child instanceof BadgedImageView) {
String key = ((BadgedImageView) child).getKey();
- Bubble bubble = mBubbleData.getBubbleWithKey(key);
+ Bubble bubble = mBubbleData.getBubbleInStackWithKey(key);
bubbles.add(bubble);
}
}
return bubbles;
}
+
+ /**
+ * Logs bubble UI click event.
+ *
+ * @param bubble the bubble notification entry that user is interacting with.
+ * @param action the user interaction enum.
+ */
+ private void logBubbleClickEvent(Bubble bubble, int action) {
+ StatusBarNotification notification = bubble.getEntry().getSbn();
+ SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+ notification.getPackageName(),
+ notification.getNotification().getChannelId(),
+ notification.getId(),
+ getBubbleIndex(getExpandedBubble()),
+ getBubbleCount(),
+ action,
+ getNormalizedXPosition(),
+ getNormalizedYPosition(),
+ bubble.showInShade(),
+ bubble.isOngoing(),
+ false /* isAppForeground (unused) */);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 501e5024d940..8a57a735f6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -116,6 +116,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
ShortcutInfo shortcutInfo;
String appName;
Bitmap badgedBubbleImage;
+ Drawable badgedAppIcon;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
@@ -139,22 +140,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
StatusBarNotification sbn = b.getEntry().getSbn();
String packageName = sbn.getPackageName();
- // Real shortcut info for this bubble
String bubbleShortcutId = b.getEntry().getBubbleMetadata().getShortcutId();
if (bubbleShortcutId != null) {
- info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c, packageName,
- sbn.getUser(), bubbleShortcutId);
- } else {
- // Check for experimental shortcut
- String shortcutId = sbn.getNotification().getShortcutId();
- if (BubbleExperimentConfig.useShortcutInfoToBubble(c) && shortcutId != null) {
- info.shortcutInfo = BubbleExperimentConfig.getShortcutInfo(c,
- packageName,
- sbn.getUser(), shortcutId);
- }
+ info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo();
}
-
// App name & app icon
PackageManager pm = c.getPackageManager();
ApplicationInfo appInfo;
@@ -187,6 +177,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
+ info.badgedAppIcon = badgedIcon;
info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
badgeBitmapInfo).icon;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index ef84c73b3145..ca3e2e27fa9a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -20,15 +20,17 @@ import android.graphics.Bitmap;
import android.graphics.Path;
import android.view.View;
+import androidx.annotation.Nullable;
+
/**
* Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
*/
interface BubbleViewProvider {
- BubbleExpandedView getExpandedView();
+ @Nullable BubbleExpandedView getExpandedView();
void setContentVisibility(boolean visible);
- View getIconView();
+ @Nullable View getIconView();
void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index d974adc34ee0..35406c71a080 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -128,17 +128,31 @@ public class ExpandedAnimationController
*/
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.
+ * 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) {
+ public void expandFromStack(
+ @Nullable Runnable after, @Nullable Runnable leadBubbleEndAction) {
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 */);
+ }
+
/** Animate collapsing the bubbles back to their stacked position. */
public void collapseBackToStack(PointF collapsePoint, Runnable after) {
mAnimatingExpand = false;
@@ -237,11 +251,17 @@ public class ExpandedAnimationController
? (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 */)
+ Interpolators.LINEAR /* targetAnimInterpolator */,
+ isLeadBubble ? mLeadBubbleEndAction : null /* endAction */,
+ () -> mLeadBubbleEndAction = null /* endAction */)
.withStartDelay(startDelay)
.withStiffness(EXPAND_COLLAPSE_ANIM_STIFFNESS);
}).startAll(after);
@@ -329,7 +349,7 @@ public class ExpandedAnimationController
}
/** Plays a dismiss animation on the dragged out bubble. */
- public void dismissDraggedOutBubble(View bubble, Runnable after) {
+ public void dismissDraggedOutBubble(View bubble, float translationYBy, Runnable after) {
if (bubble == null) {
return;
}
@@ -337,6 +357,7 @@ public class ExpandedAnimationController
.withStiffness(SpringForce.STIFFNESS_HIGH)
.scaleX(1.1f)
.scaleY(1.1f)
+ .translationY(bubble.getTranslationY() + translationYBy)
.alpha(0f, after)
.start();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index b1bbafc1ed8f..a7d1be1a766a 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -758,21 +758,34 @@ public class PhysicsAnimationLayout extends FrameLayout {
* or {@link #position}, ultimately animating the view's position to the final point on the
* given path.
*
- * Any provided end listeners will be called when the physics-based animations kicked off by
- * the moving target have completed - not when the target animation completes.
+ * @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... endActions) {
+ Runnable... pathAnimEndActions) {
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);
- mPositionEndActions = endActions;
-
// Remove translation related values since we're going to ignore them and follow the
// path instead.
clearTranslationValues();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
index 00de8b4a51b8..5f3a2bd9eb8b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/StackAnimationController.java
@@ -647,17 +647,18 @@ public class StackAnimationController extends
}
/**
- * 'Implode' the stack by shrinking the bubbles via chained animations and fading them out.
+ * 'Implode' the stack by shrinking the bubbles, fading them out, and translating them down.
*/
- public void implodeStack(Runnable after) {
- // Pop and fade the bubbles sequentially.
- animationForChildAtIndex(0)
- .scaleX(0.5f)
- .scaleY(0.5f)
- .alpha(0f)
- .withDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
- .withStiffness(SpringForce.STIFFNESS_HIGH)
- .start(after);
+ public void animateStackDismissal(float translationYBy, Runnable after) {
+ animationsForChildrenFromIndex(0, (index, animation) ->
+ animation
+ .scaleX(0.5f)
+ .scaleY(0.5f)
+ .alpha(0f)
+ .translationY(
+ mLayout.getChildAt(index).getTranslationY() + translationYBy)
+ .withStiffness(SpringForce.STIFFNESS_HIGH))
+ .startAll(after);
}
/**
@@ -710,8 +711,6 @@ public class StackAnimationController extends
if (property.equals(DynamicAnimation.TRANSLATION_X)
|| property.equals(DynamicAnimation.TRANSLATION_Y)) {
return index + 1;
- } else if (isStackStuckToTarget()) {
- return index + 1; // Chain all animations in dismiss (scale, alpha, etc. are used).
} else {
return NONE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
index e84e932c9e61..72d646e0554d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java
@@ -16,6 +16,7 @@
package com.android.systemui.bubbles.dagger;
+import android.app.INotificationManager;
import android.content.Context;
import com.android.systemui.bubbles.BubbleController;
@@ -64,14 +65,15 @@ public interface BubbleModule {
FeatureFlags featureFlags,
DumpManager dumpManager,
FloatingContentCoordinator floatingContentCoordinator,
- SysUiState sysUiState) {
+ SysUiState sysUiState,
+ INotificationManager notifManager) {
return new BubbleController(
context,
notificationShadeWindowController,
statusBarStateController,
shadeController,
data,
- /* synchronizer */null,
+ null /* synchronizer */,
configurationController,
interruptionStateProvider,
zenModeController,
@@ -82,6 +84,7 @@ public interface BubbleModule {
featureFlags,
dumpManager,
floatingContentCoordinator,
- sysUiState);
+ sysUiState,
+ notifManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 7e8fec716b1f..e84f439c1fe2 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -31,6 +31,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import javax.inject.Singleton
@@ -284,13 +285,19 @@ open class ControlsBindingControllerImpl @Inject constructor(
val requestLimit: Long
) : IControlsSubscriber.Stub() {
val loadedControls = ArrayList<Control>()
- private var isTerminated = false
+ private var isTerminated = AtomicBoolean(false)
private var _loadCancelInternal: (() -> Unit)? = null
private lateinit var subscription: IControlsSubscription
+ /**
+ * Potentially cancel a subscriber. The subscriber may also have terminated, in which case
+ * the request is ignored.
+ */
fun loadCancel() = Runnable {
- Log.d(TAG, "Cancel load requested")
- _loadCancelInternal?.invoke()
+ _loadCancelInternal?.let {
+ Log.d(TAG, "Canceling loadSubscribtion")
+ it.invoke()
+ }
}
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
@@ -301,7 +308,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
override fun onNext(token: IBinder, c: Control) {
backgroundExecutor.execute {
- if (isTerminated) return@execute
+ if (isTerminated.get()) return@execute
loadedControls.add(c)
@@ -325,13 +332,15 @@ open class ControlsBindingControllerImpl @Inject constructor(
}
private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
- if (isTerminated) return
+ if (isTerminated.get()) return
- isTerminated = true
_loadCancelInternal = {}
currentProvider?.cancelLoadTimeout()
- backgroundExecutor.execute(postTerminateFn)
+ backgroundExecutor.execute {
+ isTerminated.compareAndSet(false, true)
+ postTerminateFn.run()
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 7cab847d52f7..bc97c10756fd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -23,6 +23,7 @@ import android.service.controls.actions.ControlAction
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.UserAwareController
import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlWithState
import com.android.systemui.controls.ui.ControlsUiController
import java.util.function.Consumer
@@ -51,19 +52,17 @@ interface ControlsController : UserAwareController {
* Load all available [Control] for a given service.
*
* @param componentName the [ComponentName] of the [ControlsProviderService] to load from
- * @param dataCallback a callback in which to retrieve the result.
+ * @param dataCallback a callback in which to retrieve the result
+ * @param cancelWrapper a callback to receive a [Runnable] that can be run to cancel the
+ * request
*/
fun loadForComponent(
componentName: ComponentName,
- dataCallback: Consumer<LoadData>
+ dataCallback: Consumer<LoadData>,
+ cancelWrapper: Consumer<Runnable>
)
/**
- * Cancels a pending load call
- */
- fun cancelLoad()
-
- /**
* Request to subscribe for favorited controls per structure
*
* @param structureInfo structure to limit the subscription to
@@ -111,6 +110,13 @@ interface ControlsController : UserAwareController {
@ControlAction.ResponseResult response: Int
)
+ /**
+ * When a control should be highlighted, dimming down what's around it.
+ *
+ * @param cws focused control, or {@code null} if nothing should be highlighted.
+ */
+ fun onFocusChanged(cws: ControlWithState?)
+
// FAVORITE MANAGEMENT
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 6d34009169d5..8e88756b16fd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -41,6 +41,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.ControlStatus
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlWithState
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
@@ -76,8 +77,6 @@ class ControlsControllerImpl @Inject constructor (
private var userChanging: Boolean = true
- private var loadCanceller: Runnable? = null
-
private var seedingInProgress = false
private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
@@ -275,28 +274,29 @@ class ControlsControllerImpl @Inject constructor (
override fun loadForComponent(
componentName: ComponentName,
- dataCallback: Consumer<ControlsController.LoadData>
+ dataCallback: Consumer<ControlsController.LoadData>,
+ cancelWrapper: Consumer<Runnable>
) {
if (!confirmAvailability()) {
if (userChanging) {
// Try again later, userChanging should not last forever. If so, we have bigger
// problems. This will return a runnable that allows to cancel the delayed version,
// it will not be able to cancel the load if
- loadCanceller = executor.executeDelayed(
- { loadForComponent(componentName, dataCallback) },
- USER_CHANGE_RETRY_DELAY,
- TimeUnit.MILLISECONDS
+ executor.executeDelayed(
+ { loadForComponent(componentName, dataCallback, cancelWrapper) },
+ USER_CHANGE_RETRY_DELAY,
+ TimeUnit.MILLISECONDS
)
- } else {
- dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
}
- return
+
+ dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
}
- loadCanceller = bindingController.bindAndLoad(
+
+ cancelWrapper.accept(
+ bindingController.bindAndLoad(
componentName,
object : ControlsBindingController.LoadCallback {
override fun accept(controls: List<Control>) {
- loadCanceller = null
executor.execute {
val favoritesForComponentKeys = Favorites
.getControlsForComponent(componentName).map { it.controlId }
@@ -332,7 +332,6 @@ class ControlsControllerImpl @Inject constructor (
}
override fun error(message: String) {
- loadCanceller = null
executor.execute {
val controls = Favorites.getStructuresForComponent(componentName)
.flatMap { st ->
@@ -347,6 +346,7 @@ class ControlsControllerImpl @Inject constructor (
}
}
}
+ )
)
}
@@ -431,12 +431,6 @@ class ControlsControllerImpl @Inject constructor (
seedingCallbacks.clear()
}
- override fun cancelLoad() {
- loadCanceller?.let {
- executor.execute(it)
- }
- }
-
private fun createRemovedStatus(
componentName: ComponentName,
controlInfo: ControlInfo,
@@ -504,6 +498,10 @@ class ControlsControllerImpl @Inject constructor (
}
}
+ override fun onFocusChanged(cws: ControlWithState?) {
+ uiController.onFocusChanged(cws)
+ }
+
override fun refreshStatus(componentName: ComponentName, control: Control) {
if (!confirmAvailability()) {
Log.d(TAG, "Controls not available")
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 3bed55912332..5765be57b5b0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -30,6 +30,8 @@ import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import com.android.systemui.controls.ui.ControlActionCoordinator
+import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
@@ -55,6 +57,11 @@ abstract class ControlsModule {
@Binds
abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
+ @Binds
+ abstract fun provideControlActionCoordinator(
+ coordinator: ControlActionCoordinatorImpl
+ ): ControlActionCoordinator
+
@BindsOptionalOf
abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
@@ -85,4 +92,4 @@ abstract class ControlsModule {
abstract fun provideControlsRequestDialog(
activity: ControlsRequestDialog
): Activity
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index 8b3454a2bc7c..0bc6579739db 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -102,7 +102,9 @@ class AppAdapter(
fun bindData(data: ControlsServiceInfo) {
icon.setImageDrawable(data.loadIcon())
title.text = data.loadLabel()
- favorites.text = favRenderer.renderFavoritesForComponent(data.componentName)
+ val text = favRenderer.renderFavoritesForComponent(data.componentName)
+ favorites.text = text
+ favorites.visibility = if (text == null) View.GONE else View.VISIBLE
}
}
}
@@ -112,12 +114,12 @@ class FavoritesRenderer(
private val favoriteFunction: (ComponentName) -> Int
) {
- fun renderFavoritesForComponent(component: ComponentName): String {
+ fun renderFavoritesForComponent(component: ComponentName): String? {
val qty = favoriteFunction(component)
if (qty != 0) {
return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty)
} else {
- return ""
+ return null
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
new file mode 100644
index 000000000000..cad166d7cd9e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.annotation.IdRes
+import android.content.Intent
+
+import android.transition.Transition
+import android.transition.TransitionValues
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+
+import com.android.systemui.Interpolators
+import com.android.systemui.R
+
+import com.android.systemui.controls.ui.ControlsUiController
+
+object ControlsAnimations {
+
+ private const val ALPHA_EXIT_DURATION = 167L
+ private const val ALPHA_ENTER_DELAY = ALPHA_EXIT_DURATION
+ private const val ALPHA_ENTER_DURATION = 350L - ALPHA_ENTER_DELAY
+
+ private const val Y_TRANSLATION_EXIT_DURATION = 183L
+ private const val Y_TRANSLATION_ENTER_DELAY = Y_TRANSLATION_EXIT_DURATION - ALPHA_ENTER_DELAY
+ private const val Y_TRANSLATION_ENTER_DURATION = 400L - Y_TRANSLATION_EXIT_DURATION
+ private var translationY: Float = -1f
+
+ /**
+ * Setup an activity to handle enter/exit animations. [view] should be the root of the content.
+ * Fade and translate together.
+ */
+ fun observerForAnimations(view: ViewGroup, window: Window, intent: Intent): LifecycleObserver {
+ return object : LifecycleObserver {
+ var showAnimation = intent.getBooleanExtra(ControlsUiController.EXTRA_ANIMATE, false)
+
+ init {
+ // Must flag the parent group to move it all together, and set the initial
+ // transitionAlpha to 0.0f. This property is reserved for fade animations.
+ view.setTransitionGroup(true)
+ view.transitionAlpha = 0.0f
+
+ if (translationY == -1f) {
+ translationY = view.context.resources.getDimensionPixelSize(
+ R.dimen.global_actions_controls_y_translation).toFloat()
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ fun setup() {
+ with(window) {
+ allowEnterTransitionOverlap = true
+ enterTransition = enterWindowTransition(view.getId())
+ exitTransition = exitWindowTransition(view.getId())
+ reenterTransition = enterWindowTransition(view.getId())
+ returnTransition = exitWindowTransition(view.getId())
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
+ fun enterAnimation() {
+ if (showAnimation) {
+ ControlsAnimations.enterAnimation(view).start()
+ showAnimation = false
+ }
+ }
+
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ fun resetAnimation() {
+ view.translationY = 0f
+ }
+ }
+ }
+
+ fun enterAnimation(view: View): Animator {
+ Log.d(ControlsUiController.TAG, "Enter animation for $view")
+
+ view.transitionAlpha = 0.0f
+ view.alpha = 1.0f
+
+ view.translationY = translationY
+
+ val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f, 1.0f).apply {
+ interpolator = Interpolators.DECELERATE_QUINT
+ startDelay = ALPHA_ENTER_DELAY
+ duration = ALPHA_ENTER_DURATION
+ }
+
+ val yAnimator = ObjectAnimator.ofFloat(view, "translationY", 0.0f).apply {
+ interpolator = Interpolators.DECELERATE_QUINT
+ startDelay = Y_TRANSLATION_ENTER_DURATION
+ duration = Y_TRANSLATION_ENTER_DURATION
+ }
+
+ return AnimatorSet().apply {
+ playTogether(alphaAnimator, yAnimator)
+ }
+ }
+
+ /**
+ * Properly handle animations originating from dialogs. Activity transitions require
+ * transitioning between two activities, so expose this method for dialogs to animate
+ * on exit.
+ */
+ @JvmStatic
+ fun exitAnimation(view: View, onEnd: Runnable? = null): Animator {
+ Log.d(ControlsUiController.TAG, "Exit animation for $view")
+
+ val alphaAnimator = ObjectAnimator.ofFloat(view, "transitionAlpha", 0.0f).apply {
+ interpolator = Interpolators.ACCELERATE
+ duration = ALPHA_EXIT_DURATION
+ }
+
+ view.translationY = 0.0f
+ val yAnimator = ObjectAnimator.ofFloat(view, "translationY", -translationY).apply {
+ interpolator = Interpolators.ACCELERATE
+ duration = Y_TRANSLATION_EXIT_DURATION
+ }
+
+ return AnimatorSet().apply {
+ playTogether(alphaAnimator, yAnimator)
+ onEnd?.let {
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ it.run()
+ }
+ })
+ }
+ }
+ }
+
+ fun enterWindowTransition(@IdRes id: Int) =
+ WindowTransition({ view: View -> enterAnimation(view) }).apply {
+ addTarget(id)
+ }
+
+ fun exitWindowTransition(@IdRes id: Int) =
+ WindowTransition({ view: View -> exitAnimation(view) }).apply {
+ addTarget(id)
+ }
+}
+
+/**
+ * In order to animate, at least one property must be marked on each view that should move.
+ * Setting "item" is just a flag to indicate that it should move by the animator.
+ */
+class WindowTransition(
+ val animator: (view: View) -> Animator
+) : Transition() {
+ override fun captureStartValues(tv: TransitionValues) {
+ tv.values["item"] = 0.0f
+ }
+
+ override fun captureEndValues(tv: TransitionValues) {
+ tv.values["item"] = 1.0f
+ }
+
+ override fun createAnimator(
+ sceneRoot: ViewGroup,
+ startValues: TransitionValues?,
+ endValues: TransitionValues?
+ ): Animator? = animator(startValues!!.view)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index ee1ce7ab3d83..4e9c550297c5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -16,11 +16,11 @@
package com.android.systemui.controls.management
-import android.app.Activity
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import android.view.View
+import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
@@ -31,7 +31,9 @@ import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.util.LifecycleActivity
import javax.inject.Inject
/**
@@ -39,8 +41,9 @@ import javax.inject.Inject
*/
class ControlsEditingActivity @Inject constructor(
private val controller: ControlsControllerImpl,
- broadcastDispatcher: BroadcastDispatcher
-) : Activity() {
+ broadcastDispatcher: BroadcastDispatcher,
+ private val globalActionsComponent: GlobalActionsComponent
+) : LifecycleActivity() {
companion object {
private const val TAG = "ControlsEditingActivity"
@@ -66,10 +69,6 @@ class ControlsEditingActivity @Inject constructor(
}
}
- override fun onBackPressed() {
- finish()
- }
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -84,14 +83,48 @@ class ControlsEditingActivity @Inject constructor(
bindViews()
bindButtons()
+ }
+ override fun onStart() {
+ super.onStart()
setUpList()
currentUserTracker.startTracking()
}
+ override fun onStop() {
+ super.onStop()
+ currentUserTracker.stopTracking()
+ }
+
+ override fun onBackPressed() {
+ globalActionsComponent.handleShowGlobalActionsMenu()
+ animateExitAndFinish()
+ }
+
+ private fun animateExitAndFinish() {
+ val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
+ ControlsAnimations.exitAnimation(
+ rootView,
+ object : Runnable {
+ override fun run() {
+ finish()
+ }
+ }
+ ).start()
+ }
+
private fun bindViews() {
setContentView(R.layout.controls_management)
+
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.controls_management_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_editing
inflate()
@@ -103,27 +136,14 @@ class ControlsEditingActivity @Inject constructor(
}
private fun bindButtons() {
- requireViewById<Button>(R.id.other_apps).apply {
- visibility = View.VISIBLE
- setText(R.string.controls_menu_add)
- setOnClickListener {
- saveFavorites()
- val intent = Intent(this@ControlsEditingActivity,
- ControlsFavoritingActivity::class.java).apply {
- putExtras(this@ControlsEditingActivity.intent)
- putExtra(ControlsFavoritingActivity.EXTRA_SINGLE_STRUCTURE, true)
- }
- startActivity(intent)
- finish()
- }
- }
-
+ val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
saveButton = requireViewById<Button>(R.id.done).apply {
isEnabled = false
setText(R.string.save)
setOnClickListener {
saveFavorites()
- finishAffinity()
+ animateExitAndFinish()
+ globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}
@@ -151,26 +171,38 @@ class ControlsEditingActivity @Inject constructor(
val controls = controller.getFavoritesForStructure(component, structure)
model = FavoritesModel(component, controls, favoritesModelCallback)
val elevation = resources.getFloat(R.dimen.control_card_elevation)
- val adapter = ControlAdapter(elevation)
- val recycler = requireViewById<RecyclerView>(R.id.list)
+ val recyclerView = requireViewById<RecyclerView>(R.id.list)
+ recyclerView.alpha = 0.0f
+ val adapter = ControlAdapter(elevation).apply {
+ registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+ override fun onChanged() {
+ if (!hasAnimated) {
+ hasAnimated = true
+ ControlsAnimations.enterAnimation(recyclerView).start()
+ }
+ }
+ })
+ }
+
val margin = resources
.getDimensionPixelSize(R.dimen.controls_card_margin)
val itemDecorator = MarginItemDecorator(margin, margin)
- recycler.apply {
+ recyclerView.apply {
this.adapter = adapter
- layoutManager = GridLayoutManager(recycler.context, 2).apply {
+ layoutManager = GridLayoutManager(recyclerView.context, 2).apply {
spanSizeLookup = adapter.spanSizeLookup
}
addItemDecoration(itemDecorator)
}
adapter.changeModel(model)
model.attachAdapter(adapter)
- ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recycler)
+ ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView)
}
override fun onDestroy() {
currentUserTracker.stopTracking()
super.onDestroy()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 6f34deeb8547..e3175aafb1b1 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -16,11 +16,12 @@
package com.android.systemui.controls.management
-import android.app.Activity
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.content.res.Configuration
-import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.TextUtils
import android.view.Gravity
@@ -29,8 +30,8 @@ import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.FrameLayout
-import android.widget.ImageView
import android.widget.TextView
+import android.widget.Toast
import androidx.viewpager2.widget.ViewPager2
import com.android.systemui.Prefs
import com.android.systemui.R
@@ -40,7 +41,9 @@ import com.android.systemui.controls.TooltipManager
import com.android.systemui.controls.controller.ControlsControllerImpl
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
+import com.android.systemui.util.LifecycleActivity
import java.text.Collator
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -50,8 +53,9 @@ class ControlsFavoritingActivity @Inject constructor(
@Main private val executor: Executor,
private val controller: ControlsControllerImpl,
private val listingController: ControlsListingController,
- broadcastDispatcher: BroadcastDispatcher
-) : Activity() {
+ broadcastDispatcher: BroadcastDispatcher,
+ private val globalActionsComponent: GlobalActionsComponent
+) : LifecycleActivity() {
companion object {
private const val TAG = "ControlsFavoritingActivity"
@@ -62,6 +66,7 @@ class ControlsFavoritingActivity @Inject constructor(
// If provided, show this structure page first
const val EXTRA_STRUCTURE = "extra_structure"
const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure"
+ internal const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector"
private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT
private const val TOOLTIP_MAX_SHOWN = 2
}
@@ -69,18 +74,20 @@ class ControlsFavoritingActivity @Inject constructor(
private var component: ComponentName? = null
private var appName: CharSequence? = null
private var structureExtra: CharSequence? = null
+ private var fromProviderSelector = false
private lateinit var structurePager: ViewPager2
private lateinit var statusText: TextView
private lateinit var titleView: TextView
- private lateinit var iconView: ImageView
- private lateinit var iconFrame: View
private lateinit var pageIndicator: ManagementPageIndicator
private var mTooltipManager: TooltipManager? = null
private lateinit var doneButton: View
+ private lateinit var otherAppsButton: View
private var listOfStructures = emptyList<StructureContainer>()
private lateinit var comparator: Comparator<StructureContainer>
+ private var cancelLoadRunnable: Runnable? = null
+ private var isPagerLoaded = false
private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
private val startingUser = controller.currentUserId
@@ -94,42 +101,32 @@ class ControlsFavoritingActivity @Inject constructor(
}
private val listingCallback = object : ControlsListingController.ControlsListingCallback {
- private var icon: Drawable? = null
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
- val newIcon = serviceInfos.firstOrNull { it.componentName == component }?.loadIcon()
- if (icon == newIcon) return
- icon = newIcon
- executor.execute {
- if (icon != null) {
- iconView.setImageDrawable(icon)
- }
- iconFrame.visibility = if (icon != null) View.VISIBLE else View.GONE
+ if (serviceInfos.size > 1) {
+ otherAppsButton.visibility = View.VISIBLE
}
}
}
override fun onBackPressed() {
- finish()
+ if (!fromProviderSelector) {
+ globalActionsComponent.handleShowGlobalActionsMenu()
+ }
+ animateExitAndFinish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
val collator = Collator.getInstance(resources.configuration.locales[0])
comparator = compareBy(collator) { it.structureName }
appName = intent.getCharSequenceExtra(EXTRA_APP)
structureExtra = intent.getCharSequenceExtra(EXTRA_STRUCTURE) ?: ""
component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
+ fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false)
bindViews()
-
- setUpPager()
-
- loadControls()
-
- listingController.addCallback(listingCallback)
-
- currentUserTracker.startTracking()
}
private val controlsModelCallback = object : ControlsModel.ControlsModelCallback {
@@ -174,12 +171,33 @@ class ControlsFavoritingActivity @Inject constructor(
pageIndicator.setLocation(0f)
pageIndicator.visibility =
if (listOfStructures.size > 1) View.VISIBLE else View.GONE
+
+ ControlsAnimations.enterAnimation(pageIndicator).apply {
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ // Position the tooltip if necessary after animations are complete
+ // so we can get the position on screen. The tooltip is not
+ // rooted in the layout root.
+ if (pageIndicator.visibility == View.VISIBLE &&
+ mTooltipManager != null) {
+ val p = IntArray(2)
+ pageIndicator.getLocationOnScreen(p)
+ val x = p[0] + pageIndicator.width / 2
+ val y = p[1] + pageIndicator.height
+ mTooltipManager?.show(R.string.controls_structure_tooltip, x, y)
+ }
+ }
+ })
+ }.start()
+ ControlsAnimations.enterAnimation(structurePager).start()
}
- })
+ }, Consumer { runnable -> cancelLoadRunnable = runnable })
}
}
private fun setUpPager() {
+ structurePager.alpha = 0.0f
+ pageIndicator.alpha = 0.0f
structurePager.apply {
adapter = StructureAdapter(emptyList())
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
@@ -203,6 +221,15 @@ class ControlsFavoritingActivity @Inject constructor(
private fun bindViews() {
setContentView(R.layout.controls_management)
+
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.controls_management_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_favorites
inflate()
@@ -223,27 +250,6 @@ class ControlsFavoritingActivity @Inject constructor(
}
pageIndicator = requireViewById<ManagementPageIndicator>(
R.id.structure_page_indicator).apply {
- addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
- override fun onLayoutChange(
- v: View,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- if (v.visibility == View.VISIBLE && mTooltipManager != null) {
- val p = IntArray(2)
- v.getLocationOnScreen(p)
- val x = p[0] + (right - left) / 2
- val y = p[1] + bottom - top
- mTooltipManager?.show(R.string.controls_structure_tooltip, x, y)
- }
- }
- })
visibilityListener = {
if (it != View.VISIBLE) {
mTooltipManager?.hide(true)
@@ -259,8 +265,6 @@ class ControlsFavoritingActivity @Inject constructor(
}
requireViewById<TextView>(R.id.subtitle).text =
resources.getText(R.string.controls_favorite_subtitle)
- iconView = requireViewById(com.android.internal.R.id.icon)
- iconFrame = requireViewById(R.id.icon_frame)
structurePager = requireViewById<ViewPager2>(R.id.structure_pager)
structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
@@ -271,14 +275,35 @@ class ControlsFavoritingActivity @Inject constructor(
bindButtons()
}
+ private fun animateExitAndFinish() {
+ val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
+ ControlsAnimations.exitAnimation(
+ rootView,
+ object : Runnable {
+ override fun run() {
+ finish()
+ }
+ }
+ ).start()
+ }
+
private fun bindButtons() {
- requireViewById<Button>(R.id.other_apps).apply {
- visibility = View.VISIBLE
+ otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
setOnClickListener {
- val i = Intent()
- i.setComponent(
- ComponentName(context, ControlsProviderSelectorActivity::class.java))
- context.startActivity(i)
+ val i = Intent().apply {
+ component = ComponentName(context, ControlsProviderSelectorActivity::class.java)
+ }
+ if (doneButton.isEnabled) {
+ // The user has made changes
+ Toast.makeText(
+ applicationContext,
+ R.string.controls_favorite_toast_no_changes,
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ startActivity(i, ActivityOptions
+ .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle())
+ animateExitAndFinish()
}
}
@@ -292,7 +317,8 @@ class ControlsFavoritingActivity @Inject constructor(
StructureInfo(component!!, it.structureName, favoritesForStorage)
)
}
- finishAffinity()
+ animateExitAndFinish()
+ globalActionsComponent.handleShowGlobalActionsMenu()
}
}
}
@@ -302,15 +328,39 @@ class ControlsFavoritingActivity @Inject constructor(
mTooltipManager?.hide(false)
}
+ override fun onStart() {
+ super.onStart()
+
+ listingController.addCallback(listingCallback)
+ currentUserTracker.startTracking()
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ // only do once, to make sure that any user changes do not get replaces if resume is called
+ // more than once
+ if (!isPagerLoaded) {
+ setUpPager()
+ loadControls()
+ isPagerLoaded = true
+ }
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ listingController.removeCallback(listingCallback)
+ currentUserTracker.stopTracking()
+ }
+
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mTooltipManager?.hide(false)
}
override fun onDestroy() {
- currentUserTracker.stopTracking()
- listingController.removeCallback(listingCallback)
- controller.cancelLoad()
+ cancelLoadRunnable?.run()
super.onDestroy()
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 3be59009f531..08147746a4c8 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -16,20 +16,25 @@
package com.android.systemui.controls.management
+import android.app.ActivityOptions
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
import android.view.ViewStub
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.settings.CurrentUserTracker
import com.android.systemui.util.LifecycleActivity
import java.util.concurrent.Executor
@@ -43,6 +48,7 @@ class ControlsProviderSelectorActivity @Inject constructor(
@Background private val backExecutor: Executor,
private val listingController: ControlsListingController,
private val controlsController: ControlsController,
+ private val globalActionsComponent: GlobalActionsComponent,
broadcastDispatcher: BroadcastDispatcher
) : LifecycleActivity() {
@@ -66,12 +72,47 @@ class ControlsProviderSelectorActivity @Inject constructor(
super.onCreate(savedInstanceState)
setContentView(R.layout.controls_management)
+
+ getLifecycle().addObserver(
+ ControlsAnimations.observerForAnimations(
+ requireViewById<ViewGroup>(R.id.controls_management_root),
+ window,
+ intent
+ )
+ )
+
requireViewById<ViewStub>(R.id.stub).apply {
layoutResource = R.layout.controls_management_apps
inflate()
}
recyclerView = requireViewById(R.id.list)
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ requireViewById<TextView>(R.id.title).apply {
+ text = resources.getText(R.string.controls_providers_title)
+ }
+
+ requireViewById<Button>(R.id.other_apps).apply {
+ visibility = View.VISIBLE
+ setText(com.android.internal.R.string.cancel)
+ setOnClickListener {
+ onBackPressed()
+ }
+ }
+ requireViewById<View>(R.id.done).visibility = View.GONE
+ }
+
+ override fun onBackPressed() {
+ globalActionsComponent.handleShowGlobalActionsMenu()
+ animateExitAndFinish()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ currentUserTracker.startTracking()
+
+ recyclerView.alpha = 0.0f
recyclerView.adapter = AppAdapter(
backExecutor,
executor,
@@ -80,17 +121,22 @@ class ControlsProviderSelectorActivity @Inject constructor(
LayoutInflater.from(this),
::launchFavoritingActivity,
FavoritesRenderer(resources, controlsController::countFavoritesForComponent),
- resources)
- recyclerView.layoutManager = LinearLayoutManager(applicationContext)
-
- requireViewById<TextView>(R.id.title).text =
- resources.getText(R.string.controls_providers_title)
-
- requireViewById<Button>(R.id.done).setOnClickListener {
- this@ControlsProviderSelectorActivity.finishAffinity()
+ resources).apply {
+ registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
+ var hasAnimated = false
+ override fun onChanged() {
+ if (!hasAnimated) {
+ hasAnimated = true
+ ControlsAnimations.enterAnimation(recyclerView).start()
+ }
+ }
+ })
}
+ }
- currentUserTracker.startTracking()
+ override fun onStop() {
+ super.onStop()
+ currentUserTracker.stopTracking()
}
/**
@@ -98,16 +144,16 @@ class ControlsProviderSelectorActivity @Inject constructor(
* @param component a component name for a [ControlsProviderService]
*/
fun launchFavoritingActivity(component: ComponentName?) {
- backExecutor.execute {
+ executor.execute {
component?.let {
val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java)
.apply {
putExtra(ControlsFavoritingActivity.EXTRA_APP,
listingController.getAppLabel(it))
putExtra(Intent.EXTRA_COMPONENT_NAME, it)
- flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+ putExtra(ControlsFavoritingActivity.EXTRA_FROM_PROVIDER_SELECTOR, true)
}
- startActivity(intent)
+ startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
}
}
}
@@ -116,4 +162,16 @@ class ControlsProviderSelectorActivity @Inject constructor(
currentUserTracker.stopTracking()
super.onDestroy()
}
+
+ private fun animateExitAndFinish() {
+ val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
+ ControlsAnimations.exitAnimation(
+ rootView,
+ object : Runnable {
+ override fun run() {
+ finish()
+ }
+ }
+ ).start()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index a7a41033bb5d..1f07e37d2ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -47,16 +47,31 @@ object ChallengeDialogs {
* [ControlAction#RESPONSE_CHALLENGE_PIN] responses, decided by the useAlphaNumeric
* parameter.
*/
- fun createPinDialog(cvh: ControlViewHolder, useAlphaNumeric: Boolean): Dialog? {
+ fun createPinDialog(
+ cvh: ControlViewHolder,
+ useAlphaNumeric: Boolean,
+ useRetryStrings: Boolean
+ ): Dialog? {
val lastAction = cvh.lastAction
if (lastAction == null) {
Log.e(ControlsUiController.TAG,
"PIN Dialog attempted but no last action is set. Will not show")
return null
}
+ val res = cvh.context.resources
+ val (title, instructions) = if (useRetryStrings) {
+ Pair(
+ res.getString(R.string.controls_pin_wrong),
+ R.string.controls_pin_instructions_retry
+ )
+ } else {
+ Pair(
+ res.getString(R.string.controls_pin_verify, cvh.title.getText()),
+ R.string.controls_pin_instructions
+ )
+ }
val builder = AlertDialog.Builder(cvh.context, STYLE).apply {
- val res = cvh.context.resources
- setTitle(res.getString(R.string.controls_pin_verify, cvh.title.getText()))
+ setTitle(title)
setView(R.layout.controls_dialog_pin)
setPositiveButton(
android.R.string.ok,
@@ -81,6 +96,7 @@ object ChallengeDialogs {
}
setOnShowListener(DialogInterface.OnShowListener { _ ->
val editText = requireViewById<EditText>(R.id.controls_pin_input)
+ editText.setHint(instructions)
val useAlphaCheckBox = requireViewById<CheckBox>(R.id.controls_pin_use_alpha)
useAlphaCheckBox.setChecked(useAlphaNumeric)
setInputType(editText, useAlphaCheckBox.isChecked())
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
index ad86eeb089c8..70092d31fe1e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt
@@ -16,68 +16,52 @@
package com.android.systemui.controls.ui
-import android.app.Dialog
-import android.app.PendingIntent
-import android.content.Intent
import android.service.controls.Control
-import android.service.controls.actions.BooleanAction
-import android.service.controls.actions.CommandAction
-import android.util.Log
-import android.view.HapticFeedbackConstants
-import com.android.systemui.R
-
-object ControlActionCoordinator {
- public const val MIN_LEVEL = 0
- public const val MAX_LEVEL = 10000
-
- private var dialog: Dialog? = null
+/**
+ * All control interactions should be routed through this coordinator. It handles dispatching of
+ * actions, haptic support, and all detail panels
+ */
+interface ControlActionCoordinator {
- fun closeDialog() {
- dialog?.dismiss()
- dialog = null
- }
+ /**
+ * Close any dialogs which may have been open
+ */
+ fun closeDialogs()
- fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
- cvh.action(BooleanAction(templateId, !isChecked))
+ /**
+ * Create a [BooleanAction], and inform the service of a request to change the device state
+ *
+ * @param cvh [ControlViewHolder] for the control
+ * @param templateId id of the control's template, as given by the service
+ * @param isChecked new requested state of the control
+ */
+ fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean)
- val nextLevel = if (isChecked) MIN_LEVEL else MAX_LEVEL
- cvh.clipLayer.setLevel(nextLevel)
- }
+ /**
+ * For non-toggle controls, touching may create a dialog or invoke a [CommandAction].
+ *
+ * @param cvh [ControlViewHolder] for the control
+ * @param templateId id of the control's template, as given by the service
+ * @param control the control as sent by the service
+ */
+ fun touch(cvh: ControlViewHolder, templateId: String, control: Control)
- fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
- if (cvh.usePanel()) {
- showDialog(cvh, control.getAppIntent().getIntent())
- } else {
- cvh.action(CommandAction(templateId))
- }
- }
+ /**
+ * When a ToggleRange control is interacting with, a drag event is sent.
+ *
+ * @param isEdge did the drag event reach a control edge
+ */
+ fun drag(isEdge: Boolean)
/**
- * Allow apps to specify whether they would like to appear in a detail panel or within
- * the full activity by setting the {@link Control#EXTRA_USE_PANEL} flag. In order for
- * activities to determine how they are being launched, they should inspect the
- * {@link Control#EXTRA_USE_PANEL} flag for a value of true.
+ * All long presses will be shown in a 3/4 height bottomsheet panel, in order for the user to
+ * retain context with their favorited controls in the power menu.
*/
- fun longPress(cvh: ControlViewHolder) {
- // Long press snould only be called when there is valid control state, otherwise ignore
- cvh.cws.control?.let {
- try {
- it.getAppIntent().send()
- cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
- cvh.context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
- } catch (e: PendingIntent.CanceledException) {
- Log.e(ControlsUiController.TAG, "Error sending pending intent", e)
- cvh.setTransientStatus(
- cvh.context.resources.getString(R.string.controls_error_failed))
- }
- }
- }
+ fun longPress(cvh: ControlViewHolder)
- private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
- dialog = DetailDialog(cvh, intent).also {
- it.setOnDismissListener { _ -> dialog = null }
- it.show()
- }
- }
+ /**
+ * Event to inform the UI that the user has has focused on a single control.
+ */
+ fun setFocusedElement(cvh: ControlViewHolder?)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
new file mode 100644
index 000000000000..9e6f58851caf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.app.Dialog
+import android.content.Context
+import android.content.Intent
+import android.os.Vibrator
+import android.os.VibrationEffect
+import android.service.controls.Control
+import android.service.controls.actions.BooleanAction
+import android.service.controls.actions.CommandAction
+import android.util.Log
+import android.view.HapticFeedbackConstants
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.globalactions.GlobalActionsComponent
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.DelayableExecutor
+
+import dagger.Lazy
+
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlActionCoordinatorImpl @Inject constructor(
+ private val context: Context,
+ private val bgExecutor: DelayableExecutor,
+ private val controlsController: Lazy<ControlsController>,
+ private val activityStarter: ActivityStarter,
+ private val keyguardStateController: KeyguardStateController,
+ private val globalActionsComponent: GlobalActionsComponent
+) : ControlActionCoordinator {
+ private var dialog: Dialog? = null
+ private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ private var lastAction: (() -> Unit)? = null
+
+ override fun closeDialogs() {
+ dialog?.dismiss()
+ dialog = null
+ }
+
+ override fun toggle(cvh: ControlViewHolder, templateId: String, isChecked: Boolean) {
+ bouncerOrRun {
+ val effect = if (isChecked) Vibrations.toggleOnEffect else Vibrations.toggleOffEffect
+ vibrate(effect)
+ cvh.action(BooleanAction(templateId, !isChecked))
+ }
+ }
+
+ override fun touch(cvh: ControlViewHolder, templateId: String, control: Control) {
+ vibrate(Vibrations.toggleOnEffect)
+
+ bouncerOrRun {
+ if (cvh.usePanel()) {
+ showDialog(cvh, control.getAppIntent().getIntent())
+ } else {
+ cvh.action(CommandAction(templateId))
+ }
+ }
+ }
+
+ override fun drag(isEdge: Boolean) {
+ bouncerOrRun {
+ if (isEdge) {
+ vibrate(Vibrations.rangeEdgeEffect)
+ } else {
+ vibrate(Vibrations.rangeMiddleEffect)
+ }
+ }
+ }
+
+ override fun longPress(cvh: ControlViewHolder) {
+ bouncerOrRun {
+ // Long press snould only be called when there is valid control state, otherwise ignore
+ cvh.cws.control?.let {
+ cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
+ showDialog(cvh, it.getAppIntent().getIntent())
+ }
+ }
+ }
+
+ override fun setFocusedElement(cvh: ControlViewHolder?) {
+ controlsController.get().onFocusChanged(cvh?.cws)
+ }
+
+ private fun bouncerOrRun(f: () -> Unit) {
+ if (!keyguardStateController.isUnlocked()) {
+ context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
+ activityStarter.dismissKeyguardThenExecute({
+ Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action")
+ globalActionsComponent.handleShowGlobalActionsMenu()
+ f()
+ true
+ }, null, true)
+ } else {
+ f()
+ }
+ }
+
+ private fun vibrate(effect: VibrationEffect) {
+ bgExecutor.execute { vibrator.vibrate(effect) }
+ }
+
+ private fun showDialog(cvh: ControlViewHolder, intent: Intent) {
+ dialog = DetailDialog(cvh, intent).also {
+ it.setOnDismissListener { _ -> dialog = null }
+ it.show()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index 0eb6cb100933..a9b540eddb2c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -16,6 +16,10 @@
package com.android.systemui.controls.ui
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.app.Dialog
import android.content.Context
import android.graphics.drawable.ClipDrawable
import android.graphics.drawable.GradientDrawable
@@ -28,15 +32,16 @@ import android.service.controls.templates.StatelessTemplate
import android.service.controls.templates.TemperatureControlTemplate
import android.service.controls.templates.ToggleRangeTemplate
import android.service.controls.templates.ToggleTemplate
+import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
-
+import com.android.internal.graphics.ColorUtils
+import com.android.systemui.Interpolators
+import com.android.systemui.R
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.R
-
import kotlin.reflect.KClass
/**
@@ -49,19 +54,29 @@ class ControlViewHolder(
val controlsController: ControlsController,
val uiExecutor: DelayableExecutor,
val bgExecutor: DelayableExecutor,
- val usePanels: Boolean
+ val controlActionCoordinator: ControlActionCoordinator
) {
companion object {
+ const val STATE_ANIMATION_DURATION = 700L
private const val UPDATE_DELAY_IN_MILLIS = 3000L
- private const val ALPHA_ENABLED = (255.0 * 0.2).toInt()
- private const val ALPHA_DISABLED = 255
+ private const val ALPHA_ENABLED = 255
+ private const val ALPHA_DISABLED = 0
private val FORCE_PANEL_DEVICES = setOf(
DeviceTypes.TYPE_THERMOSTAT,
DeviceTypes.TYPE_CAMERA
)
+
+ const val MIN_LEVEL = 0
+ const val MAX_LEVEL = 10000
}
+ private val toggleBackgroundIntensity: Float = layout.context.resources
+ .getFraction(R.fraction.controls_toggle_bg_intensity, 1, 1)
+ private val dimmedAlpha: Float = layout.context.resources
+ .getFraction(R.fraction.controls_dimmed_alpha, 1, 1)
+ private var stateAnimator: ValueAnimator? = null
+ private val baseLayer: GradientDrawable
val icon: ImageView = layout.requireViewById(R.id.icon)
val status: TextView = layout.requireViewById(R.id.status)
val title: TextView = layout.requireViewById(R.id.title)
@@ -72,13 +87,22 @@ class ControlViewHolder(
var cancelUpdate: Runnable? = null
var behavior: Behavior? = null
var lastAction: ControlAction? = null
+ private var lastChallengeDialog: Dialog? = null
+
val deviceType: Int
get() = cws.control?.let { it.getDeviceType() } ?: cws.ci.deviceType
+ var dimmed: Boolean = false
+ set(value) {
+ field = value
+ bindData(cws)
+ }
init {
val ld = layout.getBackground() as LayerDrawable
ld.mutate()
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer) as ClipDrawable
+ clipLayer.alpha = ALPHA_DISABLED
+ baseLayer = ld.findDrawableByLayerId(R.id.background) as GradientDrawable
// needed for marquee to start
status.setSelected(true)
}
@@ -101,7 +125,7 @@ class ControlViewHolder(
cws.control?.let {
layout.setClickable(true)
layout.setOnLongClickListener(View.OnLongClickListener() {
- ControlActionCoordinator.longPress(this@ControlViewHolder)
+ controlActionCoordinator.longPress(this@ControlViewHolder)
true
})
}
@@ -123,7 +147,37 @@ class ControlViewHolder(
}
fun actionResponse(@ControlAction.ResponseResult response: Int) {
- // TODO: b/150931809 - handle response codes
+ // OK responses signal normal behavior, and the app will provide control updates
+ val failedAttempt = lastChallengeDialog != null
+ when (response) {
+ ControlAction.RESPONSE_OK ->
+ lastChallengeDialog = null
+ ControlAction.RESPONSE_UNKNOWN -> {
+ lastChallengeDialog = null
+ setTransientStatus(context.resources.getString(R.string.controls_error_failed))
+ }
+ ControlAction.RESPONSE_FAIL -> {
+ lastChallengeDialog = null
+ setTransientStatus(context.resources.getString(R.string.controls_error_failed))
+ }
+ ControlAction.RESPONSE_CHALLENGE_PIN -> {
+ lastChallengeDialog = ChallengeDialogs.createPinDialog(this, false, failedAttempt)
+ lastChallengeDialog?.show()
+ }
+ ControlAction.RESPONSE_CHALLENGE_PASSPHRASE -> {
+ lastChallengeDialog = ChallengeDialogs.createPinDialog(this, true, failedAttempt)
+ lastChallengeDialog?.show()
+ }
+ ControlAction.RESPONSE_CHALLENGE_ACK -> {
+ lastChallengeDialog = ChallengeDialogs.createConfirmationDialog(this)
+ lastChallengeDialog?.show()
+ }
+ }
+ }
+
+ fun dismiss() {
+ lastChallengeDialog?.dismiss()
+ lastChallengeDialog = null
}
fun setTransientStatus(tempStatus: String) {
@@ -141,8 +195,7 @@ class ControlViewHolder(
controlsController.action(cws.componentName, cws.ci, action)
}
- fun usePanel(): Boolean =
- usePanels && deviceType in ControlViewHolder.FORCE_PANEL_DEVICES
+ fun usePanel(): Boolean = deviceType in ControlViewHolder.FORCE_PANEL_DEVICES
private fun findBehavior(
status: Int,
@@ -150,7 +203,9 @@ class ControlViewHolder(
deviceType: Int
): KClass<out Behavior> {
return when {
- status == Control.STATUS_UNKNOWN -> UnknownBehavior::class
+ status == Control.STATUS_UNKNOWN -> StatusBehavior::class
+ status == Control.STATUS_ERROR -> StatusBehavior::class
+ status == Control.STATUS_NOT_FOUND -> StatusBehavior::class
deviceType == DeviceTypes.TYPE_CAMERA -> TouchBehavior::class
template is ToggleTemplate -> ToggleBehavior::class
template is StatelessTemplate -> TouchBehavior::class
@@ -160,30 +215,77 @@ class ControlViewHolder(
}
}
- internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0) {
+ internal fun applyRenderInfo(enabled: Boolean, offset: Int = 0, animated: Boolean = true) {
setEnabled(enabled)
val ri = RenderInfo.lookup(context, cws.componentName, deviceType, enabled, offset)
- val fg = context.getResources().getColorStateList(ri.foreground, context.getTheme())
- val (bg, alpha) = if (enabled) {
- Pair(ri.enabledBackground, ALPHA_ENABLED)
+ val fg = context.resources.getColorStateList(ri.foreground, context.theme)
+ val bg = context.resources.getColor(R.color.control_default_background, context.theme)
+ val dimAlpha = if (dimmed) dimmedAlpha else 1f
+ var (newClipColor, newAlpha) = if (enabled) {
+ // allow color overrides for the enabled state only
+ val color = cws.control?.getCustomColor()?.let {
+ val state = intArrayOf(android.R.attr.state_enabled)
+ it.getColorForState(state, it.getDefaultColor())
+ } ?: context.resources.getColor(ri.enabledBackground, context.theme)
+ listOf(color, ALPHA_ENABLED)
} else {
- Pair(R.color.control_default_background, ALPHA_DISABLED)
+ listOf(
+ context.resources.getColor(R.color.control_default_background, context.theme),
+ ALPHA_DISABLED
+ )
}
status.setTextColor(fg)
- icon.setImageDrawable(ri.icon)
+ cws.control?.getCustomIcon()?.let {
+ // do not tint custom icons, assume the intended icon color is correct
+ icon.imageTintList = null
+ icon.setImageIcon(it)
+ } ?: run {
+ icon.setImageDrawable(ri.icon)
- // do not color app icons
- if (deviceType != DeviceTypes.TYPE_ROUTINE) {
- icon.setImageTintList(fg)
+ // do not color app icons
+ if (deviceType != DeviceTypes.TYPE_ROUTINE) {
+ icon.imageTintList = fg
+ }
}
(clipLayer.getDrawable() as GradientDrawable).apply {
- setColor(context.getResources().getColor(bg, context.getTheme()))
- setAlpha(alpha)
+ val newBaseColor = if (behavior is ToggleRangeBehavior) {
+ ColorUtils.blendARGB(bg, newClipColor, toggleBackgroundIntensity)
+ } else {
+ bg
+ }
+ stateAnimator?.cancel()
+ if (animated) {
+ val oldColor = color?.defaultColor ?: newClipColor
+ val oldBaseColor = baseLayer.color?.defaultColor ?: newBaseColor
+ val oldAlpha = layout.alpha
+ stateAnimator = ValueAnimator.ofInt(clipLayer.alpha, newAlpha).apply {
+ addUpdateListener {
+ alpha = it.animatedValue as Int
+ setColor(ColorUtils.blendARGB(oldColor, newClipColor, it.animatedFraction))
+ baseLayer.setColor(ColorUtils.blendARGB(oldBaseColor,
+ newBaseColor, it.animatedFraction))
+ layout.alpha = MathUtils.lerp(oldAlpha, dimAlpha, it.animatedFraction)
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ stateAnimator = null
+ }
+ })
+ duration = STATE_ANIMATION_DURATION
+ interpolator = Interpolators.CONTROL_STATE
+ start()
+ }
+ } else {
+ alpha = newAlpha
+ setColor(newClipColor)
+ baseLayer.setColor(newBaseColor)
+ layout.alpha = dimAlpha
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 0f105376847f..aed7cd316bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -26,9 +26,10 @@ interface ControlsUiController {
companion object {
public const val TAG = "ControlsUiController"
+ public const val EXTRA_ANIMATE = "extra_animate"
}
- fun show(parent: ViewGroup)
+ fun show(parent: ViewGroup, dismissGlobalActions: Runnable)
fun hide()
fun onRefreshState(componentName: ComponentName, controls: List<Control>)
fun onActionResponse(
@@ -36,4 +37,5 @@ interface ControlsUiController {
controlId: String,
@ControlAction.ResponseResult response: Int
)
+ fun onFocusChanged(controlWithState: ControlWithState?)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index fab6fc7357dd..7108966072b9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -20,7 +20,6 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.app.AlertDialog
-import android.app.Dialog
import android.content.ComponentName
import android.content.Context
import android.content.DialogInterface
@@ -30,9 +29,7 @@ import android.content.res.Configuration
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Process
-import android.provider.Settings
import android.service.controls.Control
-import android.service.controls.actions.ControlAction
import android.util.Log
import android.util.TypedValue
import android.view.ContextThemeWrapper
@@ -61,6 +58,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.text.Collator
@@ -77,15 +76,17 @@ class ControlsUiControllerImpl @Inject constructor (
@Main val uiExecutor: DelayableExecutor,
@Background val bgExecutor: DelayableExecutor,
val controlsListingController: Lazy<ControlsListingController>,
- @Main val sharedPreferences: SharedPreferences
+ @Main val sharedPreferences: SharedPreferences,
+ val controlActionCoordinator: ControlActionCoordinator,
+ private val activityStarter: ActivityStarter,
+ private val keyguardStateController: KeyguardStateController
) : ControlsUiController {
companion object {
private const val PREF_COMPONENT = "controls_component"
private const val PREF_STRUCTURE = "controls_structure"
- private const val USE_PANELS = "systemui.controls_use_panel"
- private const val FADE_IN_MILLIS = 225L
+ private const val FADE_IN_MILLIS = 200L
private val EMPTY_COMPONENT = ComponentName("", "")
private val EMPTY_STRUCTURE = StructureInfo(
@@ -102,8 +103,8 @@ class ControlsUiControllerImpl @Inject constructor (
private lateinit var parent: ViewGroup
private lateinit var lastItems: List<SelectionItem>
private var popup: ListPopupWindow? = null
- private var activeDialog: Dialog? = null
private var hidden = true
+ private lateinit var dismissGlobalActions: Runnable
override val available: Boolean
get() = controlsController.get().available
@@ -134,9 +135,10 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
- override fun show(parent: ViewGroup) {
+ override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) {
Log.d(ControlsUiController.TAG, "show()")
this.parent = parent
+ this.dismissGlobalActions = dismissGlobalActions
hidden = false
allStructures = controlsController.get().getFavorites()
@@ -164,12 +166,18 @@ class ControlsUiControllerImpl @Inject constructor (
private fun reload(parent: ViewGroup) {
if (hidden) return
+ controlsListingController.get().removeCallback(listingCallback)
+ controlsController.get().unsubscribe()
+
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
fadeAnim.setDuration(FADE_IN_MILLIS)
fadeAnim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- show(parent)
+ controlViewsById.clear()
+ controlsById.clear()
+
+ show(parent, dismissGlobalActions)
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
showAnim.setDuration(FADE_IN_MILLIS)
@@ -209,6 +217,20 @@ class ControlsUiControllerImpl @Inject constructor (
}
}
+ override fun onFocusChanged(focusedControl: ControlWithState?) {
+ controlViewsById.forEach { key: ControlKey, viewHolder: ControlViewHolder ->
+ val state = controlsById.get(key) ?: return@forEach
+ val shouldBeDimmed = focusedControl != null && state != focusedControl
+ if (viewHolder.dimmed == shouldBeDimmed) {
+ return@forEach
+ }
+
+ uiExecutor.execute {
+ viewHolder.dimmed = shouldBeDimmed
+ }
+ }
+ }
+
private fun startFavoritingActivity(context: Context, si: StructureInfo) {
startTargetedActivity(context, si, ControlsFavoritingActivity::class.java)
}
@@ -242,9 +264,18 @@ class ControlsUiControllerImpl @Inject constructor (
}
private fun startActivity(context: Context, intent: Intent) {
- val closeDialog = Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
- context.sendBroadcast(closeDialog)
- context.startActivity(intent)
+ // Force animations when transitioning from a dialog to an activity
+ intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true)
+ dismissGlobalActions.run()
+
+ if (!keyguardStateController.isUnlocked()) {
+ activityStarter.dismissKeyguardThenExecute({
+ context.startActivity(intent)
+ true
+ }, null, true)
+ } else {
+ context.startActivity(intent)
+ }
}
private fun showControlsView(items: List<SelectionItem>) {
@@ -424,28 +455,27 @@ class ControlsUiControllerImpl @Inject constructor (
val maxColumns = findMaxColumns()
- // use flag only temporarily for testing
- val usePanels = Settings.Secure.getInt(context.contentResolver, USE_PANELS, 0) == 1
-
val listView = parent.requireViewById(R.id.global_actions_controls_list) as ViewGroup
var lastRow: ViewGroup = createRow(inflater, listView)
selectedStructure.controls.forEach {
- if (lastRow.getChildCount() == maxColumns) {
- lastRow = createRow(inflater, listView)
- }
- val baseLayout = inflater.inflate(
- R.layout.controls_base_item, lastRow, false) as ViewGroup
- lastRow.addView(baseLayout)
- val cvh = ControlViewHolder(
- baseLayout,
- controlsController.get(),
- uiExecutor,
- bgExecutor,
- usePanels
- )
val key = ControlKey(selectedStructure.componentName, it.controlId)
- cvh.bindData(controlsById.getValue(key))
- controlViewsById.put(key, cvh)
+ controlsById.get(key)?.let {
+ if (lastRow.getChildCount() == maxColumns) {
+ lastRow = createRow(inflater, listView)
+ }
+ val baseLayout = inflater.inflate(
+ R.layout.controls_base_item, lastRow, false) as ViewGroup
+ lastRow.addView(baseLayout)
+ val cvh = ControlViewHolder(
+ baseLayout,
+ controlsController.get(),
+ uiExecutor,
+ bgExecutor,
+ controlActionCoordinator
+ )
+ cvh.bindData(it)
+ controlViewsById.put(key, cvh)
+ }
}
// add spacers if necessary to keep control size consistent
@@ -511,7 +541,6 @@ class ControlsUiControllerImpl @Inject constructor (
if (newSelection != selectedStructure) {
selectedStructure = newSelection
updatePreferences(selectedStructure)
- controlsListingController.get().removeCallback(listingCallback)
reload(parent)
}
}
@@ -519,22 +548,24 @@ class ControlsUiControllerImpl @Inject constructor (
override fun hide() {
Log.d(ControlsUiController.TAG, "hide()")
hidden = true
- popup?.dismiss()
- activeDialog?.dismiss()
- ControlActionCoordinator.closeDialog()
+ popup?.dismissImmediate()
+ controlViewsById.forEach {
+ it.value.dismiss()
+ }
+ controlActionCoordinator.closeDialogs()
controlsController.get().unsubscribe()
parent.removeAllViews()
controlsById.clear()
controlViewsById.clear()
+
controlsListingController.get().removeCallback(listingCallback)
RenderInfo.clearCache()
}
override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
- Log.d(ControlsUiController.TAG, "onRefreshState()")
controls.forEach { c ->
controlsById.get(ControlKey(componentName, c.getControlId()))?.let {
Log.d(ControlsUiController.TAG, "onRefreshState() for id: " + c.getControlId())
@@ -552,23 +583,7 @@ class ControlsUiControllerImpl @Inject constructor (
override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
val key = ControlKey(componentName, controlId)
uiExecutor.execute {
- controlViewsById.get(key)?.let { cvh ->
- when (response) {
- ControlAction.RESPONSE_CHALLENGE_PIN -> {
- activeDialog = ChallengeDialogs.createPinDialog(cvh, false)
- activeDialog?.show()
- }
- ControlAction.RESPONSE_CHALLENGE_PASSPHRASE -> {
- activeDialog = ChallengeDialogs.createPinDialog(cvh, true)
- activeDialog?.show()
- }
- ControlAction.RESPONSE_CHALLENGE_ACK -> {
- activeDialog = ChallengeDialogs.createConfirmationDialog(cvh)
- activeDialog?.show()
- }
- else -> cvh.actionResponse(response)
- }
- }
+ controlViewsById.get(key)?.actionResponse(response)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index 15c41a2005a6..65ed9678c63e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -24,9 +24,9 @@ import android.provider.Settings
import android.view.View
import android.view.ViewGroup
import android.view.WindowInsets
+import android.view.WindowInsets.Type
import android.view.WindowManager
import android.widget.ImageView
-import android.widget.TextView
import com.android.systemui.R
@@ -45,7 +45,7 @@ class DetailDialog(
private const val PANEL_TOP_OFFSET = "systemui.controls_panel_top_offset"
}
- lateinit var activityView: ActivityView
+ var activityView = ActivityView(context, null, 0, false)
val stateCallback: ActivityView.StateCallback = object : ActivityView.StateCallback() {
override fun onActivityViewReady(view: ActivityView) {
@@ -67,10 +67,8 @@ class DetailDialog(
init {
window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-
setContentView(R.layout.controls_detail_dialog)
- activityView = ActivityView(context, null, 0, false)
requireViewById<ViewGroup>(R.id.controls_activity_view).apply {
addView(activityView)
}
@@ -79,14 +77,6 @@ class DetailDialog(
setOnClickListener { _: View -> dismiss() }
}
- requireViewById<TextView>(R.id.title).apply {
- setText(cvh.title.text)
- }
-
- requireViewById<TextView>(R.id.subtitle).apply {
- setText(cvh.subtitle.text)
- }
-
requireViewById<ImageView>(R.id.control_detail_open_in_app).apply {
setOnClickListener { v: View ->
dismiss()
@@ -97,15 +87,15 @@ class DetailDialog(
// consume all insets to achieve slide under effect
window.getDecorView().setOnApplyWindowInsetsListener {
- v: View, insets: WindowInsets ->
+ _: View, insets: WindowInsets ->
activityView.apply {
val l = getPaddingLeft()
val t = getPaddingTop()
val r = getPaddingRight()
- setPadding(l, t, r, insets.getSystemWindowInsets().bottom)
+ setPadding(l, t, r, insets.getInsets(Type.systemBars()).bottom)
}
- insets.consumeSystemWindowInsets()
+ WindowInsets.CONSUMED
}
requireViewById<ViewGroup>(R.id.control_detail_root).apply {
@@ -118,6 +108,9 @@ class DetailDialog(
val lp = getLayoutParams() as ViewGroup.MarginLayoutParams
lp.topMargin = offsetInPx
setLayoutParams(lp)
+
+ setOnClickListener { dismiss() }
+ (getParent() as View).setOnClickListener { dismiss() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index c3572491f9f1..49c44088ce3d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/UnknownBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -16,7 +16,11 @@
package com.android.systemui.controls.ui
-class UnknownBehavior : Behavior {
+import android.service.controls.Control
+
+import com.android.systemui.R
+
+class StatusBehavior : Behavior {
lateinit var cvh: ControlViewHolder
override fun initialize(cvh: ControlViewHolder) {
@@ -24,7 +28,13 @@ class UnknownBehavior : Behavior {
}
override fun bind(cws: ControlWithState) {
- cvh.status.setText(cvh.context.getString(com.android.internal.R.string.loading))
+ val status = cws.control?.status ?: Control.STATUS_UNKNOWN
+ val msg = when (status) {
+ Control.STATUS_ERROR -> R.string.controls_error_generic
+ Control.STATUS_NOT_FOUND -> R.string.controls_error_removed
+ else -> com.android.internal.R.string.loading
+ }
+ cvh.status.setText(cvh.context.getString(msg))
cvh.applyRenderInfo(false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
index 6340db1d756d..b4d0e6349605 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt
@@ -22,8 +22,8 @@ import android.service.controls.Control
import android.service.controls.templates.TemperatureControlTemplate
import com.android.systemui.R
-import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
-import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
class TemperatureControlBehavior : Behavior {
lateinit var clipLayer: Drawable
@@ -35,7 +35,7 @@ class TemperatureControlBehavior : Behavior {
this.cvh = cvh
cvh.layout.setOnClickListener { _ ->
- ControlActionCoordinator.touch(cvh, template.getTemplateId(), control)
+ cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
index a3368ef77a56..3e1669898a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt
@@ -18,13 +18,11 @@ package com.android.systemui.controls.ui
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
-import android.view.View
import android.service.controls.Control
import android.service.controls.templates.ToggleTemplate
-
+import android.view.View
import com.android.systemui.R
-import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
-import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
class ToggleBehavior : Behavior {
lateinit var clipLayer: Drawable
@@ -34,10 +32,10 @@ class ToggleBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- cvh.applyRenderInfo(false)
+ cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() {
- ControlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
+ cvh.controlActionCoordinator.toggle(cvh, template.getTemplateId(), template.isChecked())
})
}
@@ -49,9 +47,9 @@ class ToggleBehavior : Behavior {
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
+ clipLayer.level = MAX_LEVEL
val checked = template.isChecked()
- clipLayer.setLevel(if (checked) MAX_LEVEL else MIN_LEVEL)
cvh.applyRenderInfo(checked)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 5a956653285c..bfc06450b360 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -16,11 +16,20 @@
package com.android.systemui.controls.ui
-import android.os.Bundle
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
+import android.os.Bundle
+import android.service.controls.Control
+import android.service.controls.actions.FloatAction
+import android.service.controls.templates.RangeTemplate
+import android.service.controls.templates.ToggleRangeTemplate
import android.util.Log
+import android.util.MathUtils
+import android.util.TypedValue
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
@@ -29,19 +38,14 @@ import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.TextView
-import android.service.controls.Control
-import android.service.controls.actions.FloatAction
-import android.service.controls.templates.RangeTemplate
-import android.service.controls.templates.ToggleRangeTemplate
-import android.util.TypedValue
-
+import com.android.systemui.Interpolators
import com.android.systemui.R
-import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
-import com.android.systemui.controls.ui.ControlActionCoordinator.MAX_LEVEL
-
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MAX_LEVEL
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
import java.util.IllegalFormatException
class ToggleRangeBehavior : Behavior {
+ private var rangeAnimator: ValueAnimator? = null
lateinit var clipLayer: Drawable
lateinit var template: ToggleRangeTemplate
lateinit var control: Control
@@ -61,20 +65,21 @@ class ToggleRangeBehavior : Behavior {
status = cvh.status
context = status.getContext()
- cvh.applyRenderInfo(false)
+ cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
val gestureListener = ToggleRangeGestureListener(cvh.layout)
val gestureDetector = GestureDetector(context, gestureListener)
cvh.layout.setOnTouchListener { v: View, e: MotionEvent ->
if (gestureDetector.onTouchEvent(e)) {
- return@setOnTouchListener true
+ // Don't return true to let the state list change to "pressed"
+ return@setOnTouchListener false
}
if (e.getAction() == MotionEvent.ACTION_UP && gestureListener.isDragging) {
v.getParent().requestDisallowInterceptTouchEvent(false)
gestureListener.isDragging = false
endUpdateRange()
- return@setOnTouchListener true
+ return@setOnTouchListener false
}
return@setOnTouchListener false
@@ -87,17 +92,18 @@ class ToggleRangeBehavior : Behavior {
currentStatusText = control.getStatusText()
status.setText(currentStatusText)
+ // ControlViewHolder sets a long click listener, but we want to handle touch in
+ // here instead, otherwise we'll have state conflicts.
+ cvh.layout.setOnLongClickListener(null)
+
val ld = cvh.layout.getBackground() as LayerDrawable
clipLayer = ld.findDrawableByLayerId(R.id.clip_layer)
- clipLayer.setLevel(MIN_LEVEL)
template = control.getControlTemplate() as ToggleRangeTemplate
rangeTemplate = template.getRange()
val checked = template.isChecked()
- val currentRatio = rangeTemplate.getCurrentValue() /
- (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())
- updateRange(currentRatio, checked, /* isDragging */ false)
+ updateRange(rangeToLevelValue(rangeTemplate.currentValue), checked, /* isDragging */ false)
cvh.applyRenderInfo(checked)
@@ -135,7 +141,7 @@ class ToggleRangeBehavior : Behavior {
): Boolean {
val handled = when (action) {
AccessibilityNodeInfo.ACTION_CLICK -> {
- ControlActionCoordinator.toggle(cvh, template.getTemplateId(),
+ cvh.controlActionCoordinator.toggle(cvh, template.getTemplateId(),
template.isChecked())
true
}
@@ -146,9 +152,8 @@ class ToggleRangeBehavior : Behavior {
} else {
val value = arguments.getFloat(
AccessibilityNodeInfo.ACTION_ARGUMENT_PROGRESS_VALUE)
- val ratioDiff = (value - rangeTemplate.getCurrentValue()) /
- (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue())
- updateRange(ratioDiff, template.isChecked(), /* isDragging */ false)
+ val level = rangeToLevelValue(value - rangeTemplate.getCurrentValue())
+ updateRange(level, template.isChecked(), /* isDragging */ false)
endUpdateRange()
true
}
@@ -170,15 +175,37 @@ class ToggleRangeBehavior : Behavior {
fun beginUpdateRange() {
status.setTextSize(TypedValue.COMPLEX_UNIT_PX, context.getResources()
.getDimensionPixelSize(R.dimen.control_status_expanded).toFloat())
+ cvh.controlActionCoordinator.setFocusedElement(cvh)
}
- fun updateRange(ratioDiff: Float, checked: Boolean, isDragging: Boolean) {
- val changeAmount = if (checked) (MAX_LEVEL * ratioDiff).toInt() else MIN_LEVEL
- val newLevel = Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, clipLayer.getLevel() + changeAmount))
- clipLayer.setLevel(newLevel)
+ fun updateRange(level: Int, checked: Boolean, isDragging: Boolean) {
+ val newLevel = if (checked) Math.max(MIN_LEVEL, Math.min(MAX_LEVEL, level)) else MIN_LEVEL
+
+ if (newLevel == clipLayer.level) return
+
+ rangeAnimator?.cancel()
+ if (isDragging) {
+ clipLayer.level = newLevel
+ val isEdge = newLevel == MIN_LEVEL || newLevel == MAX_LEVEL
+ cvh.controlActionCoordinator.drag(isEdge)
+ } else {
+ rangeAnimator = ValueAnimator.ofInt(cvh.clipLayer.level, newLevel).apply {
+ addUpdateListener {
+ cvh.clipLayer.level = it.animatedValue as Int
+ }
+ addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ rangeAnimator = null
+ }
+ })
+ duration = ControlViewHolder.STATE_ANIMATION_DURATION
+ interpolator = Interpolators.CONTROL_STATE
+ start()
+ }
+ }
if (checked) {
- val newValue = levelToRangeValue(clipLayer.getLevel())
+ val newValue = levelToRangeValue(newLevel)
currentRangeValue = format(rangeTemplate.getFormatString().toString(),
DEFAULT_FORMAT, newValue)
val text = if (isDragging) {
@@ -206,9 +233,13 @@ class ToggleRangeBehavior : Behavior {
}
private fun levelToRangeValue(i: Int): Float {
- val ratio = i.toFloat() / MAX_LEVEL
- return rangeTemplate.getMinValue() +
- (ratio * (rangeTemplate.getMaxValue() - rangeTemplate.getMinValue()))
+ return MathUtils.constrainedMap(rangeTemplate.minValue, rangeTemplate.maxValue,
+ MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(), i.toFloat())
+ }
+
+ private fun rangeToLevelValue(i: Float): Int {
+ return MathUtils.constrainedMap(MIN_LEVEL.toFloat(), MAX_LEVEL.toFloat(),
+ rangeTemplate.minValue, rangeTemplate.maxValue, i).toInt()
}
fun endUpdateRange() {
@@ -217,6 +248,7 @@ class ToggleRangeBehavior : Behavior {
status.setText("$currentStatusText $currentRangeValue")
cvh.action(FloatAction(rangeTemplate.getTemplateId(),
findNearestStep(levelToRangeValue(clipLayer.getLevel()))))
+ cvh.controlActionCoordinator.setFocusedElement(null)
}
fun findNearestStep(value: Float): Float {
@@ -247,7 +279,10 @@ class ToggleRangeBehavior : Behavior {
}
override fun onLongPress(e: MotionEvent) {
- ControlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh)
+ if (isDragging) {
+ return
+ }
+ cvh.controlActionCoordinator.longPress(this@ToggleRangeBehavior.cvh)
}
override fun onScroll(
@@ -256,20 +291,25 @@ class ToggleRangeBehavior : Behavior {
xDiff: Float,
yDiff: Float
): Boolean {
+ if (!template.isChecked) {
+ return false
+ }
if (!isDragging) {
v.getParent().requestDisallowInterceptTouchEvent(true)
this@ToggleRangeBehavior.beginUpdateRange()
isDragging = true
}
- this@ToggleRangeBehavior.updateRange(-xDiff / v.getWidth(),
- /* checked */ true, /* isDragging */ true)
+ val ratioDiff = -xDiff / v.width
+ val changeAmount = ((MAX_LEVEL - MIN_LEVEL) * ratioDiff).toInt()
+ this@ToggleRangeBehavior.updateRange(clipLayer.level + changeAmount,
+ checked = true, isDragging = true)
return true
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
val th = this@ToggleRangeBehavior
- ControlActionCoordinator.toggle(th.cvh, th.template.getTemplateId(),
+ cvh.controlActionCoordinator.toggle(th.cvh, th.template.getTemplateId(),
th.template.isChecked())
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
index b02c9c8972fc..7ae3df751419 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt
@@ -23,7 +23,7 @@ import android.service.controls.Control
import android.service.controls.templates.ControlTemplate
import com.android.systemui.R
-import com.android.systemui.controls.ui.ControlActionCoordinator.MIN_LEVEL
+import com.android.systemui.controls.ui.ControlViewHolder.Companion.MIN_LEVEL
/**
* Supports touch events, but has no notion of state as the {@link ToggleBehavior} does. Must be
@@ -37,10 +37,10 @@ class TouchBehavior : Behavior {
override fun initialize(cvh: ControlViewHolder) {
this.cvh = cvh
- cvh.applyRenderInfo(false)
+ cvh.applyRenderInfo(false /* enabled */, 0 /* offset */, false /* animated */)
cvh.layout.setOnClickListener(View.OnClickListener() {
- ControlActionCoordinator.touch(cvh, template.getTemplateId(), control)
+ cvh.controlActionCoordinator.touch(cvh, template.getTemplateId(), control)
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
new file mode 100644
index 000000000000..a97113cc598b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/Vibrations.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.os.VibrationEffect
+import android.os.VibrationEffect.Composition.PRIMITIVE_TICK
+
+object Vibrations {
+ private const val TOGGLE_TICK_COUNT = 12
+
+ val toggleOnEffect = initToggleOnEffect()
+ val toggleOffEffect = initToggleOffEffect()
+ val rangeEdgeEffect = initRangeEdgeEffect()
+ val rangeMiddleEffect = initRangeMiddleEffect()
+
+ private fun initToggleOnEffect(): VibrationEffect {
+ val composition = VibrationEffect.startComposition()
+ var i = 0
+ while (i++ < TOGGLE_TICK_COUNT) {
+ composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
+ }
+ composition.addPrimitive(PRIMITIVE_TICK, 0.5f, 100)
+ return composition.compose()
+ }
+
+ private fun initToggleOffEffect(): VibrationEffect {
+ val composition = VibrationEffect.startComposition()
+ composition.addPrimitive(PRIMITIVE_TICK, 0.5f, 0)
+ composition.addPrimitive(PRIMITIVE_TICK, 0.05f, 100)
+ var i = 0
+ while (i++ < TOGGLE_TICK_COUNT) {
+ composition?.addPrimitive(PRIMITIVE_TICK, 0.05f, 0)
+ }
+ return composition.compose()
+ }
+
+ private fun initRangeEdgeEffect(): VibrationEffect {
+ val composition = VibrationEffect.startComposition()
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
+ return composition.compose()
+ }
+
+ private fun initRangeMiddleEffect(): VibrationEffect {
+ val composition = VibrationEffect.startComposition()
+ composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.1f)
+ return composition.compose()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 3a4b273e1c98..23bcb29923d8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -33,6 +33,8 @@ import android.view.LayoutInflater;
import android.view.WindowManager;
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;
@@ -218,4 +220,11 @@ public class DependencyProvider {
public Choreographer providesChoreographer() {
return Choreographer.getInstance();
}
+
+ /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
+ @Singleton
+ @Provides
+ static UiEventLogger provideUiEventLogger() {
+ return new UiEventLoggerImpl();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index a7c40435b3ca..2b27436c85dd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -19,8 +19,10 @@ package com.android.systemui.dagger;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.IActivityManager;
+import android.app.IActivityTaskManager;
import android.app.IWallpaperManager;
import android.app.KeyguardManager;
import android.app.NotificationManager;
@@ -35,6 +37,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
+import android.hardware.display.DisplayManager;
import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
@@ -111,6 +114,12 @@ public class SystemServicesModule {
}
@Provides
+ @Singleton
+ static DevicePolicyManager provideDevicePolicyManager(Context context) {
+ return context.getSystemService(DevicePolicyManager.class);
+ }
+
+ @Provides
@DisplayId
static int provideDisplayId(Context context) {
return context.getDisplayId();
@@ -118,8 +127,8 @@ public class SystemServicesModule {
@Provides
@Singleton
- static DevicePolicyManager provideDevicePolicyManager(Context context) {
- return context.getSystemService(DevicePolicyManager.class);
+ static DisplayManager provideDisplayManager(Context context) {
+ return context.getSystemService(DisplayManager.class);
}
@Singleton
@@ -128,6 +137,12 @@ public class SystemServicesModule {
return ActivityManager.getService();
}
+ @Singleton
+ @Provides
+ static IActivityTaskManager provideIActivityTaskManager() {
+ return ActivityTaskManager.getService();
+ }
+
@Provides
@Singleton
static IBatteryStats provideIBatteryStats() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2e9ce1200c85..90cd13fd1330 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -29,6 +29,7 @@ import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.dagger.SettingsModule;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
@@ -61,6 +62,7 @@ import dagger.Provides;
ConcurrencyModule.class,
LogModule.class,
PeopleHubModule.class,
+ SettingsModule.class
},
subcomponents = {StatusBarComponent.class,
NotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index 3f88f252bfe7..554457b3564a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -75,6 +75,12 @@ public class DozeDockHandler implements DozeMachine.Part {
public void onEvent(int dockState) {
if (DEBUG) Log.d(TAG, "dock event = " + dockState);
+ // Only act upon state changes, otherwise we might overwrite other transitions,
+ // like proximity sensor initialization.
+ if (mDockState == dockState) {
+ return;
+ }
+
mDockState = dockState;
if (isPulsing()) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 18bfd899a4e7..490890f263aa 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -159,6 +159,15 @@ public class DozeMachine {
mDozeHost = dozeHost;
}
+ /**
+ * Clean ourselves up.
+ */
+ public void destroy() {
+ for (Part part : mParts) {
+ part.destroy();
+ }
+ }
+
/** Initializes the set of {@link Part}s. Must be called exactly once after construction. */
public void setParts(Part[] parts) {
Preconditions.checkState(mParts == null);
@@ -411,6 +420,9 @@ public class DozeMachine {
/** Dump current state. For debugging only. */
default void dump(PrintWriter pw) {}
+
+ /** Give the Part a chance to clean itself up. */
+ default void destroy() {}
}
/** A wrapper interface for {@link android.service.dreams.DreamService} */
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 700a8611c8bd..10776c91df84 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -164,6 +164,17 @@ public class DozeSensors {
}
/**
+ * Unregister any sensors.
+ */
+ public void destroy() {
+ // Unregisters everything, which is enough to allow gc.
+ for (TriggerSensor triggerSensor : mSensors) {
+ triggerSensor.setListening(false);
+ }
+ mProximitySensor.pause();
+ }
+
+ /**
* Temporarily disable some sensors to avoid turning on the device while the user is
* turning it off.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 7cbbdd783e74..529b016aaca6 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -65,6 +65,7 @@ public class DozeService extends DreamService
mPluginManager.removePluginListener(this);
}
super.onDestroy();
+ mDozeMachine.destroy();
mDozeMachine = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index b3299916356c..3510e07d2cea 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -34,6 +34,9 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+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.MetricsEvent;
import com.android.systemui.Dependency;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -45,6 +48,7 @@ import com.android.systemui.util.sensors.ProximitySensor;
import com.android.systemui.util.wakelock.WakeLock;
import java.io.PrintWriter;
+import java.util.Optional;
import java.util.function.Consumer;
/**
@@ -58,6 +62,8 @@ public class DozeTriggers implements DozeMachine.Part {
/** adb shell am broadcast -a com.android.systemui.doze.pulse com.android.systemui */
private static final String PULSE_ACTION = "com.android.systemui.doze.pulse";
+ private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
+
/**
* Last value sent by the wake-display sensor.
* Assuming that the screen should start on.
@@ -88,6 +94,62 @@ public class DozeTriggers implements DozeMachine.Part {
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ @VisibleForTesting
+ public enum DozingUpdateUiEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Dozing updated due to notification.")
+ DOZING_UPDATE_NOTIFICATION(433),
+
+ @UiEvent(doc = "Dozing updated due to sigmotion.")
+ DOZING_UPDATE_SIGMOTION(434),
+
+ @UiEvent(doc = "Dozing updated because sensor was picked up.")
+ DOZING_UPDATE_SENSOR_PICKUP(435),
+
+ @UiEvent(doc = "Dozing updated because sensor was double tapped.")
+ DOZING_UPDATE_SENSOR_DOUBLE_TAP(436),
+
+ @UiEvent(doc = "Dozing updated because sensor was long squeezed.")
+ DOZING_UPDATE_SENSOR_LONG_SQUEEZE(437),
+
+ @UiEvent(doc = "Dozing updated due to docking.")
+ DOZING_UPDATE_DOCKING(438),
+
+ @UiEvent(doc = "Dozing updated because sensor woke up.")
+ DOZING_UPDATE_SENSOR_WAKEUP(439),
+
+ @UiEvent(doc = "Dozing updated because sensor woke up the lockscreen.")
+ DOZING_UPDATE_SENSOR_WAKE_LOCKSCREEN(440),
+
+ @UiEvent(doc = "Dozing updated because sensor was tapped.")
+ DOZING_UPDATE_SENSOR_TAP(441);
+
+ private final int mId;
+
+ DozingUpdateUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ static DozingUpdateUiEvent fromReason(int reason) {
+ switch (reason) {
+ case 1: return DOZING_UPDATE_NOTIFICATION;
+ case 2: return DOZING_UPDATE_SIGMOTION;
+ case 3: return DOZING_UPDATE_SENSOR_PICKUP;
+ case 4: return DOZING_UPDATE_SENSOR_DOUBLE_TAP;
+ case 5: return DOZING_UPDATE_SENSOR_LONG_SQUEEZE;
+ case 6: return DOZING_UPDATE_DOCKING;
+ case 7: return DOZING_UPDATE_SENSOR_WAKEUP;
+ case 8: return DOZING_UPDATE_SENSOR_WAKE_LOCKSCREEN;
+ case 9: return DOZING_UPDATE_SENSOR_TAP;
+ default: return null;
+ }
+ }
+ }
+
public DozeTriggers(Context context, DozeMachine machine, DozeHost dozeHost,
AlarmManager alarmManager, AmbientDisplayConfiguration config,
DozeParameters dozeParameters, AsyncSensorManager sensorManager, Handler handler,
@@ -111,6 +173,11 @@ public class DozeTriggers implements DozeMachine.Part {
mBroadcastDispatcher = broadcastDispatcher;
}
+ @Override
+ public void destroy() {
+ mDozeSensors.destroy();
+ }
+
private void onNotification(Runnable onPulseSuppressedListener) {
if (DozeMachine.DEBUG) {
Log.d(TAG, "requestNotificationPulse");
@@ -219,6 +286,8 @@ public class DozeTriggers implements DozeMachine.Part {
mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING)
.setType(MetricsEvent.TYPE_UPDATE)
.setSubtype(reason));
+ Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
+ .ifPresent(UI_EVENT_LOGGER::log);
if (mDozeParameters.getDisplayNeedsBlanking()) {
// Let's prepare the display to wake-up by drawing black.
// This will cover the hardware wake-up sequence, where the display
@@ -396,6 +465,8 @@ public class DozeTriggers implements DozeMachine.Part {
// Logs request pulse reason on AOD screen.
mMetricsLogger.write(new LogMaker(MetricsEvent.DOZING)
.setType(MetricsEvent.TYPE_UPDATE).setSubtype(reason));
+ Optional.ofNullable(DozingUpdateUiEvent.fromReason(reason))
+ .ifPresent(UI_EVENT_LOGGER::log);
}
private boolean canPulse() {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index e949007a158b..b29c5b07c765 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -24,6 +24,7 @@ import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CommandQueue.Callbacks;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
@@ -43,15 +44,18 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa
private GlobalActions mPlugin;
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
+ private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Inject
public GlobalActionsComponent(Context context, CommandQueue commandQueue,
ExtensionController extensionController,
- Provider<GlobalActions> globalActionsProvider) {
+ Provider<GlobalActions> globalActionsProvider,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
super(context);
mCommandQueue = commandQueue;
mExtensionController = extensionController;
mGlobalActionsProvider = globalActionsProvider;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
}
@Override
@@ -81,6 +85,7 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa
@Override
public void handleShowGlobalActionsMenu() {
+ mStatusBarKeyguardViewManager.setGlobalActionsVisible(true);
mExtension.get().showGlobalActions(this);
}
@@ -95,6 +100,7 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa
@Override
public void onGlobalActionsHidden() {
try {
+ mStatusBarKeyguardViewManager.setGlobalActionsVisible(false);
mBarService.onGlobalActionsHidden();
} catch (RemoteException e) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 4db5374cd566..8123158408dd 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -16,6 +16,8 @@ package com.android.systemui.globalactions;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
@@ -43,7 +45,6 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Color;
-import android.graphics.Insets;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.net.ConnectivityManager;
@@ -75,9 +76,13 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+import android.widget.ListPopupWindow;
+import android.widget.ListView;
import android.widget.TextView;
import androidx.annotation.NonNull;
@@ -107,6 +112,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.management.ControlsAnimations;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.dagger.qualifiers.Background;
@@ -123,7 +129,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.EmergencyDialerConstants;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils;
-import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
@@ -151,19 +156,20 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
/* Valid settings for global actions keys.
* see config.xml config_globalActionList */
- private static final String GLOBAL_ACTION_KEY_POWER = "power";
- private static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
- private static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
- private static final String GLOBAL_ACTION_KEY_SILENT = "silent";
- private static final String GLOBAL_ACTION_KEY_USERS = "users";
- private static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
- private static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
- private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
- private static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
- private static final String GLOBAL_ACTION_KEY_RESTART = "restart";
- private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
- private static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
- private static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
+ @VisibleForTesting
+ protected static final String GLOBAL_ACTION_KEY_POWER = "power";
+ protected static final String GLOBAL_ACTION_KEY_AIRPLANE = "airplane";
+ protected static final String GLOBAL_ACTION_KEY_BUGREPORT = "bugreport";
+ protected static final String GLOBAL_ACTION_KEY_SILENT = "silent";
+ protected static final String GLOBAL_ACTION_KEY_USERS = "users";
+ protected static final String GLOBAL_ACTION_KEY_SETTINGS = "settings";
+ protected static final String GLOBAL_ACTION_KEY_LOCKDOWN = "lockdown";
+ protected static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist";
+ protected static final String GLOBAL_ACTION_KEY_ASSIST = "assist";
+ protected static final String GLOBAL_ACTION_KEY_RESTART = "restart";
+ protected static final String GLOBAL_ACTION_KEY_LOGOUT = "logout";
+ protected static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency";
+ protected static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot";
private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "ControlsSeedingCompleted";
private static final String PREFS_CONTROLS_FILE = "controls_prefs";
@@ -191,13 +197,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
// Used for RingerModeTracker
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
- private ArrayList<Action> mItems;
+ @VisibleForTesting
+ protected final ArrayList<Action> mItems = new ArrayList<>();
+ @VisibleForTesting
+ protected final ArrayList<Action> mOverflowItems = new ArrayList<>();
+
private ActionsDialog mDialog;
private Action mSilentModeAction;
private ToggleAction mAirplaneModeOn;
private MyAdapter mAdapter;
+ private MyOverflowAdapter mOverflowAdapter;
private boolean mKeyguardShowing = false;
private boolean mDeviceProvisioned = false;
@@ -223,6 +234,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private SharedPreferences mControlsPreferences;
private final RingerModeTracker mRingerModeTracker;
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
+ private Handler mMainHandler;
+ private boolean mShowLockScreenCardsAndControls = false;
@VisibleForTesting
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
@@ -277,7 +290,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
@Background Executor backgroundExecutor,
ControlsListingController controlsListingController,
ControlsController controlsController, UiEventLogger uiEventLogger,
- RingerModeTracker ringerModeTracker) {
+ RingerModeTracker ringerModeTracker, @Main Handler handler) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
mAudioManager = audioManager;
@@ -306,6 +319,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mBlurUtils = blurUtils;
mRingerModeTracker = ringerModeTracker;
mControlsController = controlsController;
+ mMainHandler = handler;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -341,10 +355,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
- if (mDialog != null && mDialog.mPanelController != null) {
+ if (mDialog != null) {
boolean unlocked = keyguardStateController.isUnlocked()
|| keyguardStateController.canDismissLockScreen();
- mDialog.mPanelController.onDeviceLockStateChanged(unlocked);
+ if (mDialog.mPanelController != null) {
+ mDialog.mPanelController.onDeviceLockStateChanged(unlocked);
+ }
+ if (!mDialog.isShowingControls() && shouldShowControls()) {
+ mDialog.showControls(mControlsUiController);
+ }
}
}
});
@@ -359,6 +378,17 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
Context.MODE_PRIVATE);
+ // Listen for changes to show controls on the power menu while locked
+ onPowerMenuLockScreenSettingsChanged();
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT),
+ false /* notifyForDescendants */,
+ new ContentObserver(mMainHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ onPowerMenuLockScreenSettingsChanged();
+ }
+ });
}
private void seedFavorites() {
@@ -387,6 +417,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
if (preferredComponent == null) {
Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
+ return;
}
mControlsController.seedFavoritesForComponent(
@@ -403,16 +434,19 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
*
* @param keyguardShowing True if keyguard is showing
*/
- public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
+ public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned,
GlobalActionsPanelPlugin panelPlugin) {
mKeyguardShowing = keyguardShowing;
mDeviceProvisioned = isDeviceProvisioned;
mPanelPlugin = panelPlugin;
- if (mDialog != null) {
+ if (mDialog != null && mDialog.isShowing()) {
+ // In order to force global actions to hide on the same affordance press, we must
+ // register a call to onGlobalActionsShown() first to prevent the default actions
+ // menu from showing. This will be followed by a subsequent call to
+ // onGlobalActionsHidden() on dismiss()
+ mWindowManagerFuncs.onGlobalActionsShown();
mDialog.dismiss();
mDialog = null;
- // Show delayed, so that the dismiss of the previous dialog completes
- mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow();
}
@@ -444,27 +478,59 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
prepareDialog();
seedFavorites();
- // If we only have 1 item and it's a simple press action, just do this action.
- if (mAdapter.getCount() == 1
- && mAdapter.getItem(0) instanceof SinglePressAction
- && !(mAdapter.getItem(0) instanceof LongPressAction)) {
- ((SinglePressAction) mAdapter.getItem(0)).onPress();
+ WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
+ attrs.setTitle("ActionsDialog");
+ attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mDialog.getWindow().setAttributes(attrs);
+ mDialog.show();
+ mWindowManagerFuncs.onGlobalActionsShown();
+ }
+
+ @VisibleForTesting
+ protected boolean shouldShowAction(Action action) {
+ if (mKeyguardShowing && !action.showDuringKeyguard()) {
+ return false;
+ }
+ if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the maximum number of power menu items to show based on which GlobalActions
+ * layout is being used.
+ */
+ @VisibleForTesting
+ protected int getMaxShownPowerItems() {
+ if (shouldUseControlsLayout()) {
+ return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
} else {
- WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
- attrs.setTitle("ActionsDialog");
- attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- mDialog.getWindow().setAttributes(attrs);
- mDialog.show();
- mWindowManagerFuncs.onGlobalActionsShown();
+ return Integer.MAX_VALUE;
}
}
/**
- * Create the global actions dialog.
- *
- * @return A new dialog.
+ * Add a power menu action item for to either the main or overflow items lists, depending on
+ * whether controls are enabled and whether the max number of shown items has been reached.
*/
- private ActionsDialog createDialog() {
+ private void addActionItem(Action action) {
+ if (shouldShowAction(action)) {
+ if (mItems.size() < getMaxShownPowerItems()) {
+ mItems.add(action);
+ } else {
+ mOverflowItems.add(action);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ protected String[] getDefaultActions() {
+ return mResources.getStringArray(R.array.config_globalActionsList);
+ }
+
+ @VisibleForTesting
+ protected void createActionItems() {
// Simple toggle style if there's no vibrator, otherwise use a tri-state
if (!mHasVibrator) {
mSilentModeAction = new SilentModeToggleAction();
@@ -474,8 +540,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mAirplaneModeOn = new AirplaneModeAction();
onAirplaneModeChanged();
- mItems = new ArrayList<Action>();
- String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList);
+ mItems.clear();
+ mOverflowItems.clear();
+ String[] defaultActions = getDefaultActions();
+
+ // make sure emergency affordance action is first, if needed
+ if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
+ addActionItem(new EmergencyAffordanceAction());
+ }
ArraySet<String> addedKeys = new ArraySet<String>();
for (int i = 0; i < defaultActions.length; i++) {
@@ -485,46 +557,46 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
continue;
}
if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
- mItems.add(new PowerAction());
+ addActionItem(new PowerAction());
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
- mItems.add(mAirplaneModeOn);
+ addActionItem(mAirplaneModeOn);
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
if (Settings.Global.getInt(mContentResolver,
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
- mItems.add(new BugReportAction());
+ addActionItem(new BugReportAction());
}
} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
if (mShowSilentToggle) {
- mItems.add(mSilentModeAction);
+ addActionItem(mSilentModeAction);
}
} else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
- addUsersToMenu(mItems);
+ addUsersToMenu();
}
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
- mItems.add(getSettingsAction());
+ addActionItem(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
if (Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
&& shouldDisplayLockdown()) {
- mItems.add(getLockdownAction());
+ addActionItem(getLockdownAction());
}
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
- mItems.add(getVoiceAssistAction());
+ addActionItem(getVoiceAssistAction());
} else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
- mItems.add(getAssistAction());
+ addActionItem(getAssistAction());
} else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) {
- mItems.add(new RestartAction());
+ addActionItem(new RestartAction());
} else if (GLOBAL_ACTION_KEY_SCREENSHOT.equals(actionKey)) {
- mItems.add(new ScreenshotAction());
+ addActionItem(new ScreenshotAction());
} else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) {
if (mDevicePolicyManager.isLogoutEnabled()
&& getCurrentUser().id != UserHandle.USER_SYSTEM) {
- mItems.add(new LogoutAction());
+ addActionItem(new LogoutAction());
}
} else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
- mItems.add(new EmergencyDialerAction());
+ addActionItem(new EmergencyDialerAction());
}
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
@@ -532,21 +604,31 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
// Add here so we don't add more than one.
addedKeys.add(actionKey);
}
+ }
- if (mEmergencyAffordanceManager.needsEmergencyAffordance()) {
- mItems.add(new EmergencyAffordanceAction());
- }
+ private void onRotate() {
+ // re-allocate actions between main and overflow lists
+ this.createActionItems();
+ }
- mAdapter = new MyAdapter();
+ /**
+ * Create the global actions dialog.
+ *
+ * @return A new dialog.
+ */
+ private ActionsDialog createDialog() {
+ createActionItems();
- mDepthController.setShowingHomeControls(shouldShowControls());
- ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, getWalletPanelViewController(),
- mDepthController, mSysuiColorExtractor, mStatusBarService,
- mNotificationShadeWindowController,
- shouldShowControls() ? mControlsUiController : null, mBlurUtils);
+ mAdapter = new MyAdapter();
+ mOverflowAdapter = new MyOverflowAdapter();
+
+ mDepthController.setShowingHomeControls(shouldUseControlsLayout());
+ ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
+ getWalletPanelViewController(), mDepthController, mSysuiColorExtractor,
+ mStatusBarService, mNotificationShadeWindowController,
+ shouldShowControls() ? mControlsUiController : null, mBlurUtils,
+ shouldUseControlsLayout(), this::onRotate, mKeyguardShowing);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
- dialog.setKeyguardShowing(mKeyguardShowing);
-
dialog.setOnDismissListener(this);
dialog.setOnShowListener(this);
@@ -644,7 +726,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
@Override
public boolean shouldBeSeparated() {
- return !shouldShowControls();
+ return !shouldUseControlsLayout();
}
@Override
@@ -652,7 +734,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
View v = super.create(context, convertView, parent, inflater);
int textColor;
- if (shouldShowControls()) {
+ if (shouldUseControlsLayout()) {
v.setBackgroundTintList(ColorStateList.valueOf(v.getResources().getColor(
com.android.systemui.R.color.global_actions_emergency_background)));
textColor = v.getResources().getColor(
@@ -769,7 +851,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
- mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
+ mScreenshotHelper.takeScreenshot(TAKE_SCREENSHOT_FULLSCREEN, true, true,
+ SCREENSHOT_GLOBAL_ACTIONS, mHandler, null);
mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
mUiEventLogger.log(GlobalActionsEvent.GA_SCREENSHOT_PRESS);
}
@@ -1020,7 +1103,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return currentUser == null || currentUser.isPrimary();
}
- private void addUsersToMenu(ArrayList<Action> items) {
+ private void addUsersToMenu() {
if (mUserManager.isUserSwitcherEnabled()) {
List<UserInfo> users = mUserManager.getUsers();
UserInfo currentUser = getCurrentUser();
@@ -1050,7 +1133,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return false;
}
};
- items.add(switchToUser);
+ addActionItem(switchToUser);
}
}
}
@@ -1092,18 +1175,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
private int getActionLayoutId() {
- if (shouldShowControls()) {
+ if (shouldUseControlsLayout()) {
return com.android.systemui.R.layout.global_actions_grid_item_v2;
}
return com.android.systemui.R.layout.global_actions_grid_item;
}
/**
- * The adapter used for the list within the global actions dialog, taking into account whether
- * the keyguard is showing via
- * {@link com.android.systemui.globalactions.GlobalActionsDialog#mKeyguardShowing}
- * and whether the device is provisioned via
- * {@link com.android.systemui.globalactions.GlobalActionsDialog#mDeviceProvisioned}.
+ * The adapter used for power menu items shown in the global actions dialog.
*/
public class MyAdapter extends MultiListAdapter {
private int countItems(boolean separated) {
@@ -1111,23 +1190,13 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
for (int i = 0; i < mItems.size(); i++) {
final Action action = mItems.get(i);
- if (shouldBeShown(action) && action.shouldBeSeparated() == separated) {
+ if (action.shouldBeSeparated() == separated) {
count++;
}
}
return count;
}
- private boolean shouldBeShown(Action action) {
- if (mKeyguardShowing && !action.showDuringKeyguard()) {
- return false;
- }
- if (!mDeviceProvisioned && !action.showBeforeProvisioning()) {
- return false;
- }
- return true;
- }
-
@Override
public int countSeparatedItems() {
return countItems(true);
@@ -1158,7 +1227,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
int filteredPos = 0;
for (int i = 0; i < mItems.size(); i++) {
final Action action = mItems.get(i);
- if (!shouldBeShown(action)) {
+ if (!shouldShowAction(action)) {
continue;
}
if (filteredPos == position) {
@@ -1223,6 +1292,79 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
+ /**
+ * The adapter used for items in the overflow menu.
+ */
+ public class MyOverflowAdapter extends BaseAdapter {
+ @Override
+ public int getCount() {
+ return mOverflowItems.size();
+ }
+
+ @Override
+ public Action getItem(int position) {
+ return mOverflowItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ Action action = getItem(position);
+ if (action == null) {
+ Log.w(TAG, "No overflow action found at position: " + position);
+ return null;
+ }
+ int viewLayoutResource = com.android.systemui.R.layout.controls_more_item;
+ View view = convertView != null ? convertView
+ : LayoutInflater.from(mContext).inflate(viewLayoutResource, parent, false);
+ TextView textView = (TextView) view;
+ textView.setOnClickListener(v -> onClickItem(position));
+ if (action.getMessageResId() != 0) {
+ textView.setText(action.getMessageResId());
+ } else {
+ textView.setText(action.getMessage());
+ }
+
+ if (action instanceof LongPressAction) {
+ textView.setOnLongClickListener(v -> onLongClickItem(position));
+ } else {
+ textView.setOnLongClickListener(null);
+ }
+ return textView;
+ }
+
+ private boolean onLongClickItem(int position) {
+ final Action action = getItem(position);
+ if (action instanceof LongPressAction) {
+ if (mDialog != null) {
+ mDialog.hidePowerOverflowMenu();
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action long-clicked while mDialog is null.");
+ }
+ return ((LongPressAction) action).onLongPress();
+ }
+ return false;
+ }
+
+ private void onClickItem(int position) {
+ Action item = getItem(position);
+ if (!(item instanceof SilentModeTriStateAction)) {
+ if (mDialog != null) {
+ mDialog.hidePowerOverflowMenu();
+ mDialog.dismiss();
+ } else {
+ Log.w(TAG, "Action clicked while mDialog is null.");
+ }
+ item.onPress();
+ }
+ }
+ }
+
// note: the scheme below made more sense when we were planning on having
// 8 different things in the global actions dialog. seems overkill with
// only 3 items now, but may as well keep this flexible approach so it will
@@ -1248,8 +1390,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
boolean showDuringKeyguard();
/**
- * @return whether this action should appear in the dialog before the device is
- * provisioned.onlongpress
+ * @return whether this action should appear in the dialog before the
+ * device is provisioned.f
*/
boolean showBeforeProvisioning();
@@ -1258,6 +1400,18 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
default boolean shouldBeSeparated() {
return false;
}
+
+ /**
+ * Return the id of the message associated with this action, or 0 if it doesn't have one.
+ * @return
+ */
+ int getMessageResId();
+
+ /**
+ * Return the message associated with this action, or null if it doesn't have one.
+ * @return
+ */
+ CharSequence getMessage();
}
/**
@@ -1309,6 +1463,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
+
+ public int getMessageResId() {
+ return mMessageResId;
+ }
+
+ public CharSequence getMessage() {
+ return mMessage;
+ }
+
public View create(
Context context, View convertView, ViewGroup parent, LayoutInflater inflater) {
View v = inflater.inflate(getActionLayoutId(), parent, false /* attach */);
@@ -1396,6 +1559,23 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return context.getString(mMessageResId);
}
+ private boolean isOn() {
+ return mState == ToggleState.On || mState == ToggleState.TurningOn;
+ }
+
+ @Override
+ public CharSequence getMessage() {
+ return null;
+ }
+ @Override
+ public int getMessageResId() {
+ return isOn() ? mEnabledStatusMessageResId : mDisabledStatusMessageResId;
+ }
+
+ private int getIconResId() {
+ return isOn() ? mEnabledIconResId : mDisabledIconResid;
+ }
+
public View create(Context context, View convertView, ViewGroup parent,
LayoutInflater inflater) {
willCreate();
@@ -1405,17 +1585,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
final boolean enabled = isEnabled();
- boolean on = ((mState == ToggleState.On) || (mState == ToggleState.TurningOn));
if (messageView != null) {
- messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
+ messageView.setText(getMessageResId());
messageView.setEnabled(enabled);
messageView.setSelected(true); // necessary for marquee to work
}
if (icon != null) {
- icon.setImageDrawable(context.getDrawable(
- (on ? mEnabledIconResId : mDisabledIconResid)));
+ icon.setImageDrawable(context.getDrawable(getIconResId()));
icon.setEnabled(enabled);
}
@@ -1553,6 +1731,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return null;
}
+ @Override
+ public int getMessageResId() {
+ return 0;
+ }
+
+ @Override
+ public CharSequence getMessage() {
+ return null;
+ }
+
public View create(Context context, View convertView, ViewGroup parent,
LayoutInflater inflater) {
View v = inflater.inflate(R.layout.global_actions_silent_mode, parent, false);
@@ -1627,7 +1815,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
};
- private ContentObserver mAirplaneModeObserver = new ContentObserver(new Handler()) {
+ private ContentObserver mAirplaneModeObserver = new ContentObserver(mMainHandler) {
@Override
public void onChange(boolean selfChange) {
onAirplaneModeChanged();
@@ -1636,7 +1824,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private static final int MESSAGE_DISMISS = 0;
private static final int MESSAGE_REFRESH = 1;
- private static final int MESSAGE_SHOW = 2;
private static final int DIALOG_DISMISS_DELAY = 300; // ms
private static final int DIALOG_PRESS_DELAY = 850; // ms
@@ -1650,7 +1837,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
case MESSAGE_DISMISS:
if (mDialog != null) {
if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) {
- mDialog.dismissImmediately();
+ mDialog.completeDismiss();
} else {
mDialog.dismiss();
}
@@ -1661,9 +1848,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
refreshSilentMode();
mAdapter.notifyDataSetChanged();
break;
- case MESSAGE_SHOW:
- handleShow();
- break;
}
}
};
@@ -1708,6 +1892,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private final Context mContext;
private final MyAdapter mAdapter;
+ private final MyOverflowAdapter mOverflowAdapter;
private final IStatusBarService mStatusBarService;
private final IBinder mToken = new Binder();
private MultiListLayout mGlobalActionsLayout;
@@ -1722,25 +1907,34 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final NotificationShadeDepthController mDepthController;
private final BlurUtils mBlurUtils;
+ private final boolean mUseControlsLayout;
+ private ListPopupWindow mOverflowPopup;
+ private final Runnable mOnRotateCallback;
private ControlsUiController mControlsUiController;
private ViewGroup mControlsView;
+ private ViewGroup mContainer;
- ActionsDialog(Context context, MyAdapter adapter,
+ ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
GlobalActionsPanelPlugin.PanelViewController plugin,
NotificationShadeDepthController depthController,
SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- ControlsUiController controlsUiController, BlurUtils blurUtils) {
+ ControlsUiController controlsUiController, BlurUtils blurUtils,
+ boolean useControlsLayout, Runnable onRotateCallback, boolean keyguardShowing) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
mContext = context;
mAdapter = adapter;
+ mOverflowAdapter = overflowAdapter;
mDepthController = depthController;
mColorExtractor = sysuiColorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
mControlsUiController = controlsUiController;
mBlurUtils = blurUtils;
+ mUseControlsLayout = useControlsLayout;
+ mOnRotateCallback = onRotateCallback;
+ mKeyguardShowing = keyguardShowing;
// Window initialization
Window window = getWindow();
@@ -1767,6 +1961,15 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
initializeLayout();
}
+ private boolean isShowingControls() {
+ return mControlsUiController != null;
+ }
+
+ private void showControls(ControlsUiController controller) {
+ mControlsUiController = controller;
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+ }
+
private boolean shouldUsePanel() {
return mPanelController != null && mPanelController.getPanelContent() != null;
}
@@ -1817,12 +2020,50 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
+ private ListPopupWindow createPowerOverflowPopup() {
+ ListPopupWindow popup = new ListPopupWindow(new ContextThemeWrapper(
+ mContext, com.android.systemui.R.style.Control_ListPopupWindow));
+ popup.setWindowLayoutType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ View overflowButton =
+ findViewById(com.android.systemui.R.id.global_actions_overflow_button);
+ popup.setAnchorView(overflowButton);
+ int parentWidth = mGlobalActionsLayout.getWidth();
+ // arbitrarily set the menu width to half of parent
+ // TODO: Logic for menu sizing based on contents.
+ int halfParentWidth = Math.round(parentWidth * 0.5f);
+ popup.setContentWidth(halfParentWidth);
+ popup.setAdapter(mOverflowAdapter);
+ popup.setModal(true);
+ return popup;
+ }
+
+ private void showPowerOverflowMenu() {
+ mOverflowPopup.show();
+
+ // Width is fixed to slightly more than half of the GlobalActionsLayout container.
+ // TODO: Resize the width of this dialog based on the sizes of the items in it.
+ int width = Math.round(mGlobalActionsLayout.getWidth() * 0.6f);
+
+ ListView listView = mOverflowPopup.getListView();
+ listView.setDividerHeight(mContext.getResources()
+ .getDimensionPixelSize(com.android.systemui.R.dimen.control_list_divider));
+ listView.setDivider(mContext.getResources().getDrawable(
+ com.android.systemui.R.drawable.controls_list_divider));
+ mOverflowPopup.setWidth(width);
+ mOverflowPopup.setHorizontalOffset(-width + mOverflowPopup.getAnchorView().getWidth());
+ mOverflowPopup.setVerticalOffset(mOverflowPopup.getAnchorView().getHeight());
+ mOverflowPopup.show();
+ }
+
+ private void hidePowerOverflowMenu() {
+ mOverflowPopup.dismiss();
+ }
+
private void initializeLayout() {
setContentView(getGlobalActionsLayoutId(mContext));
fixNavBarClipping();
mControlsView = findViewById(com.android.systemui.R.id.global_actions_controls);
mGlobalActionsLayout = findViewById(com.android.systemui.R.id.global_actions_view);
- mGlobalActionsLayout.setOutsideTouchListener(view -> dismiss());
mGlobalActionsLayout.setListViewAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public boolean dispatchPopulateAccessibilityEvent(
@@ -1834,14 +2075,31 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
});
mGlobalActionsLayout.setRotationListener(this::onRotate);
mGlobalActionsLayout.setAdapter(mAdapter);
+ mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
+ // Some legacy dialog layouts don't have the outer container
+ if (mContainer == null) {
+ mContainer = mGlobalActionsLayout;
+ }
- View globalActionsParent = (View) mGlobalActionsLayout.getParent();
- globalActionsParent.setOnClickListener(v -> dismiss());
-
- // add fall-through dismiss handling to root view
- View rootView = findViewById(com.android.systemui.R.id.global_actions_grid_root);
- if (rootView != null) {
- rootView.setOnClickListener(v -> dismiss());
+ mOverflowPopup = createPowerOverflowPopup();
+
+ View overflowButton = findViewById(
+ com.android.systemui.R.id.global_actions_overflow_button);
+ if (overflowButton != null) {
+ if (mOverflowAdapter.getCount() > 0) {
+ overflowButton.setOnClickListener((view) -> showPowerOverflowMenu());
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
+ params.setMarginEnd(0);
+ mGlobalActionsLayout.setLayoutParams(params);
+ } else {
+ overflowButton.setVisibility(View.GONE);
+ LinearLayout.LayoutParams params =
+ (LinearLayout.LayoutParams) mGlobalActionsLayout.getLayoutParams();
+ params.setMarginEnd(mContext.getResources().getDimensionPixelSize(
+ com.android.systemui.R.dimen.global_actions_side_margin));
+ mGlobalActionsLayout.setLayoutParams(params);
+ }
}
if (shouldUsePanel()) {
@@ -1849,7 +2107,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
if (mBackgroundDrawable == null) {
mBackgroundDrawable = new ScrimDrawable();
- if (mControlsUiController != null) {
+ if (mUseControlsLayout) {
mScrimAlpha = 1.0f;
} else {
mScrimAlpha = mBlurUtils.supportsBlursOnWindows()
@@ -1869,7 +2127,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
private int getGlobalActionsLayoutId(Context context) {
- if (mControlsUiController != null) {
+ if (mUseControlsLayout) {
return com.android.systemui.R.layout.global_actions_grid_v2;
}
@@ -1914,9 +2172,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
if (!(mBackgroundDrawable instanceof ScrimDrawable)) {
return;
}
- boolean hasControls = mControlsUiController != null;
((ScrimDrawable) mBackgroundDrawable).setColor(
- !hasControls && colors.supportsDarkText() ? Color.WHITE : Color.BLACK, animate);
+ !mUseControlsLayout && colors.supportsDarkText()
+ ? Color.WHITE : Color.BLACK, animate);
View decorView = getWindow().getDecorView();
if (colors.supportsDarkText()) {
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
@@ -1939,10 +2197,10 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
mNotificationShadeWindowController.setForceHasTopUi(true);
mBackgroundDrawable.setAlpha(0);
- mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
- mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
- mGlobalActionsLayout.setAlpha(0);
- mGlobalActionsLayout.animate()
+ mContainer.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
+ mContainer.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
+ mContainer.setAlpha(0);
+ mContainer.animate()
.alpha(1)
.translationX(0)
.translationY(0)
@@ -1958,55 +2216,64 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
.start();
ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
root.setOnApplyWindowInsetsListener((v, windowInsets) -> {
- if (mControlsUiController != null) {
- Insets insets = windowInsets.getInsets(WindowInsets.Type.all());
- root.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ if (mUseControlsLayout) {
+ root.setPadding(windowInsets.getStableInsetLeft(),
+ windowInsets.getStableInsetTop(),
+ windowInsets.getStableInsetRight(),
+ windowInsets.getStableInsetBottom());
}
return WindowInsets.CONSUMED;
});
if (mControlsUiController != null) {
- mControlsUiController.show(mControlsView);
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
}
}
@Override
public void dismiss() {
+ dismissWithAnimation(() -> {
+ mContainer.setTranslationX(0);
+ mContainer.setTranslationY(0);
+ mContainer.setAlpha(1);
+ mContainer.animate()
+ .alpha(0)
+ .translationX(mGlobalActionsLayout.getAnimationOffsetX())
+ .translationY(mGlobalActionsLayout.getAnimationOffsetY())
+ .setDuration(450)
+ .withEndAction(this::completeDismiss)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener(animation -> {
+ float animatedValue = 1f - animation.getAnimatedFraction();
+ int alpha = (int) (animatedValue * mScrimAlpha * 255);
+ mBackgroundDrawable.setAlpha(alpha);
+ mDepthController.updateGlobalDialogVisibility(animatedValue,
+ mGlobalActionsLayout);
+ })
+ .start();
+ });
+ }
+
+ private void dismissForControlsActivity() {
+ dismissWithAnimation(() -> {
+ ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent();
+ ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
+ });
+ }
+
+ void dismissWithAnimation(Runnable animation) {
if (!mShowing) {
return;
}
mShowing = false;
- if (mControlsUiController != null) mControlsUiController.hide();
- mGlobalActionsLayout.setTranslationX(0);
- mGlobalActionsLayout.setTranslationY(0);
- mGlobalActionsLayout.setAlpha(1);
- mGlobalActionsLayout.animate()
- .alpha(0)
- .translationX(mGlobalActionsLayout.getAnimationOffsetX())
- .translationY(mGlobalActionsLayout.getAnimationOffsetY())
- .setDuration(550)
- .withEndAction(this::completeDismiss)
- .setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(animation -> {
- float animatedValue = 1f - animation.getAnimatedFraction();
- int alpha = (int) (animatedValue * mScrimAlpha * 255);
- mBackgroundDrawable.setAlpha(alpha);
- mDepthController.updateGlobalDialogVisibility(animatedValue,
- mGlobalActionsLayout);
- })
- .start();
- dismissPanel();
- resetOrientation();
+ animation.run();
}
- void dismissImmediately() {
+ private void completeDismiss() {
mShowing = false;
- if (mControlsUiController != null) mControlsUiController.hide();
- dismissPanel();
resetOrientation();
- completeDismiss();
- }
-
- private void completeDismiss() {
+ dismissPanel();
+ dismissOverflow(true);
+ if (mControlsUiController != null) mControlsUiController.hide();
mNotificationShadeWindowController.setForceHasTopUi(mHadTopUi);
mDepthController.updateGlobalDialogVisibility(0, null /* view */);
super.dismiss();
@@ -2018,6 +2285,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
}
}
+ private void dismissOverflow(boolean immediate) {
+ if (mOverflowPopup != null) {
+ if (immediate) {
+ mOverflowPopup.dismissImmediate();
+ } else {
+ mOverflowPopup.dismiss();
+ }
+ }
+ }
+
private void setRotationSuggestionsEnabled(boolean enabled) {
try {
final int userId = Binder.getCallingUserHandle().getIdentifier();
@@ -2060,10 +2337,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
public void refreshDialog() {
initializeLayout();
mGlobalActionsLayout.updateList();
+ if (mControlsUiController != null) {
+ mControlsUiController.show(mControlsView, this::dismissForControlsActivity);
+ }
}
public void onRotate(int from, int to) {
if (mShowing) {
+ mOnRotateCallback.run();
refreshDialog();
}
}
@@ -2090,9 +2371,22 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
return isPanelDebugModeEnabled(context);
}
- private boolean shouldShowControls() {
- return mKeyguardStateController.isUnlocked()
+ @VisibleForTesting
+ protected boolean shouldShowControls() {
+ boolean isUnlocked = mKeyguardStateController.isUnlocked()
+ || mKeyguardStateController.canDismissLockScreen();
+ return (isUnlocked || mShowLockScreenCardsAndControls)
&& mControlsUiController.getAvailable()
&& !mControlsServiceInfos.isEmpty();
}
+ // TODO: Remove legacy layout XML and classes.
+ protected boolean shouldUseControlsLayout() {
+ // always use new controls layout
+ return true;
+ }
+
+ private void onPowerMenuLockScreenSettingsChanged() {
+ mShowLockScreenCardsAndControls = Settings.Secure.getInt(mContentResolver,
+ Settings.Secure.POWER_MENU_LOCKED_SHOW_CONTENT, 0) != 0;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
index f1025615783b..2f32d972449e 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -32,7 +32,6 @@ import com.android.systemui.R;
* Flat, single-row implementation of the button layout created by the global actions dialog.
*/
public class GlobalActionsFlatLayout extends GlobalActionsLayout {
- private static final int MAX_ITEMS = 4;
public GlobalActionsFlatLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -54,11 +53,28 @@ public class GlobalActionsFlatLayout extends GlobalActionsLayout {
return null;
}
+ private View getOverflowButton() {
+ return findViewById(com.android.systemui.R.id.global_actions_overflow_button);
+ }
+
@Override
protected void addToListView(View v, boolean reverse) {
- // only add items to the list view if we haven't hit our max yet
- if (getListView().getChildCount() < MAX_ITEMS) {
- super.addToListView(v, reverse);
+ super.addToListView(v, reverse);
+ View overflowButton = getOverflowButton();
+ // if there's an overflow button, make sure it stays at the end
+ if (overflowButton != null) {
+ getListView().removeView(overflowButton);
+ super.addToListView(overflowButton, reverse);
+ }
+ }
+
+ @Override
+ protected void removeAllListViews() {
+ View overflowButton = getOverflowButton();
+ super.removeAllListViews();
+ // if there's an overflow button, add it back after clearing the list views
+ if (overflowButton != null) {
+ super.addToListView(overflowButton, false);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index 15cf1a060f4c..09757a4d6204 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -88,7 +88,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks
public void showGlobalActions(GlobalActionsManager manager) {
if (mDisabled) return;
mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
- mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
+ mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned(),
mPanelExtension.get());
Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
index 88ab9ef4b014..61524900b89b 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/GLWallpaperRenderer.java
@@ -49,20 +49,6 @@ public interface GLWallpaperRenderer {
void onDrawFrame();
/**
- * Notify ambient mode is changed.
- * @param inAmbientMode true if in ambient mode.
- * @param duration duration of transition.
- */
- void updateAmbientMode(boolean inAmbientMode, long duration);
-
- /**
- * Notify the wallpaper offsets changed.
- * @param xOffset offset along x axis.
- * @param yOffset offset along y axis.
- */
- void updateOffsets(float xOffset, float yOffset);
-
- /**
* Ask renderer to report the surface size it needs.
*/
Size reportSurfaceSize();
@@ -81,24 +67,4 @@ public interface GLWallpaperRenderer {
*/
void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args);
- /**
- * A proxy which owns surface holder.
- */
- interface SurfaceProxy {
-
- /**
- * Ask proxy to start rendering frame to surface.
- */
- void requestRender();
-
- /**
- * Ask proxy to prepare render context.
- */
- void preRender();
-
- /**
- * Ask proxy to destroy render context.
- */
- void postRender();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
index 626d0cfed997..fa45ea1acb95 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageGLWallpaper.java
@@ -33,7 +33,6 @@ import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glVertexAttribPointer;
import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.opengl.GLUtils;
import android.util.Log;
@@ -50,14 +49,9 @@ import java.nio.FloatBuffer;
class ImageGLWallpaper {
private static final String TAG = ImageGLWallpaper.class.getSimpleName();
- static final String A_POSITION = "aPosition";
- static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
- static final String U_PER85 = "uPer85";
- static final String U_REVEAL = "uReveal";
- static final String U_AOD2OPACITY = "uAod2Opacity";
- static final String U_TEXTURE = "uTexture";
-
- private static final int HANDLE_UNDEFINED = -1;
+ private static final String A_POSITION = "aPosition";
+ private static final String A_TEXTURE_COORDINATES = "aTextureCoordinates";
+ private static final String U_TEXTURE = "uTexture";
private static final int POSITION_COMPONENT_COUNT = 2;
private static final int TEXTURE_COMPONENT_COUNT = 2;
private static final int BYTES_PER_FLOAT = 4;
@@ -88,14 +82,9 @@ class ImageGLWallpaper {
private int mAttrPosition;
private int mAttrTextureCoordinates;
- private int mUniAod2Opacity;
- private int mUniPer85;
- private int mUniReveal;
private int mUniTexture;
private int mTextureId;
- private float[] mCurrentTexCoordinate;
-
ImageGLWallpaper(ImageGLProgram program) {
mProgram = program;
@@ -135,31 +124,9 @@ class ImageGLWallpaper {
}
private void setupUniforms() {
- mUniAod2Opacity = mProgram.getUniformHandle(U_AOD2OPACITY);
- mUniPer85 = mProgram.getUniformHandle(U_PER85);
- mUniReveal = mProgram.getUniformHandle(U_REVEAL);
mUniTexture = mProgram.getUniformHandle(U_TEXTURE);
}
- int getHandle(String name) {
- switch (name) {
- case A_POSITION:
- return mAttrPosition;
- case A_TEXTURE_COORDINATES:
- return mAttrTextureCoordinates;
- case U_AOD2OPACITY:
- return mUniAod2Opacity;
- case U_PER85:
- return mUniPer85;
- case U_REVEAL:
- return mUniReveal;
- case U_TEXTURE:
- return mUniTexture;
- default:
- return HANDLE_UNDEFINED;
- }
- }
-
void draw() {
glDrawArrays(GL_TRIANGLES, 0, VERTICES.length / 2);
}
@@ -201,87 +168,6 @@ class ImageGLWallpaper {
}
/**
- * This method adjust s(x-axis), t(y-axis) texture coordinates to get current display area
- * of texture and will be used during transition.
- * The adjustment happens if either the width or height of the surface is larger than
- * corresponding size of the display area.
- * If both width and height are larger than corresponding size of the display area,
- * the adjustment will happen at both s, t side.
- *
- * @param surface The size of the surface.
- * @param scissor The display area.
- * @param xOffset The offset amount along s axis.
- * @param yOffset The offset amount along t axis.
- */
- void adjustTextureCoordinates(Rect surface, Rect scissor, float xOffset, float yOffset) {
- mCurrentTexCoordinate = TEXTURES.clone();
-
- if (surface == null || scissor == null) {
- mTextureBuffer.put(mCurrentTexCoordinate);
- mTextureBuffer.position(0);
- return;
- }
-
- int surfaceWidth = surface.width();
- int surfaceHeight = surface.height();
- int scissorWidth = scissor.width();
- int scissorHeight = scissor.height();
-
- if (surfaceWidth > scissorWidth) {
- // Calculate the new s pos in pixels.
- float pixelS = (float) Math.round((surfaceWidth - scissorWidth) * xOffset);
- // Calculate the s pos in texture coordinate.
- float coordinateS = pixelS / surfaceWidth;
- // Calculate the percentage occupied by the scissor width in surface width.
- float surfacePercentageW = (float) scissorWidth / surfaceWidth;
- // Need also consider the case if surface height is smaller than scissor height.
- if (surfaceHeight < scissorHeight) {
- // We will narrow the surface percentage to keep aspect ratio.
- surfacePercentageW *= (float) surfaceHeight / scissorHeight;
- }
- // Determine the final s pos, also limit the legal s pos to prevent from out of range.
- float s = coordinateS + surfacePercentageW > 1f ? 1f - surfacePercentageW : coordinateS;
- // Traverse the s pos in texture coordinates array and adjust the s pos accordingly.
- for (int i = 0; i < mCurrentTexCoordinate.length; i += 2) {
- // indices 2, 4 and 6 are the end of s coordinates.
- if (i == 2 || i == 4 || i == 6) {
- mCurrentTexCoordinate[i] = Math.min(1f, s + surfacePercentageW);
- } else {
- mCurrentTexCoordinate[i] = s;
- }
- }
- }
-
- if (surfaceHeight > scissorHeight) {
- // Calculate the new t pos in pixels.
- float pixelT = (float) Math.round((surfaceHeight - scissorHeight) * yOffset);
- // Calculate the t pos in texture coordinate.
- float coordinateT = pixelT / surfaceHeight;
- // Calculate the percentage occupied by the scissor height in surface height.
- float surfacePercentageH = (float) scissorHeight / surfaceHeight;
- // Need also consider the case if surface width is smaller than scissor width.
- if (surfaceWidth < scissorWidth) {
- // We will narrow the surface percentage to keep aspect ratio.
- surfacePercentageH *= (float) surfaceWidth / scissorWidth;
- }
- // Determine the final t pos, also limit the legal t pos to prevent from out of range.
- float t = coordinateT + surfacePercentageH > 1f ? 1f - surfacePercentageH : coordinateT;
- // Traverse the t pos in texture coordinates array and adjust the t pos accordingly.
- for (int i = 1; i < mCurrentTexCoordinate.length; i += 2) {
- // indices 1, 3 and 11 are the end of t coordinates.
- if (i == 1 || i == 3 || i == 11) {
- mCurrentTexCoordinate[i] = Math.min(1f, t + surfacePercentageH);
- } else {
- mCurrentTexCoordinate[i] = t;
- }
- }
- }
-
- mTextureBuffer.put(mCurrentTexCoordinate);
- mTextureBuffer.position(0);
- }
-
- /**
* Called to dump current state.
* @param prefix prefix.
* @param fd fd.
@@ -289,17 +175,5 @@ class ImageGLWallpaper {
* @param args args.
*/
public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
- StringBuilder sb = new StringBuilder();
- sb.append('{');
- if (mCurrentTexCoordinate != null) {
- for (int i = 0; i < mCurrentTexCoordinate.length; i++) {
- sb.append(mCurrentTexCoordinate[i]).append(',');
- if (i == mCurrentTexCoordinate.length - 1) {
- sb.deleteCharAt(sb.length() - 1);
- }
- }
- }
- sb.append('}');
- out.print(prefix); out.print("mTexCoordinates="); out.println(sb.toString());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
deleted file mode 100644
index 703d5910500a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
+++ /dev/null
@@ -1,246 +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.glwallpaper;
-
-import static com.android.systemui.glwallpaper.ImageWallpaperRenderer.WallpaperTexture;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ColorMatrix;
-import android.graphics.ColorMatrixColorFilter;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Handler.Callback;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * A helper class that computes threshold from a bitmap.
- * Threshold will be computed each time the user picks a new image wallpaper.
- */
-class ImageProcessHelper {
- private static final String TAG = ImageProcessHelper.class.getSimpleName();
- private static final float DEFAULT_THRESHOLD = 0.8f;
- private static final float DEFAULT_OTSU_THRESHOLD = 0f;
- private static final float MAX_THRESHOLD = 0.89f;
- private static final int MSG_UPDATE_THRESHOLD = 1;
-
- /**
- * This color matrix will be applied to each pixel to get luminance from rgb by below formula:
- * Luminance = .2126f * r + .7152f * g + .0722f * b.
- */
- private static final float[] LUMINOSITY_MATRIX = new float[] {
- .2126f, .0000f, .0000f, .0000f, .0000f,
- .0000f, .7152f, .0000f, .0000f, .0000f,
- .0000f, .0000f, .0722f, .0000f, .0000f,
- .0000f, .0000f, .0000f, 1.000f, .0000f
- };
-
- private final Handler mHandler = new Handler(new Callback() {
- @Override
- public boolean handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_UPDATE_THRESHOLD:
- mThreshold = (float) msg.obj;
- return true;
- default:
- return false;
- }
- }
- });
-
- private float mThreshold = DEFAULT_THRESHOLD;
-
- void start(WallpaperTexture texture) {
- new ThresholdComputeTask(mHandler).execute(texture);
- }
-
- float getThreshold() {
- return Math.min(mThreshold, MAX_THRESHOLD);
- }
-
- private static class ThresholdComputeTask extends AsyncTask<WallpaperTexture, Void, Float> {
- private Handler mUpdateHandler;
-
- ThresholdComputeTask(Handler handler) {
- super(handler);
- mUpdateHandler = handler;
- }
-
- @Override
- protected Float doInBackground(WallpaperTexture... textures) {
- WallpaperTexture texture = textures[0];
- final float[] threshold = new float[] {DEFAULT_THRESHOLD};
- if (texture == null) {
- Log.e(TAG, "ThresholdComputeTask: WallpaperTexture not initialized");
- return threshold[0];
- }
-
- texture.use(bitmap -> {
- if (bitmap != null) {
- threshold[0] = new Threshold().compute(bitmap);
- } else {
- Log.e(TAG, "ThresholdComputeTask: Can't get bitmap");
- }
- });
- return threshold[0];
- }
-
- @Override
- protected void onPostExecute(Float result) {
- Message msg = mUpdateHandler.obtainMessage(MSG_UPDATE_THRESHOLD, result);
- mUpdateHandler.sendMessage(msg);
- }
- }
-
- private static class Threshold {
- public float compute(Bitmap bitmap) {
- Bitmap grayscale = toGrayscale(bitmap);
- int[] histogram = getHistogram(grayscale);
- boolean isSolidColor = isSolidColor(grayscale, histogram);
-
- // We will see gray wallpaper during the transition if solid color wallpaper is set,
- // please refer to b/130360362#comment16.
- // As a result, we use Percentile85 rather than Otsus if a solid color wallpaper is set.
- ThresholdAlgorithm algorithm = isSolidColor ? new Percentile85() : new Otsus();
- return algorithm.compute(grayscale, histogram);
- }
-
- private Bitmap toGrayscale(Bitmap bitmap) {
- int width = bitmap.getWidth();
- int height = bitmap.getHeight();
-
- Bitmap grayscale = Bitmap.createBitmap(width, height, bitmap.getConfig(),
- false /* hasAlpha */, bitmap.getColorSpace());
- Canvas canvas = new Canvas(grayscale);
- ColorMatrix cm = new ColorMatrix(LUMINOSITY_MATRIX);
- Paint paint = new Paint();
- paint.setColorFilter(new ColorMatrixColorFilter(cm));
- canvas.drawBitmap(bitmap, new Matrix(), paint);
-
- return grayscale;
- }
-
- private int[] getHistogram(Bitmap grayscale) {
- int width = grayscale.getWidth();
- int height = grayscale.getHeight();
-
- // TODO: Fine tune the performance here, tracking on b/123615079.
- int[] histogram = new int[256];
- for (int row = 0; row < height; row++) {
- for (int col = 0; col < width; col++) {
- int pixel = grayscale.getPixel(col, row);
- int y = Color.red(pixel) + Color.green(pixel) + Color.blue(pixel);
- histogram[y]++;
- }
- }
-
- return histogram;
- }
-
- private boolean isSolidColor(Bitmap bitmap, int[] histogram) {
- boolean solidColor = false;
- int pixels = bitmap.getWidth() * bitmap.getHeight();
-
- // In solid color case, only one element of histogram has value,
- // which is pixel counts and the value of other elements should be 0.
- for (int value : histogram) {
- if (value != 0 && value != pixels) {
- break;
- }
- if (value == pixels) {
- solidColor = true;
- break;
- }
- }
- return solidColor;
- }
- }
-
- private static class Percentile85 implements ThresholdAlgorithm {
- @Override
- public float compute(Bitmap bitmap, int[] histogram) {
- float per85 = DEFAULT_THRESHOLD;
- int pixelCount = bitmap.getWidth() * bitmap.getHeight();
- float[] acc = new float[256];
- for (int i = 0; i < acc.length; i++) {
- acc[i] = (float) histogram[i] / pixelCount;
- float prev = i == 0 ? 0f : acc[i - 1];
- float next = acc[i];
- float idx = (float) (i + 1) / 255;
- float sum = prev + next;
- if (prev < 0.85f && sum >= 0.85f) {
- per85 = idx;
- }
- if (i > 0) {
- acc[i] += acc[i - 1];
- }
- }
- return per85;
- }
- }
-
- private static class Otsus implements ThresholdAlgorithm {
- @Override
- public float compute(Bitmap bitmap, int[] histogram) {
- float threshold = DEFAULT_OTSU_THRESHOLD;
- float maxVariance = 0;
- float pixelCount = bitmap.getWidth() * bitmap.getHeight();
- float[] w = new float[2];
- float[] m = new float[2];
- float[] u = new float[2];
-
- for (int i = 0; i < histogram.length; i++) {
- m[1] += i * histogram[i];
- }
-
- w[1] = pixelCount;
- for (int tonalValue = 0; tonalValue < histogram.length; tonalValue++) {
- float dU;
- float variance;
- float numPixels = histogram[tonalValue];
- float tmp = numPixels * tonalValue;
- w[0] += numPixels;
- w[1] -= numPixels;
-
- if (w[0] == 0 || w[1] == 0) {
- continue;
- }
-
- m[0] += tmp;
- m[1] -= tmp;
- u[0] = m[0] / w[0];
- u[1] = m[1] / w[1];
- dU = u[0] - u[1];
- variance = w[0] * w[1] * dU * dU;
-
- if (variance > maxVariance) {
- threshold = (tonalValue + 1f) / histogram.length;
- maxVariance = variance;
- }
- }
- return threshold;
- }
- }
-
- private interface ThresholdAlgorithm {
- float compute(Bitmap bitmap, int[] histogram);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
deleted file mode 100644
index f815b5d476ec..000000000000
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageRevealHelper.java
+++ /dev/null
@@ -1,126 +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.glwallpaper;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.util.Log;
-
-import com.android.systemui.Interpolators;
-
-/**
- * Use ValueAnimator and appropriate interpolator to control the progress of reveal transition.
- * The transition will happen while getting awake and quit events.
- */
-class ImageRevealHelper {
- private static final String TAG = ImageRevealHelper.class.getSimpleName();
- private static final float MAX_REVEAL = 0f;
- private static final float MIN_REVEAL = 1f;
- private static final boolean DEBUG = true;
-
- private final ValueAnimator mAnimator;
- private final RevealStateListener mRevealListener;
- private float mReveal = MAX_REVEAL;
- private boolean mAwake = false;
-
- ImageRevealHelper(RevealStateListener listener) {
- mRevealListener = listener;
- mAnimator = ValueAnimator.ofFloat();
- mAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mAnimator.addUpdateListener(animator -> {
- mReveal = (float) animator.getAnimatedValue();
- if (mRevealListener != null) {
- mRevealListener.onRevealStateChanged();
- }
- });
- mAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mIsCanceled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mIsCanceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (!mIsCanceled && mRevealListener != null) {
- if (DEBUG) {
- Log.d(TAG, "transition end");
- }
- mRevealListener.onRevealEnd();
- }
- mIsCanceled = false;
- }
-
- @Override
- public void onAnimationStart(Animator animation) {
- if (mRevealListener != null) {
- if (DEBUG) {
- Log.d(TAG, "transition start");
- }
- mRevealListener.onRevealStart(true /* animate */);
- }
- }
- });
- }
-
- public float getReveal() {
- return mReveal;
- }
-
- void updateAwake(boolean awake, long duration) {
- if (DEBUG) {
- Log.d(TAG, "updateAwake: awake=" + awake + ", duration=" + duration);
- }
- mAnimator.cancel();
- mAwake = awake;
- if (duration == 0) {
- // We are transiting from home to aod or aod to home directly,
- // we don't need to do transition in these cases.
- mReveal = mAwake ? MAX_REVEAL : MIN_REVEAL;
- mRevealListener.onRevealStart(false /* animate */);
- mRevealListener.onRevealStateChanged();
- mRevealListener.onRevealEnd();
- } else {
- mAnimator.setDuration(duration);
- mAnimator.setFloatValues(mReveal, mAwake ? MAX_REVEAL : MIN_REVEAL);
- mAnimator.start();
- }
- }
-
- /**
- * A listener to trace value changes of reveal.
- */
- public interface RevealStateListener {
-
- /**
- * Called back while reveal status changes.
- */
- void onRevealStateChanged();
-
- /**
- * Called back while reveal starts.
- */
- void onRevealStart(boolean animate);
-
- /**
- * Called back while reveal ends.
- */
- void onRevealEnd();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
index e9ddb3831b1a..1a0356c4446d 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageWallpaperRenderer.java
@@ -19,18 +19,14 @@ package com.android.systemui.glwallpaper;
import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
-import static android.opengl.GLES20.glUniform1f;
import static android.opengl.GLES20.glViewport;
import android.app.WallpaperManager;
import android.content.Context;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.util.Log;
-import android.util.MathUtils;
import android.util.Size;
-import android.view.DisplayInfo;
import com.android.systemui.R;
@@ -42,57 +38,24 @@ import java.util.function.Consumer;
/**
* A GL renderer for image wallpaper.
*/
-public class ImageWallpaperRenderer implements GLWallpaperRenderer,
- ImageRevealHelper.RevealStateListener {
+public class ImageWallpaperRenderer implements GLWallpaperRenderer {
private static final String TAG = ImageWallpaperRenderer.class.getSimpleName();
- private static final float SCALE_VIEWPORT_MIN = 1f;
- private static final float SCALE_VIEWPORT_MAX = 1.1f;
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
private final ImageGLProgram mProgram;
private final ImageGLWallpaper mWallpaper;
- private final ImageProcessHelper mImageProcessHelper;
- private final ImageRevealHelper mImageRevealHelper;
-
- private SurfaceProxy mProxy;
- private final Rect mScissor;
private final Rect mSurfaceSize = new Rect();
- private final Rect mViewport = new Rect();
- private boolean mScissorMode;
- private float mXOffset;
- private float mYOffset;
private final WallpaperTexture mTexture;
- public ImageWallpaperRenderer(Context context, SurfaceProxy proxy) {
+ public ImageWallpaperRenderer(Context context) {
final WallpaperManager wpm = context.getSystemService(WallpaperManager.class);
if (wpm == null) {
Log.w(TAG, "WallpaperManager not available");
}
mTexture = new WallpaperTexture(wpm);
- DisplayInfo displayInfo = new DisplayInfo();
- context.getDisplay().getDisplayInfo(displayInfo);
-
- // We only do transition in portrait currently, b/137962047.
- int orientation = context.getResources().getConfiguration().orientation;
- if (orientation == Configuration.ORIENTATION_PORTRAIT) {
- mScissor = new Rect(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
- } else {
- mScissor = new Rect(0, 0, displayInfo.logicalHeight, displayInfo.logicalWidth);
- }
-
- mProxy = proxy;
mProgram = new ImageGLProgram(context);
mWallpaper = new ImageGLWallpaper(mProgram);
- mImageProcessHelper = new ImageProcessHelper();
- mImageRevealHelper = new ImageRevealHelper(this);
-
- startProcessingImage();
- }
-
- protected void startProcessingImage() {
- // Compute threshold of the image, this is an async work.
- mImageProcessHelper.start(mTexture);
}
@Override
@@ -121,103 +84,26 @@ public class ImageWallpaperRenderer implements GLWallpaperRenderer,
@Override
public void onDrawFrame() {
- float threshold = mImageProcessHelper.getThreshold();
- float reveal = mImageRevealHelper.getReveal();
-
- glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_AOD2OPACITY), 1);
- glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_PER85), threshold);
- glUniform1f(mWallpaper.getHandle(ImageGLWallpaper.U_REVEAL), reveal);
-
glClear(GL_COLOR_BUFFER_BIT);
- // We only need to scale viewport while doing transition.
- if (mScissorMode) {
- scaleViewport(reveal);
- } else {
- glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height());
- }
+ glViewport(0, 0, mSurfaceSize.width(), mSurfaceSize.height());
mWallpaper.useTexture();
mWallpaper.draw();
}
@Override
- public void updateAmbientMode(boolean inAmbientMode, long duration) {
- mImageRevealHelper.updateAwake(!inAmbientMode, duration);
- }
-
- @Override
- public void updateOffsets(float xOffset, float yOffset) {
- mXOffset = xOffset;
- mYOffset = yOffset;
- int left = (int) ((mSurfaceSize.width() - mScissor.width()) * xOffset);
- int right = left + mScissor.width();
- mScissor.set(left, mScissor.top, right, mScissor.bottom);
- }
-
- @Override
public Size reportSurfaceSize() {
- mTexture.use(null);
+ mTexture.use(null /* consumer */);
mSurfaceSize.set(mTexture.getTextureDimensions());
return new Size(mSurfaceSize.width(), mSurfaceSize.height());
}
@Override
public void finish() {
- mProxy = null;
- }
-
- private void scaleViewport(float reveal) {
- int left = mScissor.left;
- int top = mScissor.top;
- int width = mScissor.width();
- int height = mScissor.height();
- // Interpolation between SCALE_VIEWPORT_MAX and SCALE_VIEWPORT_MIN by reveal.
- float vpScaled = MathUtils.lerp(SCALE_VIEWPORT_MIN, SCALE_VIEWPORT_MAX, reveal);
- // Calculate the offset amount from the lower left corner.
- float offset = (SCALE_VIEWPORT_MIN - vpScaled) / 2;
- // Change the viewport.
- mViewport.set((int) (left + width * offset), (int) (top + height * offset),
- (int) (width * vpScaled), (int) (height * vpScaled));
- glViewport(mViewport.left, mViewport.top, mViewport.right, mViewport.bottom);
- }
-
- @Override
- public void onRevealStateChanged() {
- mProxy.requestRender();
- }
-
- @Override
- public void onRevealStart(boolean animate) {
- if (animate) {
- mScissorMode = true;
- // Use current display area of texture.
- mWallpaper.adjustTextureCoordinates(mSurfaceSize, mScissor, mXOffset, mYOffset);
- }
- mProxy.preRender();
- }
-
- @Override
- public void onRevealEnd() {
- if (mScissorMode) {
- mScissorMode = false;
- // reset texture coordinates to use full texture.
- mWallpaper.adjustTextureCoordinates(null, null, 0, 0);
- // We need draw full texture back before finishing render.
- mProxy.requestRender();
- }
- mProxy.postRender();
}
@Override
public void dump(String prefix, FileDescriptor fd, PrintWriter out, String[] args) {
- out.print(prefix); out.print("mProxy="); out.print(mProxy);
out.print(prefix); out.print("mSurfaceSize="); out.print(mSurfaceSize);
- out.print(prefix); out.print("mScissor="); out.print(mScissor);
- out.print(prefix); out.print("mViewport="); out.print(mViewport);
- out.print(prefix); out.print("mScissorMode="); out.print(mScissorMode);
- out.print(prefix); out.print("mXOffset="); out.print(mXOffset);
- out.print(prefix); out.print("mYOffset="); out.print(mYOffset);
- out.print(prefix); out.print("threshold="); out.print(mImageProcessHelper.getThreshold());
- out.print(prefix); out.print("mReveal="); out.print(mImageRevealHelper.getReveal());
out.print(prefix); out.print("mWcgContent="); out.print(isWcgContent());
mWallpaper.dump(prefix, fd, out, args);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 1012a5213a58..b26dc5f91245 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -1717,9 +1717,9 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
resetKeyguardDonePendingLocked();
}
- mUpdateMonitor.clearBiometricRecognized();
if (mGoingToSleep) {
+ mUpdateMonitor.clearBiometricRecognized();
Log.i(TAG, "Device is going to sleep, aborting keyguardDone");
return;
}
@@ -1740,6 +1740,7 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable {
}
handleHide();
+ mUpdateMonitor.clearBiometricRecognized();
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 14e3e9390825..123cf78d74f8 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -61,6 +61,18 @@ public class LogModule {
return buffer;
}
+ /** Provides a logging buffer for all logs related to the data layer of notifications. */
+ @Provides
+ @Singleton
+ @NotifInteractionLog
+ public static LogBuffer provideNotifInteractionLogBuffer(
+ LogcatEchoTracker echoTracker,
+ DumpManager dumpManager) {
+ LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
+ buffer.attach(dumpManager);
+ return buffer;
+ }
+
/** Provides a logging buffer for all logs related to Quick Settings. */
@Provides
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
new file mode 100644
index 000000000000..20fc6ff445a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for messages related to the user interacting with notifications (e.g.
+ * clicking on them).
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotifInteractionLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 62efd8ce4cee..9509e6d479e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -19,22 +19,30 @@ package com.android.systemui.media;
import android.annotation.LayoutRes;
import android.app.PendingIntent;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.RippleDrawable;
+import android.media.MediaDescription;
import android.media.MediaMetadata;
+import android.media.ThumbnailUtils;
import android.media.session.MediaController;
+import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession;
import android.media.session.PlaybackState;
+import android.net.Uri;
+import android.service.media.MediaBrowserService;
+import android.text.TextUtils;
import android.util.Log;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -52,13 +60,12 @@ import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.media.MediaOutputSliceConstants;
import com.android.settingslib.widget.AdaptiveIcon;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationMediaManager.MediaListener;
+import com.android.systemui.qs.QSMediaBrowser;
import com.android.systemui.util.Assert;
+import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
@@ -67,10 +74,10 @@ import java.util.concurrent.Executor;
*/
public class MediaControlPanel {
private static final String TAG = "MediaControlPanel";
- private final NotificationMediaManager mMediaManager;
@Nullable private final LocalMediaManager mLocalMediaManager;
private final Executor mForegroundExecutor;
- private final Executor mBackgroundExecutor;
+ protected final Executor mBackgroundExecutor;
+ private final ActivityStarter mActivityStarter;
private Context mContext;
protected LinearLayout mMediaNotifView;
@@ -79,12 +86,19 @@ public class MediaControlPanel {
private MediaController mController;
private int mForegroundColor;
private int mBackgroundColor;
- protected ComponentName mRecvComponent;
private MediaDevice mDevice;
+ protected ComponentName mServiceComponent;
private boolean mIsRegistered = false;
+ private String mKey;
private final int[] mActionIds;
+ public static final String MEDIA_PREFERENCES = "media_control_prefs";
+ public static final String MEDIA_PREFERENCE_KEY = "browser_components";
+ private SharedPreferences mSharedPrefs;
+ private boolean mCheckedForResumption = false;
+ private boolean mIsRemotePlayback;
+
// Button IDs used in notifications
protected static final int[] NOTIF_ACTION_IDS = {
com.android.internal.R.id.action0,
@@ -94,6 +108,13 @@ public class MediaControlPanel {
com.android.internal.R.id.action4
};
+ // URI fields to try loading album art from
+ private static final String[] ART_URIS = {
+ MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+ MediaMetadata.METADATA_KEY_ART_URI,
+ MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+ };
+
private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
@Override
public void onSessionDestroyed() {
@@ -102,12 +123,11 @@ public class MediaControlPanel {
clearControls();
makeInactive();
}
- };
-
- private final MediaListener mMediaListener = new MediaListener() {
@Override
- public void onMetadataOrStateChanged(MediaMetadata metadata, int state) {
- if (state == PlaybackState.STATE_NONE) {
+ public void onPlaybackStateChanged(PlaybackState state) {
+ final int s = state != null ? state.getState() : PlaybackState.STATE_NONE;
+ if (s == PlaybackState.STATE_NONE) {
+ Log.d(TAG, "playback state change will trigger resumption, state=" + state);
clearControls();
makeInactive();
}
@@ -153,16 +173,17 @@ public class MediaControlPanel {
* Initialize a new control panel
* @param context
* @param parent
- * @param manager
* @param routeManager Manager used to listen for device change events.
* @param layoutId layout resource to use for this control panel
* @param actionIds resource IDs for action buttons in the layout
* @param foregroundExecutor foreground executor
* @param backgroundExecutor background executor, used for processing artwork
+ * @param activityStarter activity starter
*/
- public MediaControlPanel(Context context, ViewGroup parent, NotificationMediaManager manager,
+ public MediaControlPanel(Context context, ViewGroup parent,
@Nullable LocalMediaManager routeManager, @LayoutRes int layoutId, int[] actionIds,
- Executor foregroundExecutor, Executor backgroundExecutor) {
+ Executor foregroundExecutor, Executor backgroundExecutor,
+ ActivityStarter activityStarter) {
mContext = context;
LayoutInflater inflater = LayoutInflater.from(mContext);
mMediaNotifView = (LinearLayout) inflater.inflate(layoutId, parent, false);
@@ -172,11 +193,11 @@ public class MediaControlPanel {
// attach/detach of views instead of inflating them in the constructor, which would allow
// mStateListener to be unregistered in detach.
mMediaNotifView.addOnAttachStateChangeListener(mStateListener);
- mMediaManager = manager;
mLocalMediaManager = routeManager;
mActionIds = actionIds;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
+ mActivityStarter = activityStarter;
}
/**
@@ -198,107 +219,127 @@ public class MediaControlPanel {
/**
* Update the media panel view for the given media session
* @param token
- * @param icon
+ * @param iconDrawable
+ * @param largeIcon
* @param iconColor
* @param bgColor
* @param contentIntent
* @param appNameString
- * @param device
+ * @param key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
- int bgColor, PendingIntent contentIntent, String appNameString) {
- mToken = token;
+ public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
+ int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
+ String key) {
+ // Ensure that component names are updated if token has changed
+ if (mToken == null || !mToken.equals(token)) {
+ mToken = token;
+ mServiceComponent = null;
+ mCheckedForResumption = false;
+ }
+
mForegroundColor = iconColor;
mBackgroundColor = bgColor;
mController = new MediaController(mContext, mToken);
-
- MediaMetadata mediaMetadata = mController.getMetadata();
-
- // Try to find a receiver for the media button that matches this app
- PackageManager pm = mContext.getPackageManager();
- Intent it = new Intent(Intent.ACTION_MEDIA_BUTTON);
- List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
- if (info != null) {
- for (ResolveInfo inf : info) {
- if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
- mRecvComponent = inf.getComponentInfo().getComponentName();
+ mKey = key;
+
+ // Try to find a browser service component for this app
+ // TODO also check for a media button receiver intended for restarting (b/154127084)
+ // Only check if we haven't tried yet or the session token changed
+ final String pkgName = mController.getPackageName();
+ if (mServiceComponent == null && !mCheckedForResumption) {
+ Log.d(TAG, "Checking for service component");
+ PackageManager pm = mContext.getPackageManager();
+ Intent resumeIntent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
+ List<ResolveInfo> resumeInfo = pm.queryIntentServices(resumeIntent, 0);
+ if (resumeInfo != null) {
+ for (ResolveInfo inf : resumeInfo) {
+ if (inf.serviceInfo.packageName.equals(mController.getPackageName())) {
+ mBackgroundExecutor.execute(() ->
+ tryUpdateResumptionList(inf.getComponentInfo().getComponentName()));
+ break;
+ }
}
}
+ mCheckedForResumption = true;
}
mController.registerCallback(mSessionCallback);
- if (mediaMetadata == null) {
- Log.e(TAG, "Media metadata was null");
- return;
- }
-
- ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
- if (albumView != null) {
- // Resize art in a background thread
- mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
- }
mMediaNotifView.setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
// Click action
if (contentIntent != null) {
mMediaNotifView.setOnClickListener(v -> {
- try {
- contentIntent.send();
- // Also close shade
- mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled", e);
- }
+ mActivityStarter.postStartActivityDismissingKeyguard(contentIntent);
});
}
// App icon
ImageView appIcon = mMediaNotifView.findViewById(R.id.icon);
- Drawable iconDrawable = icon.loadDrawable(mContext);
iconDrawable.setTint(mForegroundColor);
appIcon.setImageDrawable(iconDrawable);
- // Song name
- TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
- String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
- titleText.setText(songName);
- titleText.setTextColor(mForegroundColor);
+ // Transfer chip
+ mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
+ if (mSeamless != null) {
+ if (mLocalMediaManager != null) {
+ mSeamless.setVisibility(View.VISIBLE);
+ updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
+ mSeamless.setOnClickListener(v -> {
+ final Intent intent = new Intent()
+ .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
+ .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
+ mController.getPackageName())
+ .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
+ mActivityStarter.startActivity(intent, false, true /* dismissShade */,
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ });
+ } else {
+ Log.d(TAG, "LocalMediaManager is null. Not binding output chip for pkg=" + pkgName);
+ }
+ }
+ PlaybackInfo playbackInfo = mController.getPlaybackInfo();
+ if (playbackInfo != null) {
+ mIsRemotePlayback = playbackInfo.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
+ } else {
+ Log.d(TAG, "PlaybackInfo was null. Defaulting to local playback.");
+ mIsRemotePlayback = false;
+ }
+
+ makeActive();
- // Not in mini player:
- // App title
+ // App title (not in mini player)
TextView appName = mMediaNotifView.findViewById(R.id.app_name);
if (appName != null) {
appName.setText(appNameString);
appName.setTextColor(mForegroundColor);
}
- // Artist name
+ MediaMetadata mediaMetadata = mController.getMetadata();
+ if (mediaMetadata == null) {
+ Log.e(TAG, "Media metadata was null");
+ return;
+ }
+
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
+ }
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ String songName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+ titleText.setText(songName);
+ titleText.setTextColor(mForegroundColor);
+
+ // Artist name (not in mini player)
TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
if (artistText != null) {
String artistName = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
artistText.setText(artistName);
artistText.setTextColor(mForegroundColor);
}
-
- // Transfer chip
- mSeamless = mMediaNotifView.findViewById(R.id.media_seamless);
- if (mSeamless != null && mLocalMediaManager != null) {
- mSeamless.setVisibility(View.VISIBLE);
- updateDevice(mLocalMediaManager.getCurrentConnectedDevice());
- ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
- mSeamless.setOnClickListener(v -> {
- final Intent intent = new Intent()
- .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
- .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
- mController.getPackageName())
- .putExtra(MediaOutputSliceConstants.KEY_MEDIA_SESSION_TOKEN, mToken);
- mActivityStarter.startActivity(intent, false, true /* dismissShade */,
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- });
- }
-
- makeActive();
}
/**
@@ -319,13 +360,24 @@ public class MediaControlPanel {
/**
* Get the name of the package associated with the current media controller
- * @return the package name
+ * @return the package name, or null if no controller
*/
public String getMediaPlayerPackage() {
+ if (mController == null) {
+ return null;
+ }
return mController.getPackageName();
}
/**
+ * Return the original notification's key
+ * @return The notification key
+ */
+ public String getKey() {
+ return mKey;
+ }
+
+ /**
* Check whether this player has an attached media session.
* @return whether there is a controller with a current media session.
*/
@@ -361,18 +413,97 @@ public class MediaControlPanel {
/**
* Process album art for layout
+ * @param description media description
+ * @param albumView view to hold the album art
+ */
+ protected void processAlbumArt(MediaDescription description, ImageView albumView) {
+ Bitmap albumArt = null;
+
+ // First try loading from URI
+ albumArt = loadBitmapFromUri(description.getIconUri());
+
+ // Then check bitmap
+ if (albumArt == null) {
+ albumArt = description.getIconBitmap();
+ }
+
+ processAlbumArtInternal(albumArt, albumView);
+ }
+
+ /**
+ * Process album art for layout
* @param metadata media metadata
+ * @param largeIcon from notification, checked as a fallback if metadata does not have art
* @param albumView view to hold the album art
*/
- private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
- Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
- float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+ private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
+ Bitmap albumArt = null;
+
+ // First look in URI fields
+ for (String field : ART_URIS) {
+ String uriString = metadata.getString(field);
+ if (!TextUtils.isEmpty(uriString)) {
+ albumArt = loadBitmapFromUri(Uri.parse(uriString));
+ if (albumArt != null) {
+ Log.d(TAG, "loaded art from " + field);
+ break;
+ }
+ }
+ }
+
+ // Then check bitmap field
+ if (albumArt == null) {
+ albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ }
+
+ // Finally try the notification's largeIcon
+ if (albumArt == null && largeIcon != null) {
+ albumArt = largeIcon.getBitmap();
+ }
+
+ processAlbumArtInternal(albumArt, albumView);
+ }
+
+ /**
+ * Load a bitmap from a URI
+ * @param uri
+ * @return bitmap, or null if couldn't be loaded
+ */
+ private Bitmap loadBitmapFromUri(Uri uri) {
+ // ImageDecoder requires a scheme of the following types
+ if (uri.getScheme() == null) {
+ return null;
+ }
+
+ if (!uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)
+ && !uri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)
+ && !uri.getScheme().equals(ContentResolver.SCHEME_FILE)) {
+ return null;
+ }
+
+ ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
+ try {
+ return ImageDecoder.decodeBitmap(source);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ /**
+ * Resize and crop the image if provided and update the control view
+ * @param albumArt Bitmap of art to display, or null to hide view
+ * @param albumView View that will hold the art
+ */
+ private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
+ // Resize
RoundedBitmapDrawable roundedDrawable = null;
if (albumArt != null) {
+ float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
int albumSize = (int) mContext.getResources().getDimension(
R.dimen.qs_media_album_size);
- Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
+ Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
roundedDrawable.setCornerRadius(radius);
} else {
@@ -419,7 +550,16 @@ public class MediaControlPanel {
TextView deviceName = mSeamless.findViewById(R.id.media_seamless_text);
deviceName.setTextColor(fgTintList);
- if (device != null) {
+ if (mIsRemotePlayback) {
+ mSeamless.setEnabled(false);
+ mSeamless.setAlpha(0.38f);
+ iconView.setImageResource(R.drawable.ic_hardware_speaker);
+ iconView.setVisibility(View.VISIBLE);
+ iconView.setImageTintList(fgTintList);
+ deviceName.setText(R.string.media_seamless_remote_device);
+ } else if (device != null) {
+ mSeamless.setEnabled(true);
+ mSeamless.setAlpha(1f);
Drawable icon = device.getIcon();
iconView.setVisibility(View.VISIBLE);
iconView.setImageTintList(fgTintList);
@@ -434,15 +574,33 @@ public class MediaControlPanel {
deviceName.setText(device.getName());
} else {
// Reset to default
+ Log.d(TAG, "device is null. Not binding output chip.");
+ mSeamless.setEnabled(true);
+ mSeamless.setAlpha(1f);
iconView.setVisibility(View.GONE);
deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
}
}
/**
- * Put controls into a resumption state
+ * Puts controls into a resumption state if possible, or calls removePlayer if no component was
+ * found that could resume playback
*/
public void clearControls() {
+ Log.d(TAG, "clearControls to resumption state package=" + getMediaPlayerPackage());
+ if (mServiceComponent == null) {
+ // If we don't have a way to resume, just remove the player altogether
+ Log.d(TAG, "Removing unresumable controls");
+ removePlayer();
+ return;
+ }
+ resetButtons();
+ }
+
+ /**
+ * Hide the media buttons and show only a restart button
+ */
+ protected void resetButtons() {
// Hide all the old buttons
for (int i = 0; i < mActionIds.length; i++) {
ImageButton thisBtn = mMediaNotifView.findViewById(mActionIds[i]);
@@ -455,27 +613,8 @@ public class MediaControlPanel {
ImageButton btn = mMediaNotifView.findViewById(mActionIds[0]);
btn.setOnClickListener(v -> {
Log.d(TAG, "Attempting to restart session");
- // Send a media button event to previously found receiver
- if (mRecvComponent != null) {
- Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- intent.setComponent(mRecvComponent);
- int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
- intent.putExtra(
- Intent.EXTRA_KEY_EVENT,
- new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
- mContext.sendBroadcast(intent);
- } else {
- // If we don't have a receiver, try relaunching the activity instead
- if (mController.getSessionActivity() != null) {
- try {
- mController.getSessionActivity().send();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Pending intent was canceled", e);
- }
- } else {
- Log.e(TAG, "No receiver or activity to restart");
- }
- }
+ QSMediaBrowser browser = new QSMediaBrowser(mContext, null, mServiceComponent);
+ browser.restart();
});
btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
@@ -485,7 +624,6 @@ public class MediaControlPanel {
private void makeActive() {
Assert.isMainThread();
if (!mIsRegistered) {
- mMediaManager.addCallback(mMediaListener);
if (mLocalMediaManager != null) {
mLocalMediaManager.registerCallback(mDeviceCallback);
mLocalMediaManager.startScan();
@@ -501,9 +639,71 @@ public class MediaControlPanel {
mLocalMediaManager.stopScan();
mLocalMediaManager.unregisterCallback(mDeviceCallback);
}
- mMediaManager.removeCallback(mMediaListener);
mIsRegistered = false;
}
}
+ /**
+ * Verify that we can connect to the given component with a MediaBrowser, and if so, add that
+ * component to the list of resumption components
+ */
+ private void tryUpdateResumptionList(ComponentName componentName) {
+ Log.d(TAG, "Testing if we can connect to " + componentName);
+ QSMediaBrowser.testConnection(mContext,
+ new QSMediaBrowser.Callback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "yes we can resume with " + componentName);
+ mServiceComponent = componentName;
+ updateResumptionList(componentName);
+ }
+
+ @Override
+ public void onError() {
+ Log.d(TAG, "Cannot resume with " + componentName);
+ mServiceComponent = null;
+ if (!hasMediaSession()) {
+ // If it's not active and we can't resume, remove
+ removePlayer();
+ }
+ }
+ },
+ componentName);
+ }
+
+ /**
+ * Add the component to the saved list of media browser services, checking for duplicates and
+ * removing older components that exceed the maximum limit
+ * @param componentName
+ */
+ private synchronized void updateResumptionList(ComponentName componentName) {
+ // Add to front of saved list
+ if (mSharedPrefs == null) {
+ mSharedPrefs = mContext.getSharedPreferences(MEDIA_PREFERENCES, 0);
+ }
+ String componentString = componentName.flattenToString();
+ String listString = mSharedPrefs.getString(MEDIA_PREFERENCE_KEY, null);
+ if (listString == null) {
+ listString = componentString;
+ } else {
+ String[] components = listString.split(QSMediaBrowser.DELIMITER);
+ StringBuilder updated = new StringBuilder(componentString);
+ int nBrowsers = 1;
+ for (int i = 0; i < components.length
+ && nBrowsers < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
+ if (componentString.equals(components[i])) {
+ continue;
+ }
+ updated.append(QSMediaBrowser.DELIMITER).append(components[i]);
+ nBrowsers++;
+ }
+ listString = updated.toString();
+ }
+ mSharedPrefs.edit().putString(MEDIA_PREFERENCE_KEY, listString).apply();
+ }
+
+ /**
+ * Called when a player can't be resumed to give it an opportunity to hide or remove itself
+ */
+ protected void removePlayer() { }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
index b7658a9f178d..51c157a56560 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarObserver.kt
@@ -61,6 +61,7 @@ class SeekBarObserver(view: View) : Observer<SeekBarViewModel.Progress> {
if (!data.enabled) {
seekBarView.setEnabled(false)
seekBarView.getThumb().setAlpha(0)
+ seekBarView.setProgress(0)
elapsedTimeView.setText("")
totalTimeView.setText("")
return
diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
index dd83e42cde2d..142510030a5f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt
@@ -77,13 +77,25 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) {
val seekAvailable = ((playbackState?.actions ?: 0L) and PlaybackState.ACTION_SEEK_TO) != 0L
val position = playbackState?.position?.toInt()
val duration = mediaMetadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.toInt()
- val enabled = if (duration != null && duration <= 0) false else true
+ val enabled = if (playbackState == null ||
+ playbackState?.getState() == PlaybackState.STATE_NONE ||
+ (duration != null && duration <= 0)) false else true
_data = Progress(enabled, seekAvailable, position, duration, color)
if (shouldPollPlaybackPosition()) {
checkPlaybackPosition()
}
}
+ /**
+ * Puts the seek bar into a resumption state.
+ *
+ * This should be called when the media session behind the controller has been destroyed.
+ */
+ @AnyThread
+ fun clearController() = bgExecutor.execute {
+ _data = _data.copy(enabled = false)
+ }
+
@AnyThread
private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({
val currentPosition = controller?.playbackState?.position?.toInt()
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
index a827f59ac7d1..f900f1e1db63 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java
@@ -60,6 +60,11 @@ public class SysUiState implements Dumpable {
mCallbacks.remove(callback);
}
+ /** Returns the current sysui state flags. */
+ public int getFlags() {
+ return mFlags;
+ }
+
/** Methods to this call can be chained together before calling {@link #commitUpdate(int)}. */
public SysUiState setFlag(int flag, boolean enabled) {
if (enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
index dba43430b490..f322489b8dc2 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipAnimationController.java
@@ -53,16 +53,23 @@ public class PipAnimationController {
public static final int TRANSITION_DIRECTION_SAME = 1;
public static final int TRANSITION_DIRECTION_TO_PIP = 2;
public static final int TRANSITION_DIRECTION_TO_FULLSCREEN = 3;
+ public static final int TRANSITION_DIRECTION_TO_SPLIT_SCREEN = 4;
@IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = {
TRANSITION_DIRECTION_NONE,
TRANSITION_DIRECTION_SAME,
TRANSITION_DIRECTION_TO_PIP,
- TRANSITION_DIRECTION_TO_FULLSCREEN
+ TRANSITION_DIRECTION_TO_FULLSCREEN,
+ TRANSITION_DIRECTION_TO_SPLIT_SCREEN
})
@Retention(RetentionPolicy.SOURCE)
public @interface TransitionDirection {}
+ public static boolean isOutPipDirection(@TransitionDirection int direction) {
+ return direction == TRANSITION_DIRECTION_TO_FULLSCREEN
+ || direction == TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
+ }
+
private final Interpolator mFastOutSlowInInterpolator;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
@@ -253,14 +260,13 @@ public class PipAnimationController {
}
boolean shouldApplyCornerRadius() {
- return mTransitionDirection != TRANSITION_DIRECTION_TO_FULLSCREEN;
+ return !isOutPipDirection(mTransitionDirection);
}
boolean inScaleTransition() {
if (mAnimationType != ANIM_TYPE_BOUNDS) return false;
final int direction = getTransitionDirection();
- return direction != TRANSITION_DIRECTION_TO_FULLSCREEN
- && direction != TRANSITION_DIRECTION_TO_PIP;
+ return !isOutPipDirection(direction) && direction != TRANSITION_DIRECTION_TO_PIP;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
index e24d29f1aedf..9d9e74abc38f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipBoundsHandler.java
@@ -126,6 +126,10 @@ public class PipBoundsHandler {
mCurrentMinSize = minEdgeSize;
}
+ protected float getAspectRatio() {
+ return mAspectRatio;
+ }
+
/**
* Sets both shelf visibility and its height if applicable.
* @return {@code true} if the internal shelf state is changed, {@code false} otherwise.
@@ -419,7 +423,7 @@ public class PipBoundsHandler {
/**
* Populates the bounds on the screen that the PIP can be visible in.
*/
- private void getInsetBounds(Rect outRect) {
+ protected void getInsetBounds(Rect outRect) {
try {
mWindowManager.getStableInsets(mContext.getDisplayId(), mTmpInsets);
outRect.set(mTmpInsets.left + mScreenEdgeInsets.x,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index a95d6b7a73cd..d38c481752c6 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -16,7 +16,6 @@
package com.android.systemui.pip;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.systemui.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -25,6 +24,8 @@ import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTI
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_SPLIT_SCREEN;
+import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -48,6 +49,7 @@ import android.window.WindowOrganizer;
import com.android.internal.os.SomeArgs;
import com.android.systemui.R;
import com.android.systemui.pip.phone.PipUpdateThread;
+import com.android.systemui.stackdivider.Divider;
import java.util.ArrayList;
import java.util.HashMap;
@@ -85,6 +87,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
private final int mEnterExitAnimationDuration;
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Map<IBinder, Rect> mBoundsToRestore = new HashMap<>();
+ private final Divider mSplitDivider;
// These callbacks are called on the update thread
private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
@@ -126,7 +129,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
};
@SuppressWarnings("unchecked")
- private Handler.Callback mUpdateCallbacks = (msg) -> {
+ private final Handler.Callback mUpdateCallbacks = (msg) -> {
SomeArgs args = (SomeArgs) msg.obj;
Consumer<Rect> updateBoundsCallback = (Consumer<Rect>) args.arg1;
switch (msg.what) {
@@ -189,7 +192,8 @@ public class PipTaskOrganizer extends TaskOrganizer {
mSurfaceControlTransactionFactory;
public PipTaskOrganizer(Context context, @NonNull PipBoundsHandler boundsHandler,
- @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper) {
+ @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
+ @Nullable Divider divider) {
mMainHandler = new Handler(Looper.getMainLooper());
mUpdateHandler = new Handler(PipUpdateThread.get().getLooper(), mUpdateCallbacks);
mPipBoundsHandler = boundsHandler;
@@ -198,6 +202,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
mSurfaceTransactionHelper = surfaceTransactionHelper;
mPipAnimationController = new PipAnimationController(context, surfaceTransactionHelper);
mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
+ mSplitDivider = divider;
}
public Handler getUpdateHandler() {
@@ -226,25 +231,26 @@ public class PipTaskOrganizer extends TaskOrganizer {
/**
* Dismiss PiP, this is done in two phases using {@link WindowContainerTransaction}
- * - setActivityWindowingMode to fullscreen at beginning of the transaction. without changing
- * the windowing mode of the Task itself. This makes sure the activity render it's fullscreen
+ * - setActivityWindowingMode to undefined at beginning of the transaction. without changing
+ * the windowing mode of the Task itself. This makes sure the activity render it's final
* configuration while the Task is still in PiP.
- * - setWindowingMode to fullscreen at the end of transition
+ * - setWindowingMode to undefined at the end of transition
* @param animationDurationMs duration in millisecond for the exiting PiP transition
*/
public void dismissPip(int animationDurationMs) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN);
+ wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
WindowOrganizer.applyTransaction(wct);
final Rect destinationBounds = mBoundsToRestore.remove(mToken.asBinder());
+ final int direction = syncWithSplitScreenBounds(destinationBounds)
+ ? TRANSITION_DIRECTION_TO_SPLIT_SCREEN : TRANSITION_DIRECTION_TO_FULLSCREEN;
scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
- TRANSITION_DIRECTION_TO_FULLSCREEN, animationDurationMs,
- null /* updateBoundsCallback */);
+ direction, animationDurationMs, null /* updateBoundsCallback */);
mInPip = false;
}
@Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo info) {
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
Objects.requireNonNull(info, "Requires RunningTaskInfo");
final Rect destinationBounds = mPipBoundsHandler.getDestinationBounds(
info.topActivity, getAspectRatioOrDefault(info.pictureInPictureParams),
@@ -253,7 +259,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
mTaskInfo = info;
mToken = mTaskInfo.token;
mInPip = true;
- mLeash = mToken.getLeash();
+ mLeash = leash;
final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
mBoundsToRestore.put(mToken.asBinder(), currentBounds);
@@ -279,24 +285,26 @@ public class PipTaskOrganizer extends TaskOrganizer {
* Meanwhile this callback is invoked whenever the task is removed. For instance:
* - as a result of removeStacksInWindowingModes from WM
* - activity itself is died
+ * Nevertheless, we simply update the internal state here as all the heavy lifting should
+ * have been done in WM.
*/
@Override
public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
- WindowContainerToken token = info.token;
+ if (!mInPip) {
+ return;
+ }
+ final WindowContainerToken token = info.token;
Objects.requireNonNull(token, "Requires valid WindowContainerToken");
if (token.asBinder() != mToken.asBinder()) {
Log.wtf(TAG, "Unrecognized token: " + token);
return;
}
- final Rect boundsToRestore = mBoundsToRestore.remove(token.asBinder());
- scheduleAnimateResizePip(mLastReportedBounds, boundsToRestore,
- TRANSITION_DIRECTION_TO_FULLSCREEN, mEnterExitAnimationDuration,
- null /* updateBoundsCallback */);
mInPip = false;
}
@Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
+ Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
final PictureInPictureParams newParams = info.pictureInPictureParams;
if (!shouldUpdateDestinationBounds(newParams)) {
Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
@@ -321,13 +329,19 @@ public class PipTaskOrganizer extends TaskOrganizer {
* @param destinationBoundsOut the current destination bounds will be populated to this param
*/
@SuppressWarnings("unchecked")
- public void onMovementBoundsChanged(Rect destinationBoundsOut,
+ public void onMovementBoundsChanged(Rect destinationBoundsOut, boolean fromRotation,
boolean fromImeAdjustment, boolean fromShelfAdjustment) {
final PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
- destinationBoundsOut.set(mLastReportedBounds);
if (animator == null || !animator.isRunning()
|| animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
+ if (mInPip && fromRotation) {
+ // this could happen if rotation finishes before the animation
+ mLastReportedBounds.set(destinationBoundsOut);
+ scheduleFinishResizePip(mLastReportedBounds);
+ } else if (!mLastReportedBounds.isEmpty()) {
+ destinationBoundsOut.set(mLastReportedBounds);
+ }
return;
}
@@ -375,7 +389,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
@PipAnimationController.TransitionDirection int direction, int durationMs,
Consumer<Rect> updateBoundsCallback) {
if (!mInPip) {
- // Ignore animation when we are no longer in PIP
+ // can be initiated in other component, ignore if we are no longer in PIP
return;
}
SomeArgs args = SomeArgs.obtain();
@@ -427,6 +441,10 @@ public class PipTaskOrganizer extends TaskOrganizer {
private void scheduleFinishResizePip(SurfaceControl.Transaction tx,
Rect destinationBounds, @PipAnimationController.TransitionDirection int direction,
Consumer<Rect> updateBoundsCallback) {
+ if (!mInPip) {
+ // can be initiated in other component, ignore if we are no longer in PIP
+ return;
+ }
SomeArgs args = SomeArgs.obtain();
args.arg1 = updateBoundsCallback;
args.arg2 = tx;
@@ -441,7 +459,7 @@ public class PipTaskOrganizer extends TaskOrganizer {
public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
Consumer<Rect> updateBoundsCallback) {
if (!mInPip) {
- // Ignore offsets when we are no longer in PIP
+ // can be initiated in other component, ignore if we are no longer in PIP
return;
}
SomeArgs args = SomeArgs.obtain();
@@ -508,14 +526,13 @@ public class PipTaskOrganizer extends TaskOrganizer {
mLastReportedBounds.set(destinationBounds);
final WindowContainerTransaction wct = new WindowContainerTransaction();
final Rect taskBounds;
- if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) {
+ if (isOutPipDirection(direction)) {
// If we are animating to fullscreen, then we need to reset the override bounds
- // on the task to ensure that the task "matches" the parent's bounds, this applies
- // also to the final windowing mode, which should be reset to undefined rather than
- // fullscreen.
- taskBounds = null;
- wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED)
- .setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ // on the task to ensure that the task "matches" the parent's bounds.
+ taskBounds = (direction == TRANSITION_DIRECTION_TO_FULLSCREEN)
+ ? null : destinationBounds;
+ // As for the final windowing mode, simply reset it to undefined.
+ wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
} else {
taskBounds = destinationBounds;
}
@@ -567,6 +584,24 @@ public class PipTaskOrganizer extends TaskOrganizer {
}
/**
+ * Sync with {@link #mSplitDivider} on destination bounds if PiP is going to split screen.
+ *
+ * @param destinationBoundsOut contain the updated destination bounds if applicable
+ * @return {@code true} if destinationBounds is altered for split screen
+ */
+ private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut) {
+ if (mSplitDivider == null || !mSplitDivider.inSplitMode()) {
+ // bail early if system is not in split screen mode
+ return false;
+ }
+ // PiP window will go to split-secondary mode instead of fullscreen, populates the
+ // split screen bounds here.
+ destinationBoundsOut.set(
+ mSplitDivider.getView().getNonMinimizedSplitScreenSecondaryBounds());
+ return true;
+ }
+
+ /**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
public interface PipTransitionCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index a2667d9a4c74..a86a884c8016 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -19,7 +19,7 @@ package com.android.systemui.pip.phone;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
+import static com.android.systemui.pip.PipAnimationController.isOutPipDirection;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -53,6 +53,7 @@ import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.wm.DisplayChangeController;
@@ -97,8 +98,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
final boolean changed = mPipBoundsHandler.onDisplayRotationChanged(mTmpNormalBounds,
displayId, fromRotation, toRotation, t);
if (changed) {
- updateMovementBounds(mTmpNormalBounds, false /* fromImeAdjustment */,
- false /* fromShelfAdjustment */);
+ updateMovementBounds(mTmpNormalBounds, true /* fromRotation */,
+ false /* fromImeAdjustment */, false /* fromShelfAdjustment */);
}
};
@@ -134,8 +135,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask) {
- if (task.configuration.windowConfiguration.getWindowingMode()
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
return;
}
@@ -163,7 +164,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onMovementBoundsChanged(boolean fromImeAdjustment) {
mHandler.post(() -> updateMovementBounds(null /* toBounds */,
- fromImeAdjustment, false /* fromShelfAdjustment */));
+ false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */));
}
@Override
@@ -199,7 +200,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
DeviceConfigProxy deviceConfig,
PipBoundsHandler pipBoundsHandler,
PipSnapAlgorithm pipSnapAlgorithm,
- PipSurfaceTransactionHelper surfaceTransactionHelper) {
+ PipSurfaceTransactionHelper surfaceTransactionHelper,
+ Divider divider) {
mContext = context;
mActivityManager = ActivityManager.getService();
@@ -214,7 +216,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
mPipBoundsHandler = pipBoundsHandler;
mPipTaskOrganizer = new PipTaskOrganizer(context, pipBoundsHandler,
- surfaceTransactionHelper);
+ surfaceTransactionHelper, divider);
mPipTaskOrganizer.registerPipTransitionCallback(this);
mInputConsumerController = InputConsumerController.getPipInputConsumer();
mMediaController = new PipMediaController(context, mActivityManager, broadcastDispatcher);
@@ -294,7 +296,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
if (changed) {
mTouchHandler.onShelfVisibilityChanged(visible, height);
updateMovementBounds(mPipBoundsHandler.getLastDestinationBounds(),
- false /* fromImeAdjustment */, true /* fromShelfAdjustment */);
+ false /* fromRotation */, false /* fromImeAdjustment */,
+ true /* fromShelfAdjustment */);
}
});
}
@@ -311,7 +314,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onPipTransitionStarted(ComponentName activity, int direction) {
- if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) {
+ if (isOutPipDirection(direction)) {
// On phones, the expansion animation that happens on pip tap before restoring
// to fullscreen makes it so that the bounds received here are the expanded
// bounds. We want to restore to the unexpanded bounds when re-entering pip,
@@ -338,22 +341,22 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Override
public void onPipTransitionFinished(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled();
+ onPipTransitionFinishedOrCanceled(direction);
}
@Override
public void onPipTransitionCanceled(ComponentName activity, int direction) {
- onPipTransitionFinishedOrCanceled();
+ onPipTransitionFinishedOrCanceled(direction);
}
- private void onPipTransitionFinishedOrCanceled() {
+ private void onPipTransitionFinishedOrCanceled(int direction) {
// Re-enable touches after the animation completes
mTouchHandler.setTouchEnabled(true);
- mTouchHandler.onPinnedStackAnimationEnded();
+ mTouchHandler.onPinnedStackAnimationEnded(direction);
mMenuController.onPinnedStackAnimationEnded();
}
- private void updateMovementBounds(@Nullable Rect toBounds,
+ private void updateMovementBounds(@Nullable Rect toBounds, boolean fromRotation,
boolean fromImeAdjustment, boolean fromShelfAdjustment) {
// Populate inset / normal bounds and DisplayInfo from mPipBoundsHandler before
// passing to mTouchHandler/mPipTaskOrganizer
@@ -361,7 +364,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
mPipBoundsHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
outBounds, mTmpDisplayInfo);
// mTouchHandler would rely on the bounds populated from mPipTaskOrganizer
- mPipTaskOrganizer.onMovementBoundsChanged(outBounds,
+ mPipTaskOrganizer.onMovementBoundsChanged(outBounds, fromRotation,
fromImeAdjustment, fromShelfAdjustment);
mTouchHandler.onMovementBoundsChanged(mTmpInsetBounds, mTmpNormalBounds,
outBounds, fromImeAdjustment, fromShelfAdjustment,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index 2b9b1716cb18..ec15dd16f46e 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -54,6 +54,7 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
@@ -129,9 +130,7 @@ public class PipMenuActivity extends Activity {
}
};
- private Handler mHandler = new Handler();
- private Messenger mToControllerMessenger;
- private Messenger mMessenger = new Messenger(new Handler() {
+ private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -174,7 +173,9 @@ public class PipMenuActivity extends Activity {
}
}
}
- });
+ };
+ private Messenger mToControllerMessenger;
+ private Messenger mMessenger = new Messenger(mHandler);
private final Runnable mFinishRunnable = new Runnable() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index d660b670446b..61ed40d5d782 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -30,6 +30,7 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
@@ -122,7 +123,7 @@ public class PipMenuActivityController {
private boolean mStartActivityRequested;
private long mStartActivityRequestedTime;
private Messenger mToActivityMessenger;
- private Handler mHandler = new Handler() {
+ private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -133,15 +134,15 @@ public class PipMenuActivityController {
break;
}
case MESSAGE_EXPAND_PIP: {
- mListeners.forEach(l -> l.onPipExpand());
+ mListeners.forEach(Listener::onPipExpand);
break;
}
case MESSAGE_DISMISS_PIP: {
- mListeners.forEach(l -> l.onPipDismiss());
+ mListeners.forEach(Listener::onPipDismiss);
break;
}
case MESSAGE_SHOW_MENU: {
- mListeners.forEach(l -> l.onPipShowMenu());
+ mListeners.forEach(Listener::onPipShowMenu);
break;
}
case MESSAGE_UPDATE_ACTIVITY_CALLBACK: {
@@ -259,6 +260,8 @@ public class PipMenuActivityController {
if (DEBUG) {
Log.d(TAG, "showMenu() state=" + menuState
+ " hasActivity=" + (mToActivityMessenger != null)
+ + " allowMenuTimeout=" + allowMenuTimeout
+ + " willResizeMenu=" + willResizeMenu
+ " callers=\n" + Debug.getCallers(5, " "));
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
index a8a5d896537f..00f693de8f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -168,6 +168,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
void synchronizePinnedStackBounds() {
cancelAnimations();
mBounds.set(mPipTaskOrganizer.getLastReportedBounds());
+ mFloatingContentCoordinator.onContentMoved(this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
index 0b076559ae36..d80f18a983ee 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipResizeGestureHandler.java
@@ -56,7 +56,6 @@ public class PipResizeGestureHandler {
private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
private final PipBoundsHandler mPipBoundsHandler;
- private final PipTouchHandler mPipTouchHandler;
private final PipMotionHelper mMotionHelper;
private final int mDisplayId;
private final Executor mMainExecutor;
@@ -70,10 +69,10 @@ public class PipResizeGestureHandler {
private final Rect mTmpBounds = new Rect();
private final int mDelta;
- private boolean mAllowGesture = false;
+ private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
- private boolean mEnablePipResize;
+ private boolean mEnableUserResize;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -82,21 +81,20 @@ public class PipResizeGestureHandler {
private int mCtrlType;
public PipResizeGestureHandler(Context context, PipBoundsHandler pipBoundsHandler,
- PipTouchHandler pipTouchHandler, PipMotionHelper motionHelper,
- DeviceConfigProxy deviceConfig, PipTaskOrganizer pipTaskOrganizer) {
+ PipMotionHelper motionHelper, DeviceConfigProxy deviceConfig,
+ PipTaskOrganizer pipTaskOrganizer) {
final Resources res = context.getResources();
context.getDisplay().getMetrics(mDisplayMetrics);
mDisplayId = context.getDisplayId();
mMainExecutor = context.getMainExecutor();
mPipBoundsHandler = pipBoundsHandler;
- mPipTouchHandler = pipTouchHandler;
mMotionHelper = motionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
context.getDisplay().getRealSize(mMaxSize);
mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
- mEnablePipResize = DeviceConfig.getBoolean(
+ mEnableUserResize = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_SYSTEMUI,
PIP_USER_RESIZE,
/* defaultValue = */ true);
@@ -105,7 +103,7 @@ public class PipResizeGestureHandler {
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
if (properties.getKeyset().contains(PIP_USER_RESIZE)) {
- mEnablePipResize = properties.getBoolean(
+ mEnableUserResize = properties.getBoolean(
PIP_USER_RESIZE, /* defaultValue = */ true);
}
}
@@ -134,7 +132,7 @@ public class PipResizeGestureHandler {
}
private void updateIsEnabled() {
- boolean isEnabled = mIsAttached && mEnablePipResize;
+ boolean isEnabled = mIsAttached && mEnableUserResize;
if (isEnabled == mIsEnabled) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index ddba9eab7766..f5c83c1fffd7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -16,6 +16,7 @@
package com.android.systemui.pip.phone;
+import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_CLOSE;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_FULL;
import static com.android.systemui.pip.phone.PipMenuActivityController.MENU_STATE_NONE;
@@ -56,6 +57,7 @@ import androidx.dynamicanimation.animation.SpringForce;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.logging.MetricsLoggerWrapper;
import com.android.systemui.R;
+import com.android.systemui.pip.PipAnimationController;
import com.android.systemui.pip.PipBoundsHandler;
import com.android.systemui.pip.PipSnapAlgorithm;
import com.android.systemui.pip.PipTaskOrganizer;
@@ -229,7 +231,7 @@ public class PipTouchHandler {
mMotionHelper = new PipMotionHelper(mContext, activityTaskManager, pipTaskOrganizer,
mMenuController, mSnapAlgorithm, mFlingAnimationUtils, floatingContentCoordinator);
mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsHandler, this, mMotionHelper,
+ new PipResizeGestureHandler(context, pipBoundsHandler, mMotionHelper,
deviceConfig, pipTaskOrganizer);
mTouchState = new PipTouchState(ViewConfiguration.get(context), mHandler,
() -> mMenuController.showMenu(MENU_STATE_FULL, mMotionHelper.getBounds(),
@@ -266,6 +268,10 @@ public class PipTouchHandler {
mMagnetizedPip = mMotionHelper.getMagnetizedPip();
mMagneticTarget = mMagnetizedPip.addTarget(mTargetView, 0);
+
+ // Set the magnetic field radius equal to twice the size of the target.
+ mMagneticTarget.setMagneticFieldRadiusPx(targetSize * 2);
+
mMagnetizedPip.setPhysicsAnimatorUpdateListener(mMotionHelper.mResizePipUpdateListener);
mMagnetizedPip.setMagnetListener(new MagnetizedObject.MagnetListener() {
@Override
@@ -321,7 +327,7 @@ public class PipTouchHandler {
}
public void onActivityPinned() {
- createDismissTargetMaybe();
+ createOrUpdateDismissTarget();
mShowPipMenuOnAnimationEnd = true;
mPipResizeGestureHandler.onActivityPinned();
@@ -339,11 +345,16 @@ public class PipTouchHandler {
mPipResizeGestureHandler.onActivityUnpinned();
}
- public void onPinnedStackAnimationEnded() {
+ public void onPinnedStackAnimationEnded(
+ @PipAnimationController.TransitionDirection int direction) {
// Always synchronize the motion helper bounds once PiP animations finish
mMotionHelper.synchronizePinnedStackBounds();
updateMovementBounds();
- mResizedBounds.set(mMotionHelper.getBounds());
+ if (direction == TRANSITION_DIRECTION_TO_PIP) {
+ // updates mResizedBounds only if it's an entering PiP animation
+ // mResized should be otherwise updated in setMenuState.
+ mResizedBounds.set(mMotionHelper.getBounds());
+ }
if (mShowPipMenuOnAnimationEnd) {
mMenuController.showMenu(MENU_STATE_CLOSE, mMotionHelper.getBounds(),
@@ -357,8 +368,7 @@ public class PipTouchHandler {
mMotionHelper.synchronizePinnedStackBounds();
// Recreate the dismiss target for the new orientation.
- cleanUpDismissTarget();
- createDismissTargetMaybe();
+ createOrUpdateDismissTarget();
}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -454,43 +464,57 @@ public class PipTouchHandler {
}
/** Adds the magnetic target view to the WindowManager so it's ready to be animated in. */
- private void createDismissTargetMaybe() {
+ private void createOrUpdateDismissTarget() {
if (!mTargetViewContainer.isAttachedToWindow()) {
mHandler.removeCallbacks(mShowTargetAction);
mMagneticTargetAnimator.cancel();
- final Point windowSize = new Point();
- mWindowManager.getDefaultDisplay().getRealSize(windowSize);
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT,
- mDismissAreaHeight,
- 0, windowSize.y - mDismissAreaHeight,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
- | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("pip-dismiss-overlay");
- lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
- lp.setFitInsetsTypes(0 /* types */);
-
mTargetViewContainer.setVisibility(View.INVISIBLE);
- mWindowManager.addView(mTargetViewContainer, lp);
+
+ try {
+ mWindowManager.addView(mTargetViewContainer, getDismissTargetLayoutParams());
+ } catch (IllegalStateException e) {
+ // This shouldn't happen, but if the target is already added, just update its layout
+ // params.
+ mWindowManager.updateViewLayout(
+ mTargetViewContainer, getDismissTargetLayoutParams());
+ }
+ } else {
+ mWindowManager.updateViewLayout(mTargetViewContainer, getDismissTargetLayoutParams());
}
}
+ /** Returns layout params for the dismiss target, using the latest display metrics. */
+ private WindowManager.LayoutParams getDismissTargetLayoutParams() {
+ final Point windowSize = new Point();
+ mWindowManager.getDefaultDisplay().getRealSize(windowSize);
+
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ mDismissAreaHeight,
+ 0, windowSize.y - mDismissAreaHeight,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSLUCENT);
+
+ lp.setTitle("pip-dismiss-overlay");
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setFitInsetsTypes(0 /* types */);
+
+ return lp;
+ }
+
/** Makes the dismiss target visible and animates it in, if it isn't already visible. */
private void showDismissTargetMaybe() {
- createDismissTargetMaybe();
+ createOrUpdateDismissTarget();
if (mTargetViewContainer.getVisibility() != View.VISIBLE) {
mTargetView.setTranslationY(mTargetViewContainer.getHeight());
mTargetViewContainer.setVisibility(View.VISIBLE);
- // Set the magnetic field radius to half of PIP's width.
- mMagneticTarget.setMagneticFieldRadiusPx(mMotionHelper.getBounds().width());
-
// Cancel in case we were in the middle of animating it out.
mMagneticTargetAnimator.cancel();
mMagneticTargetAnimator
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index 3a2d786cebe4..fae8af4f575a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -57,6 +57,7 @@ import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.PinnedStackListenerForwarder.PinnedStackListener;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.WindowManagerWrapper;
+import com.android.systemui.stackdivider.Divider;
import java.util.ArrayList;
import java.util.List;
@@ -232,7 +233,8 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
@Inject
public PipManager(Context context, BroadcastDispatcher broadcastDispatcher,
PipBoundsHandler pipBoundsHandler,
- PipSurfaceTransactionHelper surfaceTransactionHelper) {
+ PipSurfaceTransactionHelper surfaceTransactionHelper,
+ Divider divider) {
if (mInitialized) {
return;
}
@@ -249,7 +251,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
mResizeAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipResizeAnimationDuration);
mPipTaskOrganizer = new PipTaskOrganizer(mContext, mPipBoundsHandler,
- surfaceTransactionHelper);
+ surfaceTransactionHelper, divider);
mPipTaskOrganizer.registerPipTransitionCallback(this);
mActivityTaskManager = ActivityTaskManager.getService();
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
@@ -706,15 +708,15 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
mActiveMediaSessionListener, null);
updateMediaController(mMediaSessionManager.getActiveSessions(null));
for (int i = mListeners.size() - 1; i >= 0; i--) {
- mListeners.get(i).onPipEntered();
+ mListeners.get(i).onPipEntered(packageName);
}
updatePipVisibility(true);
}
@Override
public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible,
- boolean clearedTask) {
- if (task.configuration.windowConfiguration.getWindowingMode()
+ boolean clearedTask, boolean wasVisible) {
+ if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_PINNED) {
return;
}
@@ -756,7 +758,7 @@ public class PipManager implements BasePipManager, PipTaskOrganizer.PipTransitio
* because there's no guarantee for the PIP manager be return relavent information
* correctly. (e.g. {@link isPipShown}).
*/
- void onPipEntered();
+ void onPipEntered(String packageName);
/** Invoked when a PIPed activity is closed. */
void onPipActivityClosed();
/** Invoked when the PIP menu gets shown. */
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
index c7e77ccfa488..158be45e0adb 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipMenuActivity.java
@@ -137,8 +137,8 @@ public class PipMenuActivity extends Activity implements PipManager.Listener {
}
@Override
- public void onPipEntered() {
- if (DEBUG) Log.d(TAG, "onPipEntered()");
+ public void onPipEntered(String packageName) {
+ if (DEBUG) Log.d(TAG, "onPipEntered(), packageName=" + packageName);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
index b01c2f4eb5fb..30ec29683942 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipNotification.java
@@ -23,6 +23,8 @@ import android.content.BroadcastReceiver;
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.ParceledListSlice;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -50,6 +52,8 @@ public class PipNotification {
private static final String ACTION_MENU = "PipNotification.menu";
private static final String ACTION_CLOSE = "PipNotification.close";
+ private final PackageManager mPackageManager;
+
private final PipManager mPipManager;
private final NotificationManager mNotificationManager;
@@ -59,13 +63,16 @@ public class PipNotification {
private String mDefaultTitle;
private int mDefaultIconResId;
+ /** Package name for the application that owns PiP window. */
+ private String mPackageName;
private boolean mNotified;
- private String mTitle;
+ private String mMediaTitle;
private Bitmap mArt;
private PipManager.Listener mPipListener = new PipManager.Listener() {
@Override
- public void onPipEntered() {
+ public void onPipEntered(String packageName) {
+ mPackageName = packageName;
updateMediaControllerMetadata();
notifyPipNotification();
}
@@ -73,6 +80,7 @@ public class PipNotification {
@Override
public void onPipActivityClosed() {
dismissPipNotification();
+ mPackageName = null;
}
@Override
@@ -88,6 +96,7 @@ public class PipNotification {
@Override
public void onMoveToFullscreen() {
dismissPipNotification();
+ mPackageName = null;
}
@Override
@@ -146,6 +155,8 @@ public class PipNotification {
public PipNotification(Context context, BroadcastDispatcher broadcastDispatcher,
PipManager pipManager) {
+ mPackageManager = context.getPackageManager();
+
mNotificationManager = (NotificationManager) context.getSystemService(
Context.NOTIFICATION_SERVICE);
@@ -188,7 +199,7 @@ public class PipNotification {
.setShowWhen(true)
.setWhen(System.currentTimeMillis())
.setSmallIcon(mDefaultIconResId)
- .setContentTitle(!TextUtils.isEmpty(mTitle) ? mTitle : mDefaultTitle);
+ .setContentTitle(getNotificationTitle());
if (mArt != null) {
mNotificationBuilder.setStyle(new Notification.BigPictureStyle()
.bigPicture(mArt));
@@ -220,14 +231,36 @@ public class PipNotification {
}
}
}
- if (!TextUtils.equals(title, mTitle) || art != mArt) {
- mTitle = title;
+ if (!TextUtils.equals(title, mMediaTitle) || art != mArt) {
+ mMediaTitle = title;
mArt = art;
return true;
}
return false;
}
+ private String getNotificationTitle() {
+ if (!TextUtils.isEmpty(mMediaTitle)) {
+ return mMediaTitle;
+ }
+
+ final String applicationTitle = getApplicationLabel(mPackageName);
+ if (!TextUtils.isEmpty(applicationTitle)) {
+ return applicationTitle;
+ }
+
+ return mDefaultTitle;
+ }
+
+ private String getApplicationLabel(String packageName) {
+ try {
+ final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, 0);
+ return mPackageManager.getApplicationLabel(appInfo).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
private static PendingIntent createPendingIntent(Context context, String action) {
return PendingIntent.getBroadcast(context, 0,
new Intent(action), PendingIntent.FLAG_CANCEL_CURRENT);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index 448531a132df..c5ae3ab2c9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -20,10 +20,14 @@ import android.content.Context
import android.content.res.Configuration
import android.view.View
import android.view.ViewGroup
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
import com.android.systemui.qs.TileLayout.exactly
-class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTileLayout {
+class DoubleLineTileLayout(
+ context: Context,
+ private val uiEventLogger: UiEventLogger
+) : ViewGroup(context), QSPanel.QSTileLayout {
companion object {
private const val NUM_LINES = 2
@@ -86,6 +90,13 @@ class DoubleLineTileLayout(context: Context) : ViewGroup(context), QSPanel.QSTil
for (record in mRecords) {
record.tile.setListening(this, listening)
}
+ if (listening) {
+ for (i in 0 until numVisibleTiles) {
+ val tile = mRecords[i].tile
+ uiEventLogger.logWithInstanceId(
+ QSEvent.QQS_TILE_VISIBLE, 0, tile.metricsSpec, tile.instanceId)
+ }
+ }
}
override fun getNumVisibleTiles() = tilesToShow
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index cd737217b84a..b157f4b3c5ed 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -22,7 +22,9 @@ import android.widget.Scroller;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
+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;
@@ -63,7 +65,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
private int mLayoutDirection;
private int mHorizontalClipBound;
private final Rect mClippingRect;
- private int mLastMaxHeight = -1;
+ private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
public PagedTileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -75,6 +77,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
mLayoutDirection = getLayoutDirection();
mClippingRect = new Rect();
}
+ private int mLastMaxHeight = -1;
public void saveInstanceState(Bundle outState) {
outState.putInt(CURRENT_PAGE, getCurrentItem());
@@ -126,6 +129,15 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
return page;
}
+ // This will dump to the ui log all the tiles that are visible in this page
+ private void logVisibleTiles(TilePage page) {
+ for (int i = 0; i < page.mRecords.size(); i++) {
+ QSTile t = page.mRecords.get(i).tile;
+ mUiEventLogger.logWithInstanceId(QSEvent.QS_TILE_VISIBLE, 0, t.getMetricsSpec(),
+ t.getInstanceId());
+ }
+ }
+
@Override
public void setListening(boolean listening) {
if (mListening == listening) return;
@@ -218,7 +230,11 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
int currentItem = getCurrentPageNumber();
for (int i = 0; i < mPages.size(); i++) {
- mPages.get(i).setSelected(i == currentItem ? selected : false);
+ TilePage page = mPages.get(i);
+ page.setSelected(i == currentItem ? selected : false);
+ if (page.isSelected()) {
+ logVisibleTiles(page);
+ }
}
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
}
@@ -419,6 +435,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
mPageListener.onPageChanged(isLayoutRtl() ? position == mPages.size() - 1
: position == 0);
}
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
index 17aaff1f7383..ee3b499edfb7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java
@@ -39,6 +39,7 @@ import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dependency;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
@@ -53,6 +54,7 @@ public class QSDetail extends LinearLayout {
private static final long FADE_DURATION = 300;
private final SparseArray<View> mDetailViews = new SparseArray<>();
+ private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
private ViewGroup mDetailContent;
protected TextView mDetailSettingsButton;
@@ -205,6 +207,7 @@ public class QSDetail extends LinearLayout {
mDetailContent.addView(detailView);
mDetailViews.put(viewCacheIndex, detailView);
Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
+ mUiEventLogger.log(adapter.openDetailEvent());
announceForAccessibility(mContext.getString(
R.string.accessibility_quick_settings_detail,
adapter.getTitle()));
@@ -214,6 +217,7 @@ public class QSDetail extends LinearLayout {
} else {
if (mDetailAdapter != null) {
Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
+ mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
}
mClosingDetail = true;
mDetailAdapter = null;
@@ -249,6 +253,7 @@ public class QSDetail extends LinearLayout {
mDetailSettingsButton.setOnClickListener(v -> {
Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
adapter.getMetricsCategory());
+ mUiEventLogger.log(adapter.moreSettingsEvent());
Dependency.get(ActivityStarter.class)
.postStartActivityDismissingKeyguard(settingsIntent, 0);
});
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
new file mode 100644
index 000000000000..54e8a2be0d2a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt
@@ -0,0 +1,124 @@
+/*
+ * 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 com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLoggerImpl
+import com.android.internal.logging.testing.UiEventLoggerFake
+
+object QSEvents {
+
+ var qsUiEventsLogger: UiEventLogger = UiEventLoggerImpl()
+ private set
+
+ fun setLoggerForTesting(): UiEventLoggerFake {
+ return UiEventLoggerFake().also {
+ qsUiEventsLogger = it
+ }
+ }
+
+ fun resetLogger() {
+ qsUiEventsLogger = UiEventLoggerImpl()
+ }
+}
+
+enum class QSEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Tile clicked. It has an instance id and a spec (or packageName)")
+ QS_ACTION_CLICK(387),
+
+ @UiEvent(doc = "Tile secondary button clicked. " +
+ "It has an instance id and a spec (or packageName)")
+ QS_ACTION_SECONDARY_CLICK(388),
+
+ @UiEvent(doc = "Tile long clicked. It has an instance id and a spec (or packageName)")
+ QS_ACTION_LONG_PRESS(389),
+
+ @UiEvent(doc = "Quick Settings panel expanded")
+ QS_PANEL_EXPANDED(390),
+
+ @UiEvent(doc = "Quick Settings panel collapsed")
+ QS_PANEL_COLLAPSED(391),
+
+ @UiEvent(doc = "Tile visible in Quick Settings panel. The tile may be in a different page. " +
+ "It has an instance id and a spec (or packageName)")
+ QS_TILE_VISIBLE(392),
+
+ @UiEvent(doc = "Quick Quick Settings panel expanded")
+ QQS_PANEL_EXPANDED(393),
+
+ @UiEvent(doc = "Quick Quick Settings panel collapsed")
+ QQS_PANEL_COLLAPSED(394),
+
+ @UiEvent(doc = "Tile visible in Quick Quick Settings panel. " +
+ "It has an instance id and a spec (or packageName)")
+ QQS_TILE_VISIBLE(395);
+
+ override fun getId() = _id
+}
+
+enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "Tile removed from current tiles")
+ QS_EDIT_REMOVE(210),
+
+ @UiEvent(doc = "Tile added to current tiles")
+ QS_EDIT_ADD(211),
+
+ @UiEvent(doc = "Tile moved")
+ QS_EDIT_MOVE(212),
+
+ @UiEvent(doc = "QS customizer open")
+ QS_EDIT_OPEN(213),
+
+ @UiEvent(doc = "QS customizer closed")
+ QS_EDIT_CLOSED(214),
+
+ @UiEvent(doc = "QS tiles reset")
+ QS_EDIT_RESET(215);
+
+ override fun getId() = _id
+}
+
+enum class QSDndEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "TODO(beverlyt)")
+ QS_DND_CONDITION_SELECT(420),
+
+ @UiEvent(doc = "TODO(beverlyt)")
+ QS_DND_TIME_UP(422),
+
+ @UiEvent(doc = "TODO(beverlyt)")
+ QS_DND_TIME_DOWN(423);
+
+ override fun getId() = _id
+}
+
+enum class QSUserSwitcherEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The current user has been switched in the detail panel")
+ QS_USER_SWITCH(424),
+
+ @UiEvent(doc = "User switcher QS detail panel open")
+ QS_USER_DETAIL_OPEN(425),
+
+ @UiEvent(doc = "User switcher QS detail panel closed")
+ QS_USER_DETAIL_CLOSE(426),
+
+ @UiEvent(doc = "User switcher QS detail panel more settings pressed")
+ QS_USER_MORE_SETTINGS(427);
+
+ override fun getId() = _id
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
index ece1ce8bb4d0..1e8c4d86da36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java
@@ -16,6 +16,8 @@ package com.android.systemui.qs;
import android.content.Context;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.external.TileServices;
import com.android.systemui.qs.logging.QSLogger;
@@ -30,6 +32,7 @@ public interface QSHost {
Context getContext();
Context getUserContext();
QSLogger getQSLogger();
+ UiEventLogger getUiEventLogger();
Collection<QSTile> getTiles();
void addCallback(Callback callback);
void removeCallback(Callback callback);
@@ -39,6 +42,8 @@ public interface QSHost {
int indexOf(String tileSpec);
+ InstanceId getNewInstanceId();
+
interface Callback {
void onTilesChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
new file mode 100644
index 000000000000..9e532868427f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaBrowser.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Media browser for managing resumption in QS media controls
+ */
+public class QSMediaBrowser {
+
+ /** Maximum number of controls to show on boot */
+ public static final int MAX_RESUMPTION_CONTROLS = 5;
+
+ /** Delimiter for saved component names */
+ public static final String DELIMITER = ":";
+
+ private static final String TAG = "QSMediaBrowser";
+ private final Context mContext;
+ private final Callback mCallback;
+ private MediaBrowser mMediaBrowser;
+ private ComponentName mComponentName;
+
+ /**
+ * Initialize a new media browser
+ * @param context the context
+ * @param callback used to report media items found
+ * @param componentName Component name of the MediaBrowserService this browser will connect to
+ */
+ public QSMediaBrowser(Context context, Callback callback, ComponentName componentName) {
+ mContext = context;
+ mCallback = callback;
+ mComponentName = componentName;
+
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ mMediaBrowser = new MediaBrowser(mContext,
+ mComponentName,
+ mConnectionCallback,
+ rootHints);
+ }
+
+ /**
+ * Connects to the MediaBrowserService and looks for valid media. If a media item is returned
+ * by the service, QSMediaBrowser.Callback#addTrack will be called with its MediaDescription
+ */
+ public void findRecentMedia() {
+ Log.d(TAG, "Connecting to " + mComponentName);
+ mMediaBrowser.connect();
+ }
+
+ private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+ new MediaBrowser.SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(String parentId,
+ List<MediaBrowser.MediaItem> children) {
+ if (children.size() == 0) {
+ Log.e(TAG, "No children found for " + mComponentName);
+ return;
+ }
+ // We ask apps to return a playable item as the first child when sending
+ // a request with EXTRA_RECENT; if they don't, no resume controls
+ MediaBrowser.MediaItem child = children.get(0);
+ MediaDescription desc = child.getDescription();
+ if (child.isPlayable()) {
+ mCallback.addTrack(desc, mMediaBrowser.getServiceComponent(), QSMediaBrowser.this);
+ } else {
+ Log.e(TAG, "Child found but not playable for " + mComponentName);
+ }
+ mMediaBrowser.disconnect();
+ }
+
+ @Override
+ public void onError(String parentId) {
+ Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId);
+ mMediaBrowser.disconnect();
+ }
+
+ @Override
+ public void onError(String parentId, Bundle options) {
+ Log.e(TAG, "Subscribe error for " + mComponentName + ": " + parentId
+ + ", options: " + options);
+ mMediaBrowser.disconnect();
+ }
+ };
+
+ private final MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ /**
+ * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed.
+ * For resumption controls, apps are expected to return a playable media item as the first
+ * child. If there are no children or it isn't playable it will be ignored.
+ */
+ @Override
+ public void onConnected() {
+ if (mMediaBrowser.isConnected()) {
+ mCallback.onConnected();
+ Log.d(TAG, "Service connected for " + mComponentName);
+ String root = mMediaBrowser.getRoot();
+ mMediaBrowser.subscribe(root, mSubscriptionCallback);
+ }
+ }
+
+ /**
+ * Invoked when the client is disconnected from the media browser.
+ */
+ @Override
+ public void onConnectionSuspended() {
+ Log.d(TAG, "Connection suspended for " + mComponentName);
+ }
+
+ /**
+ * Invoked when the connection to the media browser failed.
+ */
+ @Override
+ public void onConnectionFailed() {
+ Log.e(TAG, "Connection failed for " + mComponentName);
+ mCallback.onError();
+ }
+ };
+
+ /**
+ * Connects to the MediaBrowserService and starts playback
+ */
+ public void restart() {
+ if (mMediaBrowser.isConnected()) {
+ mMediaBrowser.disconnect();
+ }
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ mMediaBrowser = new MediaBrowser(mContext, mComponentName,
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "Connected for restart " + mMediaBrowser.isConnected());
+ MediaSession.Token token = mMediaBrowser.getSessionToken();
+ MediaController controller = new MediaController(mContext, token);
+ controller.getTransportControls();
+ controller.getTransportControls().prepare();
+ controller.getTransportControls().play();
+ }
+ }, rootHints);
+ mMediaBrowser.connect();
+ }
+
+ /**
+ * Get the media session token
+ * @return the token, or null if the MediaBrowser is null or disconnected
+ */
+ public MediaSession.Token getToken() {
+ if (mMediaBrowser == null || !mMediaBrowser.isConnected()) {
+ return null;
+ }
+ return mMediaBrowser.getSessionToken();
+ }
+
+ /**
+ * Get an intent to launch the app associated with this browser service
+ * @return
+ */
+ public PendingIntent getAppIntent() {
+ PackageManager pm = mContext.getPackageManager();
+ Intent launchIntent = pm.getLaunchIntentForPackage(mComponentName.getPackageName());
+ return PendingIntent.getActivity(mContext, 0, launchIntent, 0);
+ }
+
+ /**
+ * Used to test if SystemUI is allowed to connect to the given component as a MediaBrowser
+ * @param mContext the context
+ * @param callback methods onConnected or onError will be called to indicate whether the
+ * connection was successful or not
+ * @param mComponentName Component name of the MediaBrowserService this browser will connect to
+ */
+ public static MediaBrowser testConnection(Context mContext, Callback callback,
+ ComponentName mComponentName) {
+ final MediaBrowser.ConnectionCallback mConnectionCallback =
+ new MediaBrowser.ConnectionCallback() {
+ @Override
+ public void onConnected() {
+ Log.d(TAG, "connected");
+ callback.onConnected();
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ Log.d(TAG, "suspended");
+ callback.onError();
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ Log.d(TAG, "failed");
+ callback.onError();
+ }
+ };
+ Bundle rootHints = new Bundle();
+ rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
+ MediaBrowser browser = new MediaBrowser(mContext,
+ mComponentName,
+ mConnectionCallback,
+ rootHints);
+ browser.connect();
+ return browser;
+ }
+
+ /**
+ * Interface to handle results from QSMediaBrowser
+ */
+ public static class Callback {
+ /**
+ * Called when the browser has successfully connected to the service
+ */
+ public void onConnected() {
+ }
+
+ /**
+ * Called when the browser encountered an error connecting to the service
+ */
+ public void onError() {
+ }
+
+ /**
+ * Called when the browser finds a suitable track to add to the media carousel
+ * @param track media info for the item
+ * @param component component of the MediaBrowserService which returned this
+ * @param browser reference to the browser
+ */
+ public void addTrack(MediaDescription track, ComponentName component,
+ QSMediaBrowser browser) {
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index e636707a9722..e76cd5116818 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -18,11 +18,13 @@ package com.android.systemui.qs;
import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
-import android.app.Notification;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.media.MediaDescription;
import android.media.session.MediaController;
import android.media.session.MediaSession;
import android.util.Log;
@@ -39,7 +41,7 @@ import com.android.systemui.R;
import com.android.systemui.media.MediaControlPanel;
import com.android.systemui.media.SeekBarObserver;
import com.android.systemui.media.SeekBarViewModel;
-import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.concurrent.Executor;
@@ -61,24 +63,28 @@ public class QSMediaPlayer extends MediaControlPanel {
};
private final QSPanel mParent;
+ private final Executor mForegroundExecutor;
private final DelayableExecutor mBackgroundExecutor;
private final SeekBarViewModel mSeekBarViewModel;
private final SeekBarObserver mSeekBarObserver;
+ private String mPackageName;
/**
* Initialize quick shade version of player
* @param context
* @param parent
- * @param manager
+ * @param routeManager Provides information about device
* @param foregroundExecutor
* @param backgroundExecutor
+ * @param activityStarter
*/
- public QSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
- LocalMediaManager routeManager, Executor foregroundExecutor,
- DelayableExecutor backgroundExecutor) {
- super(context, parent, manager, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
- foregroundExecutor, backgroundExecutor);
+ public QSMediaPlayer(Context context, ViewGroup parent, LocalMediaManager routeManager,
+ Executor foregroundExecutor, DelayableExecutor backgroundExecutor,
+ ActivityStarter activityStarter) {
+ super(context, parent, routeManager, R.layout.qs_media_panel, QS_ACTION_IDS,
+ foregroundExecutor, backgroundExecutor, activityStarter);
mParent = (QSPanel) parent;
+ mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor);
mSeekBarObserver = new SeekBarObserver(getView());
@@ -92,48 +98,103 @@ public class QSMediaPlayer extends MediaControlPanel {
}
/**
+ * Add a media panel view based on a media description. Used for resumption
+ * @param description
+ * @param iconColor
+ * @param bgColor
+ * @param contentIntent
+ * @param pkgName
+ */
+ public void setMediaSession(MediaSession.Token token, MediaDescription description,
+ int iconColor, int bgColor, PendingIntent contentIntent, String pkgName) {
+ mPackageName = pkgName;
+ PackageManager pm = getContext().getPackageManager();
+ Drawable icon = null;
+ CharSequence appName = pkgName.substring(pkgName.lastIndexOf("."));
+ try {
+ icon = pm.getApplicationIcon(pkgName);
+ appName = pm.getApplicationLabel(pm.getApplicationInfo(pkgName, 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Error getting package information", e);
+ }
+
+ // Set what we can normally
+ super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
+ appName.toString(), null);
+
+ // Then add info from MediaDescription
+ ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
+ if (albumView != null) {
+ // Resize art in a background thread
+ mBackgroundExecutor.execute(() -> processAlbumArt(description, albumView));
+ }
+
+ // Song name
+ TextView titleText = mMediaNotifView.findViewById(R.id.header_title);
+ CharSequence songName = description.getTitle();
+ titleText.setText(songName);
+ titleText.setTextColor(iconColor);
+
+ // Artist name (not in mini player)
+ TextView artistText = mMediaNotifView.findViewById(R.id.header_artist);
+ if (artistText != null) {
+ CharSequence artistName = description.getSubtitle();
+ artistText.setText(artistName);
+ artistText.setTextColor(iconColor);
+ }
+
+ initLongPressMenu(iconColor);
+
+ // Set buttons to resume state
+ resetButtons();
+ }
+
+ /**
* Update media panel view for the given media session
* @param token token for this media session
* @param icon app notification icon
+ * @param largeIcon notification's largeIcon, used as a fallback for album art
* @param iconColor foreground color (for text, icons)
* @param bgColor background color
* @param actionsContainer a LinearLayout containing the media action buttons
- * @param notif reference to original notification
- * @param device current playback device
+ * @param contentIntent Intent to send when user taps on player
+ * @param appName Application title
+ * @param key original notification's key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor,
- int bgColor, View actionsContainer, Notification notif) {
+ public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
+ String appName, String key) {
- String appName = Notification.Builder.recoverBuilder(getContext(), notif)
- .loadHeaderAppName();
- super.setMediaSession(token, icon, iconColor, bgColor, notif.contentIntent,
- appName);
+ super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
+ key);
// Media controls
- LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
- int i = 0;
- for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
- if (thatBtn == null || thatBtn.getDrawable() == null
- || thatBtn.getVisibility() != View.VISIBLE) {
- thisBtn.setVisibility(View.GONE);
- continue;
- }
+ if (actionsContainer != null) {
+ LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
+ int i = 0;
+ for (; i < parentActionsLayout.getChildCount() && i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ ImageButton thatBtn = parentActionsLayout.findViewById(NOTIF_ACTION_IDS[i]);
+ if (thatBtn == null || thatBtn.getDrawable() == null
+ || thatBtn.getVisibility() != View.VISIBLE) {
+ thisBtn.setVisibility(View.GONE);
+ continue;
+ }
- Drawable thatIcon = thatBtn.getDrawable();
- thisBtn.setImageDrawable(thatIcon.mutate());
- thisBtn.setVisibility(View.VISIBLE);
- thisBtn.setOnClickListener(v -> {
- Log.d(TAG, "clicking on other button");
- thatBtn.performClick();
- });
- }
+ Drawable thatIcon = thatBtn.getDrawable();
+ thisBtn.setImageDrawable(thatIcon.mutate());
+ thisBtn.setVisibility(View.VISIBLE);
+ thisBtn.setOnClickListener(v -> {
+ Log.d(TAG, "clicking on other button");
+ thatBtn.performClick();
+ });
+ }
- // Hide any unused buttons
- for (; i < QS_ACTION_IDS.length; i++) {
- ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
- thisBtn.setVisibility(View.GONE);
+ // Hide any unused buttons
+ for (; i < QS_ACTION_IDS.length; i++) {
+ ImageButton thisBtn = mMediaNotifView.findViewById(QS_ACTION_IDS[i]);
+ thisBtn.setVisibility(View.GONE);
+ }
}
// Seek Bar
@@ -141,6 +202,10 @@ public class QSMediaPlayer extends MediaControlPanel {
mBackgroundExecutor.execute(
() -> mSeekBarViewModel.updateController(controller, iconColor));
+ initLongPressMenu(iconColor);
+ }
+
+ private void initLongPressMenu(int iconColor) {
// Set up long press menu
View guts = mMediaNotifView.findViewById(R.id.media_guts);
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -148,7 +213,7 @@ public class QSMediaPlayer extends MediaControlPanel {
View clearView = options.findViewById(R.id.remove);
clearView.setOnClickListener(b -> {
- mParent.removeMediaPlayer(QSMediaPlayer.this);
+ removePlayer();
});
ImageView removeIcon = options.findViewById(R.id.remove_icon);
removeIcon.setImageTintList(ColorStateList.valueOf(iconColor));
@@ -168,9 +233,9 @@ public class QSMediaPlayer extends MediaControlPanel {
}
@Override
- public void clearControls() {
- super.clearControls();
-
+ protected void resetButtons() {
+ super.resetButtons();
+ mSeekBarViewModel.clearController();
View guts = mMediaNotifView.findViewById(R.id.media_guts);
View options = mMediaNotifView.findViewById(R.id.qs_media_controls_options);
@@ -193,4 +258,19 @@ public class QSMediaPlayer extends MediaControlPanel {
public void setListening(boolean listening) {
mSeekBarViewModel.setListening(listening);
}
+
+ @Override
+ public void removePlayer() {
+ Log.d(TAG, "removing player from parent: " + mParent);
+ // Ensure this happens on the main thread (could happen in QSMediaBrowser callback)
+ mForegroundExecutor.execute(() -> mParent.removeMediaPlayer(QSMediaPlayer.this));
+ }
+
+ @Override
+ public String getMediaPlayerPackage() {
+ if (getController() == null) {
+ return mPackageName;
+ }
+ return super.getMediaPlayerPackage();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 0566b2e621db..a3004bdc004d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -21,16 +21,26 @@ import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEX
import static com.android.systemui.util.Utils.useQsMediaPlayer;
import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.media.MediaDescription;
import android.media.session.MediaSession;
import android.metrics.LogMaker;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.service.notification.StatusBarNotification;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
@@ -42,7 +52,9 @@ import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.NotificationVisibility;
import com.android.settingslib.Utils;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
@@ -54,6 +66,8 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.media.MediaControlPanel;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -63,7 +77,9 @@ 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.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -91,6 +107,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
protected final Context mContext;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+ private final BroadcastDispatcher mBroadcastDispatcher;
private String mCachedSpecs = "";
protected final View mBrightnessView;
private final H mHandler = new H();
@@ -99,11 +116,12 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private final LinearLayout mMediaCarousel;
private final ArrayList<QSMediaPlayer> mMediaPlayers = new ArrayList<>();
- private final NotificationMediaManager mNotificationMediaManager;
private final LocalBluetoothManager mLocalBluetoothManager;
private final Executor mForegroundExecutor;
private final DelayableExecutor mBackgroundExecutor;
private boolean mUpdateCarousel = false;
+ private ActivityStarter mActivityStarter;
+ private NotificationEntryManager mNotificationEntryManager;
protected boolean mExpanded;
protected boolean mListening;
@@ -112,6 +130,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private BrightnessController mBrightnessController;
private final DumpManager mDumpManager;
private final QSLogger mQSLogger;
+ protected final UiEventLogger mUiEventLogger;
protected QSTileHost mHost;
protected QSSecurityFooter mFooter;
@@ -125,6 +144,28 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
private BrightnessMirrorController mBrightnessMirrorController;
private View mDivider;
+ private boolean mHasLoadedMediaControls;
+
+ private final BroadcastReceiver mUserChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+ if (!mHasLoadedMediaControls) {
+ loadMediaResumptionControls();
+ }
+ }
+ }
+ };
+
+ private final NotificationEntryListener mNotificationEntryListener =
+ new NotificationEntryListener() {
+ @Override
+ public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
+ boolean removedByUser, int reason) {
+ checkToRemoveMediaNotification(entry);
+ }
+ };
@Inject
public QSPanel(
@@ -133,19 +174,24 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- NotificationMediaManager notificationMediaManager,
@Main Executor foregroundExecutor,
@Background DelayableExecutor backgroundExecutor,
- @Nullable LocalBluetoothManager localBluetoothManager
+ @Nullable LocalBluetoothManager localBluetoothManager,
+ ActivityStarter activityStarter,
+ NotificationEntryManager entryManager,
+ UiEventLogger uiEventLogger
) {
super(context, attrs);
mContext = context;
mQSLogger = qsLogger;
mDumpManager = dumpManager;
- mNotificationMediaManager = notificationMediaManager;
mForegroundExecutor = foregroundExecutor;
mBackgroundExecutor = backgroundExecutor;
mLocalBluetoothManager = localBluetoothManager;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mActivityStarter = activityStarter;
+ mNotificationEntryManager = entryManager;
+ mUiEventLogger = uiEventLogger;
setOrientation(VERTICAL);
@@ -180,7 +226,7 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
updateResources();
mBrightnessController = new BrightnessController(getContext(),
- findViewById(R.id.brightness_slider), broadcastDispatcher);
+ findViewById(R.id.brightness_slider), mBroadcastDispatcher);
}
@Override
@@ -204,13 +250,16 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
* Add or update a player for the associated media session
* @param token
* @param icon
+ * @param largeIcon
* @param iconColor
* @param bgColor
* @param actionsContainer
* @param notif
+ * @param key
*/
- public void addMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
- View actionsContainer, StatusBarNotification notif) {
+ public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
+ String key) {
if (!useQsMediaPlayer(mContext)) {
// Shouldn't happen, but just in case
Log.e(TAG, "Tried to add media session without player!");
@@ -224,14 +273,20 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
QSMediaPlayer player = null;
String packageName = notif.getPackageName();
for (QSMediaPlayer p : mMediaPlayers) {
- if (p.getMediaSessionToken().equals(token)) {
- Log.d(TAG, "a player for this session already exists");
+ if (p.getKey() == null) {
+ // No notification key = loaded via mediabrowser, so just match on package
+ if (packageName.equals(p.getMediaPlayerPackage())) {
+ Log.d(TAG, "Found matching resume player by package: " + packageName);
+ player = p;
+ break;
+ }
+ } else if (p.getMediaSessionToken().equals(token)) {
+ Log.d(TAG, "Found matching player by token " + packageName);
player = p;
break;
- }
-
- if (packageName.equals(p.getMediaPlayerPackage())) {
- Log.d(TAG, "found an old session for this app");
+ } else if (packageName.equals(p.getMediaPlayerPackage()) && key.equals(p.getKey())) {
+ // Also match if it's the same package and notification key
+ Log.d(TAG, "Found matching player by package " + packageName + ", " + key);
player = p;
break;
}
@@ -252,8 +307,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
LocalMediaManager routeManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
imm, notif.getPackageName());
- player = new QSMediaPlayer(mContext, this, mNotificationMediaManager, routeManager,
- mForegroundExecutor, mBackgroundExecutor);
+ player = new QSMediaPlayer(mContext, this, routeManager, mForegroundExecutor,
+ mBackgroundExecutor, mActivityStarter);
player.setListening(mListening);
if (player.isPlaying()) {
mMediaCarousel.addView(player.getView(), 0, lp); // add in front
@@ -266,8 +321,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
Log.d(TAG, "setting player session");
- player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
- notif.getNotification());
+ String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
+ .loadHeaderAppName();
+ player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
+ notif.getNotification().contentIntent, appName, key);
if (mMediaPlayers.size() > 0) {
((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
@@ -297,6 +354,100 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return true;
}
+ private final QSMediaBrowser.Callback mMediaBrowserCallback = new QSMediaBrowser.Callback() {
+ @Override
+ public void addTrack(MediaDescription desc, ComponentName component,
+ QSMediaBrowser browser) {
+ if (component == null) {
+ Log.e(TAG, "Component cannot be null");
+ return;
+ }
+
+ if (desc == null || desc.getTitle() == null) {
+ Log.e(TAG, "Description incomplete");
+ return;
+ }
+
+ Log.d(TAG, "adding track from browser: " + desc + ", " + component);
+ QSMediaPlayer player = new QSMediaPlayer(mContext, QSPanel.this,
+ null, mForegroundExecutor, mBackgroundExecutor, mActivityStarter);
+
+ String pkgName = component.getPackageName();
+
+ // Add controls to carousel
+ int playerWidth = (int) getResources().getDimension(R.dimen.qs_media_width);
+ int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
+ LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(playerWidth,
+ LayoutParams.MATCH_PARENT);
+ lp.setMarginStart(padding);
+ lp.setMarginEnd(padding);
+ mMediaCarousel.addView(player.getView(), lp);
+ ((View) mMediaCarousel.getParent()).setVisibility(View.VISIBLE);
+ mMediaPlayers.add(player);
+
+ int iconColor = Color.DKGRAY;
+ int bgColor = Color.LTGRAY;
+
+ MediaSession.Token token = browser.getToken();
+ player.setMediaSession(token, desc, iconColor, bgColor, browser.getAppIntent(),
+ pkgName);
+ }
+ };
+
+ /**
+ * Load controls for resuming media, if available
+ */
+ private void loadMediaResumptionControls() {
+ if (!useQsMediaPlayer(mContext)) {
+ return;
+ }
+ Log.d(TAG, "Loading resumption controls");
+
+ // Look up saved components to resume
+ Context userContext = mContext.createContextAsUser(mContext.getUser(), 0);
+ SharedPreferences prefs = userContext.getSharedPreferences(
+ MediaControlPanel.MEDIA_PREFERENCES, Context.MODE_PRIVATE);
+ String listString = prefs.getString(MediaControlPanel.MEDIA_PREFERENCE_KEY, null);
+ if (listString == null) {
+ Log.d(TAG, "No saved media components");
+ return;
+ }
+
+ String[] components = listString.split(QSMediaBrowser.DELIMITER);
+ Log.d(TAG, "components are: " + listString + " count " + components.length);
+ for (int i = 0; i < components.length && i < QSMediaBrowser.MAX_RESUMPTION_CONTROLS; i++) {
+ String[] info = components[i].split("/");
+ String packageName = info[0];
+ String className = info[1];
+ ComponentName component = new ComponentName(packageName, className);
+ QSMediaBrowser browser = new QSMediaBrowser(mContext, mMediaBrowserCallback,
+ component);
+ browser.findRecentMedia();
+ }
+ mHasLoadedMediaControls = true;
+ }
+
+ private void checkToRemoveMediaNotification(NotificationEntry entry) {
+ if (!useQsMediaPlayer(mContext)) {
+ return;
+ }
+
+ if (!entry.isMediaNotification()) {
+ return;
+ }
+
+ // If this entry corresponds to an existing set of controls, clear the controls
+ // This will handle apps that use an action to clear their notification
+ for (QSMediaPlayer p : mMediaPlayers) {
+ if (p.getKey() != null && p.getKey().equals(entry.getKey())) {
+ Log.d(TAG, "Clearing controls since notification removed " + entry.getKey());
+ p.clearControls();
+ return;
+ }
+ }
+ Log.d(TAG, "Media notification removed but no player found " + entry.getKey());
+ }
+
protected void addDivider() {
mDivider = LayoutInflater.from(mContext).inflate(R.layout.qs_divider, this, false);
mDivider.setBackgroundColor(Utils.applyAlpha(mDivider.getAlpha(),
@@ -347,6 +498,23 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mBrightnessMirrorController.addCallback(this);
}
mDumpManager.registerDumpable(getDumpableTag(), this);
+
+ if (getClass() == QSPanel.class) {
+ //TODO(ethibodeau) remove class check after media refactor in ag/11059751
+ // Only run this in QSPanel proper, not QQS
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ mBroadcastDispatcher.registerReceiver(mUserChangeReceiver, filter, null,
+ UserHandle.ALL);
+ mHasLoadedMediaControls = false;
+
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ if (userManager.isUserUnlocked(mContext.getUserId())) {
+ // If it's already unlocked (like if dark theme was toggled), we can load now
+ loadMediaResumptionControls();
+ }
+ }
+ mNotificationEntryManager.addNotificationEntryListener(mNotificationEntryListener);
}
@Override
@@ -362,6 +530,8 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
mBrightnessMirrorController.removeCallback(this);
}
mDumpManager.unregisterDumpable(getDumpableTag());
+ mBroadcastDispatcher.unregisterReceiver(mUserChangeReceiver);
+ mNotificationEntryManager.removeNotificationEntryListener(mNotificationEntryListener);
super.onDetachedFromWindow();
}
@@ -512,8 +682,10 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
}
mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
if (!mExpanded) {
+ mUiEventLogger.log(closePanelEvent());
closeDetail();
} else {
+ mUiEventLogger.log(openPanelEvent());
logTiles();
}
}
@@ -620,6 +792,18 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne
return mHost.createTileView(tile, collapsedView);
}
+ protected QSEvent openPanelEvent() {
+ return QSEvent.QS_PANEL_EXPANDED;
+ }
+
+ protected QSEvent closePanelEvent() {
+ return QSEvent.QS_PANEL_COLLAPSED;
+ }
+
+ protected QSEvent tileVisibleEvent() {
+ return QSEvent.QS_TILE_VISIBLE;
+ }
+
protected boolean shouldShowDetail() {
return mExpanded;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 9e8eb3a28781..8835e5db50c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -31,6 +31,9 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.logging.InstanceId;
+import com.android.internal.logging.InstanceIdSequence;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -73,6 +76,7 @@ import javax.inject.Singleton;
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private static final int MAX_QS_INSTANCE_ID = 1 << 20;
public static final String TILES_SETTING = Secure.QS_TILES;
@@ -85,6 +89,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private final DumpManager mDumpManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final QSLogger mQSLogger;
+ private final UiEventLogger mUiEventLogger;
+ private final InstanceIdSequence mInstanceIdSequence;
private final List<Callback> mCallbacks = new ArrayList<>();
private AutoTileManager mAutoTiles;
@@ -106,7 +112,8 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
Optional<StatusBar> statusBarOptional,
- QSLogger qsLogger) {
+ QSLogger qsLogger,
+ UiEventLogger uiEventLogger) {
mIconController = iconController;
mContext = context;
mUserContext = context;
@@ -114,8 +121,10 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mPluginManager = pluginManager;
mDumpManager = dumpManager;
mQSLogger = qsLogger;
+ mUiEventLogger = uiEventLogger;
mBroadcastDispatcher = broadcastDispatcher;
+ mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID);
mServices = new TileServices(this, bgLooper, mBroadcastDispatcher);
mStatusBarOptional = statusBarOptional;
@@ -137,6 +146,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
return mIconController;
}
+ @Override
+ public InstanceId getNewInstanceId() {
+ return mInstanceIdSequence.newInstanceId();
+ }
+
public void destroy() {
mTiles.values().forEach(tile -> tile.destroy());
mAutoTiles.destroy();
@@ -170,6 +184,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
}
@Override
+ public UiEventLogger getUiEventLogger() {
+ return mUiEventLogger;
+ }
+
+ @Override
public void addCallback(Callback callback) {
mCallbacks.add(callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 0ba4cb159024..f77ff8cd7949 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -29,7 +29,7 @@ import android.widget.LinearLayout;
import com.android.systemui.R;
import com.android.systemui.media.MediaControlPanel;
-import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.plugins.ActivityStarter;
import java.util.concurrent.Executor;
@@ -47,29 +47,32 @@ public class QuickQSMediaPlayer extends MediaControlPanel {
* Initialize mini media player for QQS
* @param context
* @param parent
- * @param manager
* @param foregroundExecutor
* @param backgroundExecutor
+ * @param activityStarter
*/
- public QuickQSMediaPlayer(Context context, ViewGroup parent, NotificationMediaManager manager,
- Executor foregroundExecutor, Executor backgroundExecutor) {
- super(context, parent, manager, null, R.layout.qqs_media_panel, QQS_ACTION_IDS,
- foregroundExecutor, backgroundExecutor);
+ public QuickQSMediaPlayer(Context context, ViewGroup parent, Executor foregroundExecutor,
+ Executor backgroundExecutor, ActivityStarter activityStarter) {
+ super(context, parent, null, R.layout.qqs_media_panel, QQS_ACTION_IDS,
+ foregroundExecutor, backgroundExecutor, activityStarter);
}
/**
* Update media panel view for the given media session
* @param token token for this media session
* @param icon app notification icon
+ * @param largeIcon notification's largeIcon, used as a fallback for album art
* @param iconColor foreground color (for text, icons)
* @param bgColor background color
* @param actionsContainer a LinearLayout containing the media action buttons
* @param actionsToShow indices of which actions to display in the mini player
* (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
* @param contentIntent Intent to send when user taps on the view
+ * @param key original notification's key
*/
- public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
- View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
+ public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+ int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
+ PendingIntent contentIntent, String key) {
// Only update if this is a different session and currently playing
String oldPackage = "";
if (getController() != null) {
@@ -84,7 +87,7 @@ public class QuickQSMediaPlayer extends MediaControlPanel {
return;
}
- super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null);
+ super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
int i = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index e6876bd98d21..6683a1ce4f4f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -27,6 +27,7 @@ import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.Dependency;
import com.android.systemui.R;
@@ -34,12 +35,13 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.plugins.ActivityStarter;
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.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.Utils;
@@ -83,13 +85,16 @@ public class QuickQSPanel extends QSPanel {
DumpManager dumpManager,
BroadcastDispatcher broadcastDispatcher,
QSLogger qsLogger,
- NotificationMediaManager notificationMediaManager,
@Main Executor foregroundExecutor,
@Background DelayableExecutor backgroundExecutor,
- @Nullable LocalBluetoothManager localBluetoothManager
+ @Nullable LocalBluetoothManager localBluetoothManager,
+ ActivityStarter activityStarter,
+ NotificationEntryManager entryManager,
+ UiEventLogger uiEventLogger
) {
- super(context, attrs, dumpManager, broadcastDispatcher, qsLogger, notificationMediaManager,
- foregroundExecutor, backgroundExecutor, localBluetoothManager);
+ super(context, attrs, dumpManager, broadcastDispatcher, qsLogger,
+ foregroundExecutor, backgroundExecutor, localBluetoothManager, activityStarter,
+ entryManager, uiEventLogger);
if (mFooter != null) {
removeView(mFooter.getView());
}
@@ -109,15 +114,15 @@ public class QuickQSPanel extends QSPanel {
int marginSize = (int) mContext.getResources().getDimension(R.dimen.qqs_media_spacing);
mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout,
- notificationMediaManager, foregroundExecutor, backgroundExecutor);
+ foregroundExecutor, backgroundExecutor, activityStarter);
LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
lp2.setMarginEnd(marginSize);
lp2.setMarginStart(0);
mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
- mTileLayout = new DoubleLineTileLayout(context);
+ mTileLayout = new DoubleLineTileLayout(context, mUiEventLogger);
mMediaTileLayout = mTileLayout;
- mRegularTileLayout = new HeaderTileLayout(context);
+ mRegularTileLayout = new HeaderTileLayout(context, mUiEventLogger);
LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
lp.setMarginEnd(0);
lp.setMarginStart(marginSize);
@@ -132,7 +137,7 @@ public class QuickQSPanel extends QSPanel {
super.setPadding(0, 0, 0, 0);
} else {
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
- mTileLayout = new HeaderTileLayout(context);
+ mTileLayout = new HeaderTileLayout(context, mUiEventLogger);
mTileLayout.setListening(mListening);
addView((View) mTileLayout, 0 /* Between brightness and footer */);
super.setPadding(0, 0, 0, 0);
@@ -309,13 +314,30 @@ public class QuickQSPanel extends QSPanel {
super.setVisibility(visibility);
}
+ @Override
+ protected QSEvent openPanelEvent() {
+ return QSEvent.QQS_PANEL_EXPANDED;
+ }
+
+ @Override
+ protected QSEvent closePanelEvent() {
+ return QSEvent.QQS_PANEL_COLLAPSED;
+ }
+
+ @Override
+ protected QSEvent tileVisibleEvent() {
+ return QSEvent.QQS_TILE_VISIBLE;
+ }
+
private static class HeaderTileLayout extends TileLayout {
- private boolean mListening;
+ private final UiEventLogger mUiEventLogger;
+
private Rect mClippingBounds = new Rect();
- public HeaderTileLayout(Context context) {
+ public HeaderTileLayout(Context context, UiEventLogger uiEventLogger) {
super(context);
+ mUiEventLogger = uiEventLogger;
setClipChildren(false);
setClipToPadding(false);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
@@ -440,5 +462,18 @@ public class QuickQSPanel extends QSPanel {
}
return getPaddingStart() + column * (mCellWidth + mCellMarginHorizontal);
}
+
+ @Override
+ public void setListening(boolean listening) {
+ boolean startedListening = !mListening && listening;
+ super.setListening(listening);
+ if (startedListening) {
+ for (int i = 0; i < getNumVisibleTiles(); i++) {
+ QSTile tile = mRecords.get(i).tile;
+ mUiEventLogger.logWithInstanceId(QSEvent.QQS_TILE_VISIBLE, 0,
+ tile.getMetricsSpec(), tile.getInstanceId());
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
index 4f812bc1059c..c22463964a19 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SecureSetting.java
@@ -37,11 +37,15 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
protected abstract void handleValueChanged(int value, boolean observedChange);
- public SecureSetting(Context context, Handler handler, String settingName) {
+ protected SecureSetting(Context context, Handler handler, String settingName) {
+ this(context, handler, settingName, ActivityManager.getCurrentUser());
+ }
+
+ public SecureSetting(Context context, Handler handler, String settingName, int userId) {
super(handler);
mContext = context;
mSettingName = settingName;
- mUserId = ActivityManager.getCurrentUser();
+ mUserId = userId;
}
public int getValue() {
@@ -80,4 +84,8 @@ public abstract class SecureSetting extends ContentObserver implements Listenabl
setListening(true);
}
}
+
+ public int getCurrentUser() {
+ return mUserId;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 9f59277c918a..098431658e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -31,7 +31,7 @@ public class TileLayout extends ViewGroup implements QSTileLayout {
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
private int mCellMarginTop;
- private boolean mListening;
+ protected boolean mListening;
protected int mMaxAllowedRows = 3;
// Prototyping with less rows
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 bfac85bd4c10..3e2f9dec5807 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -44,6 +44,7 @@ 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;
@@ -93,7 +94,8 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
LightBarController lightBarController,
KeyguardStateController keyguardStateController,
ScreenLifecycle screenLifecycle,
- TileQueryHelper tileQueryHelper) {
+ TileQueryHelper tileQueryHelper,
+ UiEventLogger uiEventLogger) {
super(new ContextThemeWrapper(context, R.style.edit_theme), attrs);
LayoutInflater.from(getContext()).inflate(R.layout.qs_customize_panel_content, this);
@@ -115,7 +117,7 @@ public class QSCustomizer extends LinearLayout implements OnMenuItemClickListene
mToolbar.setTitle(R.string.qs_edit);
mRecyclerView = findViewById(android.R.id.list);
mTransparentView = findViewById(R.id.customizer_transparent_view);
- mTileAdapter = new TileAdapter(getContext());
+ mTileAdapter = new TileAdapter(getContext(), uiEventLogger);
mTileQueryHelper = tileQueryHelper;
mTileQueryHelper.setListener(mTileAdapter);
mRecyclerView.setAdapter(mTileAdapter);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSEditEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/customize/QSEditEvent.kt
deleted file mode 100644
index ff8ddec8397a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSEditEvent.kt
+++ /dev/null
@@ -1,38 +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.qs.customize
-
-import com.android.internal.logging.UiEvent
-import com.android.internal.logging.UiEventLogger
-
-enum class QSEditEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
-
- @UiEvent(doc = "Tile removed from current tiles")
- QS_EDIT_REMOVE(210),
- @UiEvent(doc = "Tile added to current tiles")
- QS_EDIT_ADD(211),
- @UiEvent(doc = "Tile moved")
- QS_EDIT_MOVE(212),
- @UiEvent(doc = "QS customizer open")
- QS_EDIT_OPEN(213),
- @UiEvent(doc = "QS customizer closed")
- QS_EDIT_CLOSED(214),
- @UiEvent(doc = "QS tiles reset")
- QS_EDIT_RESET(215);
-
- override fun getId() = _id
-} \ No newline at end of file
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 58de95d7ed6d..e738cec4962a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -41,8 +41,8 @@ import androidx.recyclerview.widget.RecyclerView.State;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.android.internal.logging.UiEventLogger;
-import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.R;
+import com.android.systemui.qs.QSEditEvent;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.customize.TileAdapter.Holder;
import com.android.systemui.qs.customize.TileQueryHelper.TileInfo;
@@ -92,10 +92,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
private int mAccessibilityFromIndex;
private CharSequence mAccessibilityFromLabel;
private QSTileHost mHost;
- private UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
+ private final UiEventLogger mUiEventLogger;
- public TileAdapter(Context context) {
+ public TileAdapter(Context context, UiEventLogger uiEventLogger) {
mContext = context;
+ mUiEventLogger = uiEventLogger;
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
mItemTouchHelper = new ItemTouchHelper(mCallbacks);
mDecoration = new TileItemDecoration(context);
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 79a7df24e217..db77e08c204b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -118,6 +118,7 @@ public class TileQueryHelper {
if (tile == null) {
continue;
} else if (!tile.isAvailable()) {
+ tile.setTileSpec(spec);
tile.destroy();
continue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 08c8f86c1125..30e0a766de37 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -371,6 +371,11 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener
return MetricsEvent.QS_CUSTOM;
}
+ @Override
+ public final String getMetricsSpec() {
+ return mComponent.getPackageName();
+ }
+
public void startUnlockAndRun() {
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
try {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 60f6647743d2..7e5f2e1961e1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -48,7 +48,9 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.InstanceId;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.RestrictedLockUtils;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.Utils;
@@ -62,6 +64,7 @@ import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTile.State;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.PagedTileLayout.TilePage;
+import com.android.systemui.qs.QSEvent;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.QuickStatusBarHeader;
import com.android.systemui.qs.logging.QSLogger;
@@ -97,12 +100,14 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private final StatusBarStateController
mStatusBarStateController = Dependency.get(StatusBarStateController.class);
+ private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Object mStaleListener = new Object();
protected TState mState;
private TState mTmpState;
+ private final InstanceId mInstanceId;
private boolean mAnnounceNextStateChange;
private String mTileSpec;
@@ -156,10 +161,12 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
protected QSTileImpl(QSHost host) {
mHost = host;
mContext = host.getContext();
+ mInstanceId = host.getNewInstanceId();
mState = newTileState();
mTmpState = newTileState();
mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
mQSLogger = host.getQSLogger();
+ mUiEventLogger = host.getUiEventLogger();
}
protected final void resetStates() {
@@ -173,6 +180,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
return mLifecycle;
}
+ @Override
+ public InstanceId getInstanceId() {
+ return mInstanceId;
+ }
+
/**
* Adds or removes a listening client for the tile. If the tile has one or more
* listening client it will go into the listening state.
@@ -247,6 +259,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_CLICK).setType(TYPE_ACTION)
.addTaggedData(FIELD_STATUS_BAR_STATE,
mStatusBarStateController.getState())));
+ mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_CLICK, 0, getMetricsSpec(),
+ getInstanceId());
mQSLogger.logTileClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
mHandler.sendEmptyMessage(H.CLICK);
}
@@ -255,6 +269,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_SECONDARY_CLICK).setType(TYPE_ACTION)
.addTaggedData(FIELD_STATUS_BAR_STATE,
mStatusBarStateController.getState())));
+ mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_SECONDARY_CLICK, 0, getMetricsSpec(),
+ getInstanceId());
mQSLogger.logTileSecondaryClick(mTileSpec, mStatusBarStateController.getState(),
mState.state);
mHandler.sendEmptyMessage(H.SECONDARY_CLICK);
@@ -264,6 +280,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
mMetricsLogger.write(populate(new LogMaker(ACTION_QS_LONG_PRESS).setType(TYPE_ACTION)
.addTaggedData(FIELD_STATUS_BAR_STATE,
mStatusBarStateController.getState())));
+ mUiEventLogger.logWithInstanceId(QSEvent.QS_ACTION_LONG_PRESS, 0, getMetricsSpec(),
+ getInstanceId());
mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state);
mHandler.sendEmptyMessage(H.LONG_CLICK);
@@ -483,6 +501,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy
}
}
+ @Override
+ public String getMetricsSpec() {
+ return mTileSpec;
+ }
+
/**
* Provides a default label for the tile.
* @return default label for the tile.
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 4449d483118b..f2495048bf26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -20,6 +20,7 @@ import android.provider.Settings.Secure;
import android.service.quicksettings.Tile;
import android.widget.Switch;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -34,7 +35,8 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
BatteryController.BatteryStateChangeCallback {
private final BatteryController mBatteryController;
- private final SecureSetting mSetting;
+ @VisibleForTesting
+ protected final SecureSetting mSetting;
private int mLevel;
private boolean mPowerSave;
@@ -48,7 +50,9 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
super(host);
mBatteryController = batteryController;
mBatteryController.observe(getLifecycle(), this);
- mSetting = new SecureSetting(mContext, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED) {
+ int currentUser = host.getUserContext().getUserId();
+ mSetting = new SecureSetting(mContext, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
+ currentUser) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
handleRefreshState(null);
@@ -68,6 +72,11 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
}
@Override
+ protected void handleUserSwitch(int newUserId) {
+ mSetting.setUserId(newUserId);
+ }
+
+ @Override
public int getMetricsCategory() {
return MetricsEvent.QS_BATTERY_TILE;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 666323766c12..da7890324950 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -18,9 +18,12 @@ package com.android.systemui.qs.tiles;
import android.content.Intent;
import android.service.quicksettings.Tile;
+import android.text.TextUtils;
import android.util.Log;
+import android.widget.Switch;
import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.tileimpl.QSTileImpl;
@@ -35,14 +38,17 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
implements RecordingController.RecordingStateChangeCallback {
private static final String TAG = "ScreenRecordTile";
private RecordingController mController;
+ private ActivityStarter mActivityStarter;
private long mMillisUntilFinished = 0;
private Callback mCallback = new Callback();
@Inject
- public ScreenRecordTile(QSHost host, RecordingController controller) {
+ public ScreenRecordTile(QSHost host, RecordingController controller,
+ ActivityStarter activityStarter) {
super(host);
mController = controller;
mController.observe(this, mCallback);
+ mActivityStarter = activityStarter;
}
@Override
@@ -72,6 +78,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
state.value = isRecording || isStarting;
state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.label = mContext.getString(R.string.quick_settings_screen_record_label);
if (isRecording) {
state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
@@ -87,6 +94,10 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
}
+ state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
+ ? state.label
+ : TextUtils.concat(state.label, ", ", state.secondaryLabel);
+ state.expandedAccessibilityClassName = Switch.class.getName();
}
@Override
@@ -108,7 +119,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
Log.d(TAG, "Starting countdown");
// Close QS, otherwise the permission dialog appears beneath it
getHost().collapsePanels();
- mController.launchRecordPrompt();
+ Intent intent = mController.getPromptIntent();
+ mActivityStarter.postStartActivityDismissingKeyguard(intent, 0);
}
private void cancelCountdown() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
index 447f48b5db94..89ce125ae985 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UserDetailView.java
@@ -26,10 +26,12 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.RestrictedLockUtils;
import com.android.systemui.R;
import com.android.systemui.qs.PseudoGridView;
+import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
@@ -48,8 +50,9 @@ public class UserDetailView extends PseudoGridView {
R.layout.qs_user_detail, parent, attach);
}
- public void createAndSetAdapter(UserSwitcherController controller) {
- mAdapter = new Adapter(mContext, controller);
+ public void createAndSetAdapter(UserSwitcherController controller,
+ UiEventLogger uiEventLogger) {
+ mAdapter = new Adapter(mContext, controller, uiEventLogger);
ViewGroupAdapterBridge.link(this, mAdapter);
}
@@ -63,11 +66,14 @@ public class UserDetailView extends PseudoGridView {
private final Context mContext;
protected UserSwitcherController mController;
private View mCurrentUserView;
+ private final UiEventLogger mUiEventLogger;
- public Adapter(Context context, UserSwitcherController controller) {
+ public Adapter(Context context, UserSwitcherController controller,
+ UiEventLogger uiEventLogger) {
super(controller);
mContext = context;
mController = controller;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -127,6 +133,7 @@ public class UserDetailView extends PseudoGridView {
mController.startActivity(intent);
} else if (tag.isSwitchToEnabled) {
MetricsLogger.action(mContext, MetricsEvent.QS_SWITCH_USER);
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
if (!tag.isAddUser && !tag.isRestricted && !tag.isDisabledByAdmin) {
if (mCurrentUserView != null) {
mCurrentUserView.setActivated(false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 5bf44c6a3003..f3e2f104621e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -20,8 +20,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.trust.TrustManager;
@@ -39,7 +37,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -66,21 +63,6 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
private TrustManager mTrustManager;
private OverviewProxyService mOverviewProxyService;
- private TaskStackChangeListener mListener = new TaskStackChangeListener() {
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask) {
- if (task.configuration.windowConfiguration.getWindowingMode()
- != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- return;
- }
-
- if (homeTaskVisible) {
- showRecentApps(false /* triggeredFromAltTab */);
- }
- }
- };
-
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@Inject
public OverviewProxyRecentsImpl(Optional<Lazy<StatusBar>> statusBarLazy,
@@ -95,7 +77,6 @@ public class OverviewProxyRecentsImpl implements RecentsImplementation {
mHandler = new Handler();
mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
- ActivityManagerWrapper.getInstance().registerTaskStackListener(mListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66bc177da81d..34a9e28b943a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -20,8 +20,10 @@ import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SUPPORTS_WINDOW_CORNERS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -58,6 +60,7 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.util.ScreenshotHelper;
import com.android.systemui.Dumpable;
@@ -353,10 +356,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
long token = Binder.clearCallingIdentity();
try {
- Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ final Intent intent =
+ new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
+ intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
- AccessibilityManager.ACCESSIBILITY_BUTTON);
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(token);
@@ -380,7 +384,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
Insets visibleInsets, int taskId) {
mScreenshotHelper.provideScreenshot(screenImage, locationInScreen, visibleInsets,
- taskId, mHandler, null);
+ taskId, SCREENSHOT_OVERVIEW, mHandler, null);
}
@Override
@@ -491,8 +495,9 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
}
dispatchNavButtonBounds();
- // Update the systemui state flags
+ // Force-update the systemui state flags
updateSystemUiStateFlags();
+ notifySystemUiStateFlags(mSysUiState.getFlags());
notifyConnectionChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 8dad08e49d80..ae0a1c4d9822 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -59,15 +59,15 @@ public class RecordingController
}
/**
- * Show dialog of screen recording options to user.
+ * Get an intent to show screen recording options to the user.
*/
- public void launchRecordPrompt() {
+ public Intent getPromptIntent() {
final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENRECORD_LAUNCHER);
final Intent intent = new Intent();
intent.setComponent(launcherComponent);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(intent);
+ return intent;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 1780fb1848e7..581422116c8f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -36,6 +36,7 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Insets;
@@ -47,7 +48,6 @@ import android.graphics.Region;
import android.graphics.drawable.Icon;
import android.media.MediaActionSound;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -68,6 +68,7 @@ import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
@@ -76,11 +77,13 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Toast;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@@ -104,7 +107,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
*/
static class SaveImageInBackgroundData {
public Bitmap image;
- public Uri imageUri;
public Consumer<Uri> finisher;
public GlobalScreenshot.ActionsReadyListener mActionsReadyListener;
public int errorMsgResId;
@@ -112,13 +114,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
void clearImage() {
image = null;
- imageUri = null;
+ }
+ }
+
+ /**
+ * Structure returned by the SaveImageInBackgroundTask
+ */
+ static class SavedImageData {
+ public Uri uri;
+ public Notification.Action shareAction;
+ public Notification.Action editAction;
+ public Notification.Action deleteAction;
+ public List<Notification.Action> smartActions;
+
+ /**
+ * Used to reset the return data on error
+ */
+ public void reset() {
+ uri = null;
+ shareAction = null;
+ editAction = null;
+ deleteAction = null;
+ smartActions = null;
}
}
abstract static class ActionsReadyListener {
- abstract void onActionsReady(Uri imageUri, List<Notification.Action> smartActions,
- List<Notification.Action> actions);
+ abstract void onActionsReady(SavedImageData imageData);
}
// These strings are used for communicating the action invoked to
@@ -142,11 +164,20 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
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 long SCREENSHOT_CORNER_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 WindowManager mWindowManager;
@@ -163,17 +194,21 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private final LinearLayout mActionsView;
private final ImageView mBackgroundProtection;
private final FrameLayout mDismissButton;
+ private final ImageView mDismissImage;
private Bitmap mScreenBitmap;
+ private SaveImageInBackgroundTask mSaveInBgTask;
private Animator mScreenshotAnimation;
+ private Runnable mOnCompleteRunnable;
+ private boolean mInDarkMode = false;
+ private Animator mDismissAnimation;
private float mScreenshotOffsetXPx;
private float mScreenshotOffsetYPx;
private float mScreenshotHeightPx;
private float mDismissButtonSize;
private float mCornerSizeX;
-
- private AsyncTask<Void, Void, Void> mSaveInBgTask;
+ private float mDismissDeltaY;
private MediaActionSound mCameraSound;
@@ -185,7 +220,9 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_CORNER_TIMEOUT:
- GlobalScreenshot.this.clearScreenshot("timeout");
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
+ GlobalScreenshot.this.dismissScreenshot("timeout", false);
+ mOnCompleteRunnable.run();
break;
default:
break;
@@ -199,9 +236,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
@Inject
public GlobalScreenshot(
Context context, @Main Resources resources, LayoutInflater layoutInflater,
- ScreenshotNotificationsController screenshotNotificationsController) {
+ ScreenshotNotificationsController screenshotNotificationsController,
+ UiEventLogger uiEventLogger) {
mContext = context;
mNotificationsController = screenshotNotificationsController;
+ mUiEventLogger = uiEventLogger;
// Inflate the screenshot layout
mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null);
@@ -222,7 +261,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mBackgroundProtection = mScreenshotLayout.findViewById(
R.id.global_screenshot_actions_background);
mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
- mDismissButton.setOnClickListener(view -> clearScreenshot("dismiss_button"));
+ mDismissButton.setOnClickListener(view -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
+ dismissScreenshot("dismiss_button", false);
+ mOnCompleteRunnable.run();
+ });
+ mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);
mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
@@ -231,6 +275,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotSelectorView.setFocusableInTouchMode(true);
mScreenshotView.setPivotX(0);
mScreenshotView.setPivotY(0);
+ mActionsContainer.setPivotX(0);
// Setup the window that we are going to use
mWindowLayoutParams = new WindowManager.LayoutParams(
@@ -257,6 +302,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mDismissButtonSize = resources.getDimensionPixelSize(
R.dimen.screenshot_dismiss_button_tappable_size);
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);
@@ -294,19 +340,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
data.finisher = finisher;
data.mActionsReadyListener = actionsReadyListener;
data.createDeleteAction = false;
+
if (mSaveInBgTask != null) {
- mSaveInBgTask.cancel(false);
+ mSaveInBgTask.ignoreResult();
}
- mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
+ mSaveInBgTask.execute();
}
/**
* Takes a screenshot of the current display and shows an animation.
*/
private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
- clearScreenshot("new screenshot requested");
-
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
@@ -317,11 +363,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) {
+ dismissScreenshot("new screenshot requested", true);
+
mScreenBitmap = screenshot;
+
if (mScreenBitmap == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
finisher.accept(null);
+ mOnCompleteRunnable.run();
return;
}
@@ -329,15 +379,22 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw();
+ updateDarkTheme();
+
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+ if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
+ mDismissAnimation.cancel();
+ }
// Start the post-screenshot animation
- startAnimation(finisher, screenRect.width(), screenRect.height(),
- screenRect);
+ startAnimation(finisher, screenRect.width(), screenRect.height(), screenRect);
}
- void takeScreenshot(Consumer<Uri> finisher) {
+ void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
+ dismissScreenshot("new screenshot requested", true);
+ mOnCompleteRunnable = onComplete;
+
mDisplay.getRealMetrics(mDisplayMetrics);
takeScreenshot(
finisher,
@@ -345,9 +402,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
- Insets visibleInsets, int taskId, Consumer<Uri> finisher) {
+ Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) {
// TODO use taskId and visibleInsets
- clearScreenshot("new screenshot requested");
+ dismissScreenshot("new screenshot requested", true);
+ mOnCompleteRunnable = onComplete;
takeScreenshot(screenshot, finisher, screenshotScreenBounds);
}
@@ -355,7 +413,10 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
* Displays a screenshot selector
*/
@SuppressLint("ClickableViewAccessibility")
- void takeScreenshotPartial(final Consumer<Uri> finisher) {
+ void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
+ dismissScreenshot("new screenshot requested", true);
+ mOnCompleteRunnable = onComplete;
+
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -406,8 +467,24 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
/**
* Clears current screenshot
*/
- private void clearScreenshot(String reason) {
- Log.e(TAG, "clearing screenshot: " + reason);
+ private void dismissScreenshot(String reason, boolean immediate) {
+ Log.v(TAG, "clearing screenshot: " + reason);
+ if (!immediate) {
+ mDismissAnimation = createScreenshotDismissAnimation();
+ mDismissAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ clearScreenshot();
+ }
+ });
+ mDismissAnimation.start();
+ } else {
+ clearScreenshot();
+ }
+ }
+
+ private void clearScreenshot() {
if (mScreenshotLayout.isAttachedToWindow()) {
mWindowManager.removeView(mScreenshotLayout);
}
@@ -422,6 +499,48 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mDismissButton.setVisibility(View.GONE);
mScreenshotView.setVisibility(View.GONE);
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ mScreenshotView.setContentDescription(
+ mContext.getResources().getString(R.string.screenshot_preview_description));
+ mScreenshotLayout.setAlpha(1);
+ mDismissButton.setTranslationY(0);
+ mActionsContainer.setTranslationY(0);
+ mScreenshotView.setTranslationY(0);
+ }
+
+ /**
+ * 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() {
+ mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel));
+ mActionsContainer.setBackground(
+ mContext.getDrawable(R.drawable.action_chip_container_background));
+
+ }
+
+ /**
+ * Checks the current dark theme status and updates if it has changed.
+ */
+ private void updateDarkTheme() {
+ int currentNightMode = mContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ switch (currentNightMode) {
+ case Configuration.UI_MODE_NIGHT_NO:
+ // Night mode is not active, we're using the light theme
+ if (mInDarkMode) {
+ mInDarkMode = false;
+ reloadAssets();
+ }
+ break;
+ case Configuration.UI_MODE_NIGHT_YES:
+ // Night mode is active, we're using dark theme
+ if (!mInDarkMode) {
+ mInDarkMode = true;
+ reloadAssets();
+ }
+ break;
+ }
}
/**
@@ -429,13 +548,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@Nullable Rect screenRect) {
- // If power save is on, show a toast so there is some visual indication that a screenshot
+ // If power save is on, show a toast so there is some visual indication that a
+ // screenshot
// has been taken.
- PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(
+ Context.POWER_SERVICE);
if (powerManager.isPowerSaveMode()) {
- Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+ Toast.makeText(mContext, R.string.screenshot_saved_title,
+ Toast.LENGTH_SHORT).show();
}
+
// Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap);
@@ -443,12 +566,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
@Override
- void onActionsReady(Uri uri, List<Notification.Action> smartActions,
- List<Notification.Action> actions) {
- if (uri == null) {
+ void onActionsReady(SavedImageData imageData) {
+ finisher.accept(imageData.uri);
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
mScreenshotHandler.post(() -> {
if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
mScreenshotAnimation.addListener(
@@ -456,20 +581,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
- createScreenshotActionsShadeAnimation(
- smartActions, actions).start();
+ createScreenshotActionsShadeAnimation(imageData)
+ .start();
}
});
} else {
- createScreenshotActionsShadeAnimation(smartActions,
- actions).start();
+ createScreenshotActionsShadeAnimation(imageData).start();
}
+ mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+ mScreenshotHandler.sendMessageDelayed(
+ mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+ SCREENSHOT_CORNER_TIMEOUT_MILLIS);
});
}
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}
});
mScreenshotHandler.post(() -> {
@@ -500,14 +624,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
final PointF finalPos = new PointF(mScreenshotOffsetXPx + width * cornerScale / 2f,
- mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f);
+ mDisplayMetrics.heightPixels - mScreenshotOffsetYPx
+ - height * cornerScale / 2f);
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 scalePct =
- SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
+ SCREENSHOT_TO_CORNER_SCALE_DURATION_MS
+ / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
toCorner.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction();
if (t < scalePct) {
@@ -565,8 +691,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
return dropInAnimation;
}
- private ValueAnimator createScreenshotActionsShadeAnimation(
- List<Notification.Action> smartActions, List<Notification.Action> actions) {
+ private ValueAnimator createScreenshotActionsShadeAnimation(SavedImageData imageData) {
LayoutInflater inflater = LayoutInflater.from(mContext);
mActionsView.removeAllViews();
mActionsContainer.setScrollX(0);
@@ -581,61 +706,123 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
} catch (RemoteException e) {
}
- for (Notification.Action smartAction : smartActions) {
+ 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,
- () -> clearScreenshot("chip tapped"));
+ () -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
+ dismissScreenshot("chip tapped", false);
+ mOnCompleteRunnable.run();
+ });
mActionsView.addView(actionChip);
+ chips.add(actionChip);
}
- for (Notification.Action action : actions) {
- ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- actionChip.setText(action.title);
- actionChip.setIcon(action.getIcon(), true);
- actionChip.setPendingIntent(action.actionIntent, () -> clearScreenshot("chip tapped"));
- if (action.actionIntent.getIntent().getAction().equals(Intent.ACTION_EDIT)) {
- mScreenshotView.setOnClickListener(v -> {
- try {
- action.actionIntent.send();
- clearScreenshot("screenshot preview tapped");
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Intent cancelled", e);
- }
- });
+ 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);
+
+ mScreenshotView.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);
}
- mActionsView.addView(actionChip);
- }
+ });
+ mScreenshotView.setContentDescription(imageData.editAction.title);
if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
R.layout.global_screenshot_action_chip, mActionsView, false);
Toast scrollNotImplemented = Toast.makeText(
mContext, "Not implemented", Toast.LENGTH_SHORT);
- scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate
+ scrollChip.setText("Extend"); // TODO: add resource and translate
scrollChip.setIcon(
Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true);
- scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+ scrollChip.setOnClickListener(v -> {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SCROLL_TAPPED);
+ scrollNotImplemented.show();
+ });
mActionsView.addView(scrollChip);
+ chips.add(scrollChip);
}
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
- mActionsContainer.setY(mDisplayMetrics.heightPixels);
+ animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS);
+ float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS
+ / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS;
mActionsContainer.setVisibility(VISIBLE);
- mActionsContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- float actionsViewHeight = mActionsContainer.getMeasuredHeight() + mScreenshotHeightPx;
+ mActionsContainer.setAlpha(0);
animator.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction();
mBackgroundProtection.setAlpha(t);
- mActionsContainer.setY(mDisplayMetrics.heightPixels - actionsViewHeight * t);
+ mActionsContainer.setAlpha(t < alphaFraction ? t / alphaFraction : 1);
+ float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X
+ + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X));
+ mActionsContainer.setScaleX(containerScale);
+ for (ScreenshotActionChip chip : chips) {
+ chip.setAlpha(t);
+ chip.setScaleX(1 / containerScale); // invert to keep size of children constant
+ }
});
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 = mScreenshotView.getTranslationY();
+ float dismissStartY = mDismissButton.getTranslationY();
+ yAnim.addUpdateListener(animation -> {
+ float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
+ mScreenshotView.setTranslationY(screenshotStartY + yDelta);
+ mDismissButton.setTranslationY(dismissStartY + yDelta);
+ mActionsContainer.setTranslationY(yDelta);
+ });
+
+ AnimatorSet animSet = new AnimatorSet();
+ animSet.play(yAnim).with(alphaAnim);
+
+ return animSet;
+ }
+
/**
* Receiver to proxy the share or edit intent, used to clean up the notification and send
* appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -681,7 +868,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
}
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
- String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT
+ String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
+ ? ACTION_TYPE_EDIT
: ACTION_TYPE_SHARE;
ScreenshotSmartActions.notifyScreenshotAction(
context, intent.getStringExtra(EXTRA_ID), actionType, false);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
index f3614ffbdb1b..095c32f4a2ce 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -24,7 +24,6 @@ import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.Nullable;
-import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -53,7 +52,6 @@ import android.widget.Toast;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
-import java.util.List;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -347,14 +345,13 @@ public class GlobalScreenshotLegacy {
// Save the screenshot once we have a bit of time now
saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
@Override
- void onActionsReady(Uri uri, List<Notification.Action> smartActions,
- List<Notification.Action> actions) {
- if (uri == null) {
+ void onActionsReady(GlobalScreenshot.SavedImageData actionData) {
+ if (actionData.uri == null) {
mNotificationsController.notifyScreenshotError(
R.string.screenshot_failed_to_capture_text);
} else {
mNotificationsController
- .showScreenshotActionsNotification(uri, smartActions, actions);
+ .showScreenshotActionsNotification(actionData);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index c828c4cccce5..bc3c33d68b67 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -83,6 +83,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
private final Context mContext;
private final GlobalScreenshot.SaveImageInBackgroundData mParams;
+ private final GlobalScreenshot.SavedImageData mImageData;
private final String mImageFileName;
private final long mImageTime;
private final ScreenshotNotificationSmartActionsProvider mSmartActionsProvider;
@@ -93,6 +94,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
SaveImageInBackgroundTask(Context context, GlobalScreenshot.SaveImageInBackgroundData data) {
mContext = context;
+ mImageData = new GlobalScreenshot.SavedImageData();
// Prepare all the output metadata
mParams = data;
@@ -128,11 +130,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Resources r = mContext.getResources();
try {
- CompletableFuture<List<Notification.Action>> smartActionsFuture =
- ScreenshotSmartActions.getSmartActionsFuture(
- mScreenshotId, mImageFileName, image, mSmartActionsProvider,
- mSmartActionsEnabled, isManagedProfile(mContext));
-
// Save the screenshot to the MediaStore
final ContentValues values = new ContentValues();
values.put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
@@ -145,6 +142,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
values.put(MediaColumns.IS_PENDING, 1);
final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+
+ CompletableFuture<List<Notification.Action>> smartActionsFuture =
+ ScreenshotSmartActions.getSmartActionsFuture(
+ mScreenshotId, uri, image, mSmartActionsProvider,
+ mSmartActionsEnabled, isManagedProfile(mContext));
+
try {
// First, write the actual data for our screenshot
try (OutputStream out = resolver.openOutputStream(uri)) {
@@ -192,8 +195,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
throw e;
}
- List<Notification.Action> actions =
- populateNotificationActions(mContext, r, uri);
List<Notification.Action> smartActions = new ArrayList<>();
if (mSmartActionsEnabled) {
int timeoutMs = DeviceConfig.getInt(
@@ -202,12 +203,18 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
1000);
smartActions.addAll(buildSmartActions(
ScreenshotSmartActions.getSmartActions(
- mScreenshotId, mImageFileName, smartActionsFuture, timeoutMs,
+ mScreenshotId, smartActionsFuture, timeoutMs,
mSmartActionsProvider),
mContext));
}
- mParams.mActionsReadyListener.onActionsReady(uri, smartActions, actions);
- mParams.imageUri = uri;
+
+ mImageData.uri = uri;
+ mImageData.smartActions = smartActions;
+ mImageData.shareAction = createShareAction(mContext, mContext.getResources(), uri);
+ mImageData.editAction = createEditAction(mContext, mContext.getResources(), uri);
+ mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
+
+ mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.image = null;
mParams.errorMsgResId = 0;
} catch (Exception e) {
@@ -216,15 +223,24 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Slog.e(TAG, "unable to save screenshot", e);
mParams.clearImage();
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
- mParams.mActionsReadyListener.onActionsReady(null, null, null);
+ mImageData.reset();
+ mParams.mActionsReadyListener.onActionsReady(mImageData);
}
return null;
}
- @Override
- protected void onPostExecute(Void params) {
- mParams.finisher.accept(mParams.imageUri);
+ /**
+ * If we get a new screenshot request while this one is saving, we want to continue saving in
+ * the background but not return anything.
+ */
+ void ignoreResult() {
+ mParams.mActionsReadyListener = new GlobalScreenshot.ActionsReadyListener() {
+ @Override
+ void onActionsReady(GlobalScreenshot.SavedImageData imageData) {
+ // do nothing
+ }
+ };
}
@Override
@@ -232,13 +248,14 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// If we are cancelled while the task is running in the background, we may get null
// params. The finisher is expected to always be called back, so just use the baked-in
// params from the ctor in any case.
- mParams.mActionsReadyListener.onActionsReady(null, null, null);
+ mImageData.reset();
+ mParams.mActionsReadyListener.onActionsReady(mImageData);
mParams.finisher.accept(null);
mParams.clearImage();
}
@VisibleForTesting
- List<Notification.Action> populateNotificationActions(Context context, Resources r, Uri uri) {
+ Notification.Action createShareAction(Context context, Resources r, Uri uri) {
// Note: Both the share and edit actions are proxied through ActionProxyReceiver in
// order to do some common work like dismissing the keyguard and sending
// closeSystemWindows
@@ -263,8 +280,6 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// by setting the (otherwise unused) request code to the current user id.
int requestCode = context.getUserId();
- ArrayList<Notification.Action> actions = new ArrayList<>();
-
PendingIntent chooserAction = PendingIntent.getBroadcast(context, requestCode,
new Intent(context, GlobalScreenshot.TargetChosenReceiver.class),
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
@@ -288,7 +303,15 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Notification.Action.Builder shareActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_share),
r.getString(com.android.internal.R.string.share), shareAction);
- actions.add(shareActionBuilder.build());
+
+ return shareActionBuilder.build();
+ }
+
+ @VisibleForTesting
+ Notification.Action createEditAction(Context context, Resources r, Uri uri) {
+ // Note: Both the share and edit actions are proxied through ActionProxyReceiver in
+ // order to do some common work like dismissing the keyguard and sending
+ // closeSystemWindows
// Create an edit intent, if a specific package is provided as the editor, then
// launch that directly
@@ -301,6 +324,11 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
editIntent.setData(uri);
editIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
editIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ editIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ // Make sure pending intents for the system user are still unique across users
+ // by setting the (otherwise unused) request code to the current user id.
+ int requestCode = mContext.getUserId();
// Create a edit action
PendingIntent editAction = PendingIntent.getBroadcastAsUser(context, requestCode,
@@ -317,24 +345,30 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
Notification.Action.Builder editActionBuilder = new Notification.Action.Builder(
Icon.createWithResource(r, R.drawable.ic_screenshot_edit),
r.getString(com.android.internal.R.string.screenshot_edit), editAction);
- actions.add(editActionBuilder.build());
-
- if (mCreateDeleteAction) {
- // Create a delete action for the notification
- PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
- new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
- .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
- .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
- .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
- mSmartActionsEnabled)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
- Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
- Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
- r.getString(com.android.internal.R.string.delete), deleteAction);
- actions.add(deleteActionBuilder.build());
- }
- return actions;
+
+ return editActionBuilder.build();
+ }
+
+ @VisibleForTesting
+ Notification.Action createDeleteAction(Context context, Resources r, Uri uri) {
+ // Make sure pending intents for the system user are still unique across users
+ // by setting the (otherwise unused) request code to the current user id.
+ int requestCode = mContext.getUserId();
+
+ // Create a delete action for the notification
+ PendingIntent deleteAction = PendingIntent.getBroadcast(context, requestCode,
+ new Intent(context, GlobalScreenshot.DeleteScreenshotReceiver.class)
+ .putExtra(GlobalScreenshot.SCREENSHOT_URI_ID, uri.toString())
+ .putExtra(GlobalScreenshot.EXTRA_ID, mScreenshotId)
+ .putExtra(GlobalScreenshot.EXTRA_SMART_ACTIONS_ENABLED,
+ mSmartActionsEnabled)
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT);
+ Notification.Action.Builder deleteActionBuilder = new Notification.Action.Builder(
+ Icon.createWithResource(r, R.drawable.ic_screenshot_delete),
+ r.getString(com.android.internal.R.string.delete), deleteAction);
+
+ return deleteActionBuilder.build();
}
private int getUserHandleOfForegroundApplication(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
new file mode 100644
index 000000000000..20fa991dcc1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotEvent.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_GLOBAL_ACTIONS;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OTHER;
+import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_OVERVIEW;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+public enum ScreenshotEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "screenshot requested from global actions")
+ SCREENSHOT_REQUESTED_GLOBAL_ACTIONS(302),
+ @UiEvent(doc = "screenshot requested from key chord")
+ SCREENSHOT_REQUESTED_KEY_CHORD(303),
+ @UiEvent(doc = "screenshot requested from other key press (e.g. ctrl-s)")
+ SCREENSHOT_REQUESTED_KEY_OTHER(384),
+ @UiEvent(doc = "screenshot requested from overview")
+ SCREENSHOT_REQUESTED_OVERVIEW(304),
+ @UiEvent(doc = "screenshot requested from accessibility actions")
+ SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS(382),
+ @UiEvent(doc = "screenshot requested (other)")
+ SCREENSHOT_REQUESTED_OTHER(305),
+ @UiEvent(doc = "screenshot was saved")
+ SCREENSHOT_SAVED(306),
+ @UiEvent(doc = "screenshot failed to save")
+ SCREENSHOT_NOT_SAVED(336),
+ @UiEvent(doc = "screenshot preview tapped")
+ SCREENSHOT_PREVIEW_TAPPED(307),
+ @UiEvent(doc = "screenshot edit button tapped")
+ SCREENSHOT_EDIT_TAPPED(308),
+ @UiEvent(doc = "screenshot share button tapped")
+ SCREENSHOT_SHARE_TAPPED(309),
+ @UiEvent(doc = "screenshot smart action chip tapped")
+ SCREENSHOT_SMART_ACTION_TAPPED(374),
+ @UiEvent(doc = "screenshot scroll tapped")
+ SCREENSHOT_SCROLL_TAPPED(373),
+ @UiEvent(doc = "screenshot interaction timed out")
+ SCREENSHOT_INTERACTION_TIMEOUT(310),
+ @UiEvent(doc = "screenshot explicitly dismissed")
+ SCREENSHOT_EXPLICIT_DISMISSAL(311);
+
+ private final int mId;
+
+ ScreenshotEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ static ScreenshotEvent getScreenshotSource(int source) {
+ switch (source) {
+ case SCREENSHOT_GLOBAL_ACTIONS:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_GLOBAL_ACTIONS;
+ case SCREENSHOT_KEY_CHORD:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_CHORD;
+ case SCREENSHOT_KEY_OTHER:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER;
+ case SCREENSHOT_OVERVIEW:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_OVERVIEW;
+ case SCREENSHOT_ACCESSIBILITY_ACTIONS:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_ACCESSIBILITY_ACTIONS;
+ case SCREENSHOT_OTHER:
+ default:
+ return ScreenshotEvent.SCREENSHOT_REQUESTED_OTHER;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
index 09a0644159e2..3edb33da9cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsProvider.java
@@ -19,6 +19,7 @@ package com.android.systemui.screenshot;
import android.app.Notification;
import android.content.ComponentName;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.util.Log;
import java.util.Collections;
@@ -67,7 +68,7 @@ public class ScreenshotNotificationSmartActionsProvider {
*/
public CompletableFuture<List<Notification.Action>> getActions(
String screenshotId,
- String screenshotFileName,
+ Uri screenshotUri,
Bitmap bitmap,
ComponentName componentName,
boolean isManagedProfile) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
index 811a8d936b77..fbcd6ba0ff47 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotNotificationsController.java
@@ -32,7 +32,6 @@ import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Picture;
-import android.net.Uri;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.WindowManager;
@@ -42,8 +41,6 @@ import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.util.NotificationChannels;
-import java.util.List;
-
import javax.inject.Inject;
/**
@@ -185,23 +182,20 @@ public class ScreenshotNotificationsController {
/**
* Shows a notification with the saved screenshot and actions that can be taken with it.
*
- * @param imageUri URI for the saved image
- * @param actions a list of notification actions which can be taken
+ * @param actionData SavedImageData struct with image URI and actions
*/
public void showScreenshotActionsNotification(
- Uri imageUri,
- List<Notification.Action> smartActions,
- List<Notification.Action> actions) {
- for (Notification.Action action : actions) {
- mNotificationBuilder.addAction(action);
- }
- for (Notification.Action smartAction : smartActions) {
+ GlobalScreenshot.SavedImageData actionData) {
+ mNotificationBuilder.addAction(actionData.shareAction);
+ mNotificationBuilder.addAction(actionData.editAction);
+ mNotificationBuilder.addAction(actionData.deleteAction);
+ for (Notification.Action smartAction : actionData.smartActions) {
mNotificationBuilder.addAction(smartAction);
}
// Create the intent to show the screenshot in gallery
Intent launchIntent = new Intent(Intent.ACTION_VIEW);
- launchIntent.setDataAndType(imageUri, "image/png");
+ launchIntent.setDataAndType(actionData.uri, "image/png");
launchIntent.setFlags(
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index d31344446fac..c228fe2c4334 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -23,6 +23,7 @@ import android.app.Notification;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Slog;
@@ -45,7 +46,7 @@ public class ScreenshotSmartActions {
@VisibleForTesting
static CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
- String screenshotId, String screenshotFileName, Bitmap image,
+ String screenshotId, Uri screenshotUri, Bitmap image,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
boolean smartActionsEnabled, boolean isManagedProfile) {
if (!smartActionsEnabled) {
@@ -70,7 +71,7 @@ public class ScreenshotSmartActions {
? runningTask.topActivity
: new ComponentName("", "");
smartActionsFuture = smartActionsProvider.getActions(
- screenshotId, screenshotFileName, image, componentName, isManagedProfile);
+ screenshotId, screenshotUri, image, componentName, isManagedProfile);
} catch (Throwable e) {
long waitTimeMs = SystemClock.uptimeMillis() - startTimeMs;
smartActionsFuture = CompletableFuture.completedFuture(Collections.emptyList());
@@ -84,7 +85,7 @@ public class ScreenshotSmartActions {
}
@VisibleForTesting
- static List<Notification.Action> getSmartActions(String screenshotId, String screenshotFileName,
+ static List<Notification.Action> getSmartActions(String screenshotId,
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
ScreenshotNotificationSmartActionsProvider smartActionsProvider) {
long startTimeMs = SystemClock.uptimeMillis();
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
new file mode 100644
index 000000000000..5ced40cb1b3b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScrollCaptureController.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.os.IBinder;
+import android.view.IWindowManager;
+
+import javax.inject.Inject;
+
+/**
+ * Stub
+ */
+public class ScrollCaptureController {
+
+ public static final int STATUS_A = 0;
+ public static final int STATUS_B = 1;
+
+ private final IWindowManager mWindowManagerService;
+ private StatusListener mListener;
+
+ /**
+ *
+ * @param windowManagerService
+ */
+ @Inject
+ public ScrollCaptureController(IWindowManager windowManagerService) {
+ mWindowManagerService = windowManagerService;
+ }
+
+ interface StatusListener {
+ void onScrollCaptureStatus(boolean available);
+ }
+
+ /**
+ *
+ * @param window
+ * @param listener
+ */
+ public void getStatus(IBinder window, StatusListener listener) {
+ mListener = listener;
+// try {
+// mWindowManagerService.requestScrollCapture(window, new ClientCallbacks());
+// } catch (RemoteException e) {
+// }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 8b8b6f8071e1..98030d45b05e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,6 +16,9 @@
package com.android.systemui.screenshot;
+import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_PROCESS_COMPLETE;
+import static com.android.internal.util.ScreenshotHelper.SCREENSHOT_MSG_URI;
+
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
@@ -32,6 +35,9 @@ import android.os.UserManager;
import android.util.Log;
import android.view.WindowManager;
+import com.android.internal.logging.UiEventLogger;
+import com.android.internal.util.ScreenshotHelper;
+
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -42,13 +48,21 @@ public class TakeScreenshotService extends Service {
private final GlobalScreenshot mScreenshot;
private final GlobalScreenshotLegacy mScreenshotLegacy;
private final UserManager mUserManager;
+ private final UiEventLogger mUiEventLogger;
private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
- Consumer<Uri> finisher = uri -> {
- Message reply = Message.obtain(null, 1, uri);
+ Consumer<Uri> uriConsumer = uri -> {
+ Message reply = Message.obtain(null, SCREENSHOT_MSG_URI, uri);
+ try {
+ callback.send(reply);
+ } catch (RemoteException e) {
+ }
+ };
+ Runnable onComplete = () -> {
+ Message reply = Message.obtain(null, SCREENSHOT_MSG_PROCESS_COMPLETE);
try {
callback.send(reply);
} catch (RemoteException e) {
@@ -60,42 +74,49 @@ public class TakeScreenshotService extends Service {
// animation and error notification.
if (!mUserManager.isUserUnlocked()) {
Log.w(TAG, "Skipping screenshot because storage is locked!");
- post(() -> finisher.accept(null));
+ post(() -> uriConsumer.accept(null));
+ post(onComplete);
return;
}
- // TODO (mkephart): clean up once notifications flow is fully deprecated
+ // TODO: clean up once notifications flow is fully deprecated
boolean useCornerFlow = true;
+
+ ScreenshotHelper.ScreenshotRequest screenshotRequest =
+ (ScreenshotHelper.ScreenshotRequest) msg.obj;
+
+ mUiEventLogger.log(ScreenshotEvent.getScreenshotSource(screenshotRequest.getSource()));
+
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
if (useCornerFlow) {
- mScreenshot.takeScreenshot(finisher);
+ mScreenshot.takeScreenshot(uriConsumer, onComplete);
} else {
- mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ mScreenshotLegacy.takeScreenshot(
+ uriConsumer, screenshotRequest.getHasStatusBar(),
+ screenshotRequest.getHasNavBar());
}
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
if (useCornerFlow) {
- mScreenshot.takeScreenshotPartial(finisher);
+ mScreenshot.takeScreenshotPartial(uriConsumer, onComplete);
} else {
mScreenshotLegacy.takeScreenshotPartial(
- finisher, msg.arg1 > 0, msg.arg2 > 0);
+ uriConsumer, screenshotRequest.getHasStatusBar(),
+ screenshotRequest.getHasNavBar());
}
break;
case WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE:
- Bitmap screenshot = msg.getData().getParcelable(
- WindowManager.PARCEL_KEY_SCREENSHOT_BITMAP);
- Rect screenBounds = msg.getData().getParcelable(
- WindowManager.PARCEL_KEY_SCREENSHOT_BOUNDS);
- Insets insets = msg.getData().getParcelable(
- WindowManager.PARCEL_KEY_SCREENSHOT_INSETS);
- int taskId = msg.getData().getInt(WindowManager.PARCEL_KEY_SCREENSHOT_TASK_ID);
+ Bitmap screenshot = screenshotRequest.getBitmap();
+ Rect screenBounds = screenshotRequest.getBoundsInScreen();
+ Insets insets = screenshotRequest.getInsets();
+ int taskId = screenshotRequest.getTaskId();
if (useCornerFlow) {
mScreenshot.handleImageAsScreenshot(
- screenshot, screenBounds, insets, taskId, finisher);
+ screenshot, screenBounds, insets, taskId, uriConsumer, onComplete);
} else {
mScreenshotLegacy.handleImageAsScreenshot(
- screenshot, screenBounds, insets, taskId, finisher);
+ screenshot, screenBounds, insets, taskId, uriConsumer);
}
break;
default:
@@ -106,10 +127,12 @@ public class TakeScreenshotService extends Service {
@Inject
public TakeScreenshotService(GlobalScreenshot globalScreenshot,
- GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
+ GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager,
+ UiEventLogger uiEventLogger) {
mScreenshot = globalScreenshot;
mScreenshotLegacy = globalScreenshotLegacy;
mUserManager = userManager;
+ mUiEventLogger = uiEventLogger;
}
@Override
@@ -120,7 +143,7 @@ public class TakeScreenshotService extends Service {
@Override
public boolean onUnbind(Intent intent) {
if (mScreenshot != null) mScreenshot.stopScreenshot();
- // TODO (mkephart) remove once notifications flow is fully deprecated
+ // TODO remove once notifications flow is fully deprecated
if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
new file mode 100644
index 000000000000..825a7f3dbadb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.Context
+import android.os.UserHandle
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.Assert
+import java.lang.IllegalStateException
+
+/**
+ * Tracks a reference to the context for the current user
+ *
+ * Constructor is injected at SettingsModule
+ */
+class CurrentUserContextTracker internal constructor(
+ private val sysuiContext: Context,
+ broadcastDispatcher: BroadcastDispatcher
+) {
+ private val userTracker: CurrentUserTracker
+ private var initialized = false
+
+ private var _curUserContext: Context? = null
+ val currentUserContext: Context
+ get() {
+ if (!initialized) {
+ throw IllegalStateException("Must initialize before getting context")
+ }
+ return _curUserContext!!
+ }
+
+ init {
+ userTracker = object : CurrentUserTracker(broadcastDispatcher) {
+ override fun onUserSwitched(newUserId: Int) {
+ handleUserSwitched(newUserId)
+ }
+ }
+ }
+
+ fun initialize() {
+ initialized = true
+ _curUserContext = makeUserContext(userTracker.currentUserId)
+ userTracker.startTracking()
+ }
+
+ @VisibleForTesting
+ fun handleUserSwitched(newUserId: Int) {
+ _curUserContext = makeUserContext(newUserId)
+ }
+
+ private fun makeUserContext(uid: Int): Context {
+ Assert.isMainThread()
+ return sysuiContext.createContextAsUser(UserHandle.of(uid), 0)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
new file mode 100644
index 000000000000..2c5c3ceb6e66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.CurrentUserContextTracker;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for classes found within the com.android.systemui.settings package.
+ */
+@Module
+public interface SettingsModule {
+
+ /**
+ * Provides and initializes a CurrentUserContextTracker
+ */
+ @Singleton
+ @Provides
+ static CurrentUserContextTracker provideCurrentUserContextTracker(
+ Context context,
+ BroadcastDispatcher broadcastDispatcher) {
+ CurrentUserContextTracker tracker =
+ new CurrentUserContextTracker(context, broadcastDispatcher);
+ tracker.initialize();
+ return tracker;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index b71c4ebb5930..555202a2b02c 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -21,23 +21,24 @@ import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.RemoteException;
import android.provider.Settings;
import android.util.Slog;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.View;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import android.window.WindowOrganizer;
@@ -48,6 +49,8 @@ import com.android.systemui.R;
import com.android.systemui.SystemUI;
import com.android.systemui.TransactionPool;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.wm.DisplayChangeController;
import com.android.systemui.wm.DisplayController;
@@ -90,7 +93,6 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
private boolean mHomeStackResizable = false;
private ForcedResizableInfoActivityController mForcedResizableController;
private SystemWindows mSystemWindows;
- final SurfaceSession mSurfaceSession = new SurfaceSession();
private DisplayController mDisplayController;
private DisplayImeController mImeController;
final TransactionPool mTransactionPool;
@@ -259,11 +261,25 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
wct.setScreenSizeDp(mSplits.mSecondary.token,
mSplits.mSecondary.configuration.screenWidthDp,
mSplits.mSecondary.configuration.screenHeightDp);
+
+ wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mAdjustedPrimary);
+ adjustAppBounds = new Rect(mSplits.mPrimary.configuration
+ .windowConfiguration.getAppBounds());
+ adjustAppBounds.offset(0, mSplitLayout.mAdjustedPrimary.top
+ - mSplitLayout.mPrimary.top);
+ wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
+ wct.setScreenSizeDp(mSplits.mPrimary.token,
+ mSplits.mPrimary.configuration.screenWidthDp,
+ mSplits.mPrimary.configuration.screenHeightDp);
} else {
wct.setBounds(mSplits.mSecondary.token, mSplitLayout.mSecondary);
wct.setAppBounds(mSplits.mSecondary.token, null);
wct.setScreenSizeDp(mSplits.mSecondary.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
+ wct.setBounds(mSplits.mPrimary.token, mSplitLayout.mPrimary);
+ wct.setAppBounds(mSplits.mPrimary.token, null);
+ wct.setScreenSizeDp(mSplits.mPrimary.token,
+ SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
}
WindowOrganizer.applyTransaction(wct);
@@ -415,6 +431,21 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
}
private final DividerImeController mImePositionProcessor = new DividerImeController();
+ private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
+ @Override
+ public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+ boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+ if (!wasVisible || task.configuration.windowConfiguration.getWindowingMode()
+ != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || !mSplits.isSplitScreenSupported()) {
+ return;
+ }
+
+ if (isMinimized()) {
+ onUndockingTask();
+ }
+ }
+ };
+
public Divider(Context context, Optional<Lazy<Recents>> recentsOptionalLazy,
DisplayController displayController, SystemWindows systemWindows,
DisplayImeController imeController, Handler handler,
@@ -474,7 +505,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
return;
}
try {
- mSplits.init(mSurfaceSession);
+ mSplits.init();
// Set starting tile bounds based on middle target
final WindowContainerTransaction tct = new WindowContainerTransaction();
int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
@@ -485,7 +516,7 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
removeDivider();
return;
}
- update(mDisplayController.getDisplayContext(displayId).getResources().getConfiguration());
+ ActivityManagerWrapper.getInstance().registerTaskStackListener(mActivityRestartListener);
}
@Override
@@ -554,14 +585,29 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks,
}
private void update(Configuration configuration) {
+ final boolean isDividerHidden = mView != null && mView.isHidden();
+
removeDivider();
addDivider(configuration);
- if (mMinimized && mView != null) {
- mView.setMinimizedDockStack(true, mHomeStackResizable);
- updateTouchable();
+
+ if (mView != null) {
+ if (mMinimized) {
+ mView.setMinimizedDockStack(true, mHomeStackResizable);
+ updateTouchable();
+ }
+ mView.setHidden(isDividerHidden);
}
}
+ void onTaskVanished() {
+ mHandler.post(this::removeDivider);
+ }
+
+ void onTasksReady() {
+ mHandler.post(() -> update(mDisplayController.getDisplayContext(
+ mContext.getDisplayId()).getResources().getConfiguration()));
+ }
+
void updateVisibility(final boolean visible) {
if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
if (mVisible != visible) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index a4b1310687aa..717edc591d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -33,10 +33,8 @@ import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.window.TaskOrganizer;
-import java.util.ArrayList;
-
class SplitScreenTaskOrganizer extends TaskOrganizer {
- private static final String TAG = "SplitScreenTaskOrganizer";
+ private static final String TAG = "SplitScreenTaskOrg";
private static final boolean DEBUG = Divider.DEBUG;
RunningTaskInfo mPrimary;
@@ -45,44 +43,31 @@ class SplitScreenTaskOrganizer extends TaskOrganizer {
SurfaceControl mSecondarySurface;
SurfaceControl mPrimaryDim;
SurfaceControl mSecondaryDim;
- ArrayList<SurfaceControl> mHomeAndRecentsSurfaces = new ArrayList<>();
Rect mHomeBounds = new Rect();
final Divider mDivider;
private boolean mSplitScreenSupported = false;
+ final SurfaceSession mSurfaceSession = new SurfaceSession();
+
SplitScreenTaskOrganizer(Divider divider) {
mDivider = divider;
}
- void init(SurfaceSession session) throws RemoteException {
+ void init() throws RemoteException {
registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- try {
- mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
- mSecondary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
- WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
- mPrimarySurface = mPrimary.token.getLeash();
- mSecondarySurface = mSecondary.token.getLeash();
- } catch (Exception e) {
- // teardown to prevent callbacks
- unregisterOrganizer();
- throw e;
+ synchronized (this) {
+ try {
+ mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
+ WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+ mSecondary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
+ WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+ } catch (Exception e) {
+ // teardown to prevent callbacks
+ unregisterOrganizer();
+ throw e;
+ }
}
- mSplitScreenSupported = true;
-
- // Initialize dim surfaces:
- mPrimaryDim = new SurfaceControl.Builder(session).setParent(mPrimarySurface)
- .setColorLayer().setName("Primary Divider Dim").build();
- mSecondaryDim = new SurfaceControl.Builder(session).setParent(mSecondarySurface)
- .setColorLayer().setName("Secondary Divider Dim").build();
- SurfaceControl.Transaction t = getTransaction();
- t.setLayer(mPrimaryDim, Integer.MAX_VALUE);
- t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f});
- t.setLayer(mSecondaryDim, Integer.MAX_VALUE);
- t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
- t.apply();
- releaseTransaction(t);
}
boolean isSplitScreenSupported() {
@@ -98,6 +83,67 @@ class SplitScreenTaskOrganizer extends TaskOrganizer {
}
@Override
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ synchronized (this) {
+ if (mPrimary == null || mSecondary == null) {
+ Log.w(TAG, "Received onTaskAppeared before creating root tasks " + taskInfo);
+ return;
+ }
+
+ if (taskInfo.token.equals(mPrimary.token)) {
+ mPrimarySurface = leash;
+ } else if (taskInfo.token.equals(mSecondary.token)) {
+ mSecondarySurface = leash;
+ }
+
+ if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
+ mSplitScreenSupported = true;
+
+ // Initialize dim surfaces:
+ mPrimaryDim = new SurfaceControl.Builder(mSurfaceSession)
+ .setParent(mPrimarySurface).setColorLayer()
+ .setName("Primary Divider Dim").build();
+ mSecondaryDim = new SurfaceControl.Builder(mSurfaceSession)
+ .setParent(mSecondarySurface).setColorLayer()
+ .setName("Secondary Divider Dim").build();
+ SurfaceControl.Transaction t = getTransaction();
+ t.setLayer(mPrimaryDim, Integer.MAX_VALUE);
+ t.setColor(mPrimaryDim, new float[]{0f, 0f, 0f});
+ t.setLayer(mSecondaryDim, Integer.MAX_VALUE);
+ t.setColor(mSecondaryDim, new float[]{0f, 0f, 0f});
+ t.apply();
+ releaseTransaction(t);
+
+ mDivider.onTasksReady();
+ }
+ }
+ }
+
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ synchronized (this) {
+ final boolean isPrimaryTask = mPrimary != null
+ && taskInfo.token.equals(mPrimary.token);
+ final boolean isSecondaryTask = mSecondary != null
+ && taskInfo.token.equals(mSecondary.token);
+
+ if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) {
+ mSplitScreenSupported = false;
+
+ SurfaceControl.Transaction t = getTransaction();
+ t.remove(mPrimaryDim);
+ t.remove(mSecondaryDim);
+ t.remove(mPrimarySurface);
+ t.remove(mSecondarySurface);
+ t.apply();
+ releaseTransaction(t);
+
+ mDivider.onTaskVanished();
+ }
+ }
+ }
+
+ @Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
if (taskInfo.displayId != DEFAULT_DISPLAY) {
return;
@@ -110,6 +156,15 @@ class SplitScreenTaskOrganizer extends TaskOrganizer {
* presentations based on the contents of the split regions.
*/
private void handleTaskInfoChanged(RunningTaskInfo info) {
+ if (!mSplitScreenSupported) {
+ // This shouldn't happen; but apparently there is a chance that SysUI crashes without
+ // system server receiving binder-death (or maybe it receives binder-death too late?).
+ // In this situation, when sys-ui restarts, the split root-tasks will still exist so
+ // there is a small window of time during init() where WM might send messages here
+ // before init() fails. So, avoid a cycle of crashes by returning early.
+ Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
+ return;
+ }
final boolean secondaryWasHomeOrRecents = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
|| mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS;
final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 85dcbb6316d0..3027bd225216 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -174,18 +174,19 @@ public class WindowManagerProxy {
if (rootTasks.isEmpty()) {
return false;
}
- tiles.mHomeAndRecentsSurfaces.clear();
for (int i = rootTasks.size() - 1; i >= 0; --i) {
final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
- if (isHomeOrRecentTask(rootTask)) {
- tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash());
- }
+ // Only move resizeable task to split secondary. WM will just ignore this anyways...
+ if (!rootTask.isResizable()) continue;
+ // Only move fullscreen tasks to split secondary.
if (rootTask.configuration.windowConfiguration.getWindowingMode()
!= WINDOWING_MODE_FULLSCREEN) {
continue;
}
wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
}
+ // Move the secondary split-forward.
+ wct.reorder(tiles.mSecondary.token, true /* onTop */);
boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
WindowOrganizer.applyTransaction(wct);
return isHomeResizable;
@@ -206,7 +207,6 @@ public class WindowManagerProxy {
// Set launch root first so that any task created after getChildContainers and
// before reparent (pretty unlikely) are put into fullscreen.
TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
- tiles.mHomeAndRecentsSurfaces.clear();
// TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
// plus specific APIs to clean this up.
List<ActivityManager.RunningTaskInfo> primaryChildren =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5475812b5563..43b47232d27d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -311,6 +311,7 @@ public class KeyguardIndicationController implements StateListener,
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) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 1b7524521d76..8c24c540e743 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.NavigationBarFragment;
import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NavigationModeController;
import com.android.systemui.statusbar.policy.BatteryController;
import javax.inject.Inject;
@@ -139,7 +140,8 @@ public class NavigationBarController implements Callbacks {
? Dependency.get(LightBarController.class)
: new LightBarController(context,
Dependency.get(DarkIconDispatcher.class),
- Dependency.get(BatteryController.class));
+ Dependency.get(BatteryController.class),
+ Dependency.get(NavigationModeController.class));
navBar.setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index ba3db0937422..670a65f55844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -148,7 +148,7 @@ public class NotificationHeaderUtil {
}
public void updateChildrenHeaderAppearance() {
- List<ExpandableNotificationRow> notificationChildren = mRow.getNotificationChildren();
+ List<ExpandableNotificationRow> notificationChildren = mRow.getAttachedChildren();
if (notificationChildren == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 12298817d5a6..2647c04ff586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -19,11 +19,13 @@ import static android.app.Notification.VISIBILITY_SECRET;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_MEDIA_CONTROLS;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -182,7 +184,7 @@ public class NotificationLockscreenUserManagerImpl implements
protected final Context mContext;
private final Handler mMainHandler;
protected final SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
- protected final ArrayList<UserInfo> mCurrentManagedProfiles = new ArrayList<>();
+ protected final SparseArray<UserInfo> mCurrentManagedProfiles = new SparseArray<>();
protected int mCurrentUserId = 0;
protected NotificationPresenter mPresenter;
@@ -351,7 +353,10 @@ public class NotificationLockscreenUserManagerImpl implements
boolean exceedsPriorityThreshold;
if (NotificationUtils.useNewInterruptionModel(mContext)
&& hideSilentNotificationsOnLockscreen()) {
- exceedsPriorityThreshold = entry.getBucket() != BUCKET_SILENT;
+ exceedsPriorityThreshold =
+ entry.getBucket() == BUCKET_MEDIA_CONTROLS
+ || (entry.getBucket() != BUCKET_SILENT
+ && entry.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT);
} else {
exceedsPriorityThreshold = !entry.getRanking().isAmbient();
}
@@ -425,8 +430,9 @@ public class NotificationLockscreenUserManagerImpl implements
*/
public boolean allowsManagedPrivateNotificationsInPublic() {
synchronized (mLock) {
- for (UserInfo profile : mCurrentManagedProfiles) {
- if (!userAllowsPrivateNotificationsInPublic(profile.id)) {
+ for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
+ if (!userAllowsPrivateNotificationsInPublic(
+ mCurrentManagedProfiles.valueAt(i).id)) {
return false;
}
}
@@ -490,15 +496,22 @@ public class NotificationLockscreenUserManagerImpl implements
public boolean needsRedaction(NotificationEntry ent) {
int userId = ent.getSbn().getUserId();
- boolean currentUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
- boolean notiUserWantsRedaction = !userAllowsPrivateNotificationsInPublic(userId);
- boolean redactedLockscreen = currentUserWantsRedaction || notiUserWantsRedaction;
+ boolean isCurrentUserRedactingNotifs =
+ !userAllowsPrivateNotificationsInPublic(mCurrentUserId);
+ boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId);
+ boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId);
+
+ // redact notifications if the current user is redacting notifications; however if the
+ // notification is associated with a managed profile, we rely on the managed profile
+ // setting to determine whether to redact it
+ boolean isNotifRedacted = (!isNotifForManagedProfile && isCurrentUserRedactingNotifs)
+ || isNotifUserRedacted;
boolean notificationRequestsRedaction =
ent.getSbn().getNotification().visibility == Notification.VISIBILITY_PRIVATE;
boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
- return userForcesRedaction || notificationRequestsRedaction && redactedLockscreen;
+ return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
}
private boolean packageHasVisibilityOverride(String key) {
@@ -519,7 +532,7 @@ public class NotificationLockscreenUserManagerImpl implements
for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) {
mCurrentProfiles.put(user.id, user);
if (UserManager.USER_TYPE_PROFILE_MANAGED.equals(user.userType)) {
- mCurrentManagedProfiles.add(user);
+ mCurrentManagedProfiles.put(user.id, user);
}
}
}
@@ -551,7 +564,7 @@ public class NotificationLockscreenUserManagerImpl implements
public boolean isAnyManagedProfilePublicMode() {
synchronized (mLock) {
for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
- if (isLockscreenPublicMode(mCurrentManagedProfiles.get(i).id)) {
+ if (isLockscreenPublicMode(mCurrentManagedProfiles.valueAt(i).id)) {
return true;
}
}
@@ -668,8 +681,8 @@ public class NotificationLockscreenUserManagerImpl implements
}
pw.print(" mCurrentManagedProfiles=");
synchronized (mLock) {
- for (UserInfo userInfo : mCurrentManagedProfiles) {
- pw.print("" + userInfo.id + " ");
+ for (int i = mCurrentManagedProfiles.size() - 1; i >= 0; i--) {
+ pw.print("" + mCurrentManagedProfiles.valueAt(i).id + " ");
}
}
pw.println();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 0d7715958995..25f1a974bc36 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -250,7 +250,8 @@ class NotificationShadeDepthController @Inject constructor(
private fun updateShadeBlur() {
var newBlur = 0
val state = statusBarStateController.state
- if (state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) {
+ if ((state == StatusBarState.SHADE || state == StatusBarState.SHADE_LOCKED) &&
+ !keyguardStateController.isKeyguardFadingAway) {
newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
}
shadeSpring.animateTo(newBlur)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 1297f996b743..8fcc67a0708e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -60,7 +61,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
private final Handler mHandler;
- /** Re-usable map of notifications to their sorted children.*/
+ /** Re-usable map of top-level notifications to their sorted children if any.*/
private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
new HashMap<>();
@@ -71,6 +72,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
protected final VisualStabilityManager mVisualStabilityManager;
private final SysuiStatusBarStateController mStatusBarStateController;
private final NotificationEntryManager mEntryManager;
+ private final LowPriorityInflationHelper mLowPriorityInflationHelper;
/**
* {@code true} if notifications not part of a group should by default be rendered in their
@@ -108,7 +110,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
BubbleController bubbleController,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController,
- DynamicChildBindController dynamicChildBindController) {
+ DynamicChildBindController dynamicChildBindController,
+ LowPriorityInflationHelper lowPriorityInflationHelper) {
mContext = context;
mHandler = mainHandler;
mLockscreenUserManager = notificationLockscreenUserManager;
@@ -123,14 +126,15 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
mBubbleController = bubbleController;
mDynamicPrivacyController = privacyController;
- privacyController.addListener(this);
mDynamicChildBindController = dynamicChildBindController;
+ mLowPriorityInflationHelper = lowPriorityInflationHelper;
}
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer) {
mPresenter = presenter;
mListContainer = listContainer;
+ mDynamicPrivacyController.addListener(this);
}
/**
@@ -177,6 +181,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
currentUserId);
ent.setSensitive(sensitive, deviceSensitive);
ent.getRow().setNeedsRedaction(needsRedaction);
+ mLowPriorityInflationHelper.recheckLowPriorityViewAndInflate(ent, ent.getRow());
boolean isChildInGroup = mGroupManager.isChildInGroupWithSummary(ent.getSbn());
boolean groupChangesAllowed = mVisualStabilityManager.areGroupChangesAllowed()
@@ -206,6 +211,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
orderedChildren.add(ent);
} else {
+ // Top-level notif
+ mTmpChildOrderMap.put(ent, null);
toShow.add(ent.getRow());
}
}
@@ -283,7 +290,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
- mDynamicChildBindController.updateChildContentViews(mTmpChildOrderMap);
+ mDynamicChildBindController.updateContentViews(mTmpChildOrderMap);
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
@@ -307,17 +314,20 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
-
- for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
- childIndex++) {
+ if (orderedChildren == null) {
+ // Not a group
+ continue;
+ }
+ parent.setUntruncatedChildCount(orderedChildren.size());
+ for (int childIndex = 0; childIndex < orderedChildren.size(); childIndex++) {
ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
if (children == null || !children.contains(childView)) {
if (childView.getParent() != null) {
- Log.wtf(TAG, "trying to add a notification child that already has " +
- "a parent. class:" + childView.getParent().getClass() +
- "\n child: " + childView);
+ Log.wtf(TAG, "trying to add a notification child that already has "
+ + "a parent. class:" + childView.getParent().getClass()
+ + "\n child: " + childView);
// This shouldn't happen. We can recover by removing it though.
((ViewGroup) childView.getParent()).removeView(childView);
}
@@ -349,7 +359,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
- List<ExpandableNotificationRow> children = parent.getNotificationChildren();
+ List<ExpandableNotificationRow> children = parent.getAttachedChildren();
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
if (children != null) {
@@ -454,7 +464,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
if (row.isSummaryWithChildren()) {
List<ExpandableNotificationRow> notificationChildren =
- row.getNotificationChildren();
+ row.getAttachedChildren();
int size = notificationChildren.size();
for (int i = size - 1; i >= 0; i--) {
stack.push(notificationChildren.get(i));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index e81e5cae5bfc..229aa6d98e0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -24,6 +24,7 @@ import android.util.Log;
import android.view.animation.Interpolator;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.UiEventLogger;
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.Interpolators;
@@ -69,6 +70,7 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
};
private final ArrayList<RankedListener> mListeners = new ArrayList<>();
+ private final UiEventLogger mUiEventLogger;
private int mState;
private int mLastState;
private boolean mLeaveOpenOnKeyguardHide;
@@ -119,7 +121,8 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
private Interpolator mDozeInterpolator = Interpolators.FAST_OUT_SLOW_IN;
@Inject
- public StatusBarStateControllerImpl() {
+ public StatusBarStateControllerImpl(UiEventLogger uiEventLogger) {
+ mUiEventLogger = uiEventLogger;
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistoricalRecords[i] = new HistoricalState();
}
@@ -155,6 +158,7 @@ public class StatusBarStateControllerImpl implements SysuiStatusBarStateControll
}
mLastState = mState;
mState = state;
+ mUiEventLogger.log(StatusBarStateEvent.fromState(mState));
for (RankedListener rl : new ArrayList<>(mListeners)) {
rl.mListener.onStateChanged(mState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.java
new file mode 100644
index 000000000000..8330169980d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateEvent.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.statusbar;
+
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+
+/**
+ * Events for changes in the {@link StatusBarState}.
+ */
+public enum StatusBarStateEvent implements UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "StatusBarState changed to unknown state")
+ STATUS_BAR_STATE_UNKNOWN(428),
+
+ @UiEvent(doc = "StatusBarState changed to SHADE state")
+ STATUS_BAR_STATE_SHADE(429),
+
+ @UiEvent(doc = "StatusBarState changed to KEYGUARD state")
+ STATUS_BAR_STATE_KEYGUARD(430),
+
+ @UiEvent(doc = "StatusBarState changed to SHADE_LOCKED state")
+ STATUS_BAR_STATE_SHADE_LOCKED(431),
+
+ @UiEvent(doc = "StatusBarState changed to FULLSCREEN_USER_SWITCHER state")
+ STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER(432);
+
+ private int mId;
+ StatusBarStateEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Return the event associated with the state.
+ */
+ public static StatusBarStateEvent fromState(int state) {
+ switch(state) {
+ case StatusBarState.SHADE:
+ return STATUS_BAR_STATE_SHADE;
+ case StatusBarState.KEYGUARD:
+ return STATUS_BAR_STATE_KEYGUARD;
+ case StatusBarState.SHADE_LOCKED:
+ return STATUS_BAR_STATE_SHADE_LOCKED;
+ case StatusBarState.FULLSCREEN_USER_SWITCHER:
+ return STATUS_BAR_STATE_FULLSCREEN_USER_SWITCHER;
+ default:
+ return STATUS_BAR_STATE_UNKNOWN;
+ }
+ }
+}
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 e64b423aab60..de7e36d97b22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarDependenciesModule.java
@@ -37,6 +37,7 @@ import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.inflation.LowPriorityInflationHelper;
import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -143,7 +144,8 @@ public interface StatusBarDependenciesModule {
BubbleController bubbleController,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController,
- DynamicChildBindController dynamicChildBindController) {
+ DynamicChildBindController dynamicChildBindController,
+ LowPriorityInflationHelper lowPriorityInflationHelper) {
return new NotificationViewHierarchyManager(
context,
mainHandler,
@@ -156,7 +158,8 @@ public interface StatusBarDependenciesModule {
bubbleController,
privacyController,
fgsSectionController,
- dynamicChildBindController);
+ dynamicChildBindController,
+ lowPriorityInflationHelper);
}
/**
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 6aef6b407f37..85560fefd952 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.graphics.Matrix;
import android.graphics.Rect;
@@ -41,6 +42,8 @@ import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
+import java.util.concurrent.Executor;
+
/**
* A class that allows activities to be launched in a seamless way where the notification
* transforms nicely into the starting window.
@@ -59,6 +62,7 @@ public class ActivityLaunchAnimator {
private final float mWindowCornerRadius;
private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
private final NotificationShadeDepthController mDepthController;
+ private final Executor mMainExecutor;
private Callback mCallback;
private final Runnable mTimeoutRunnable = () -> {
setAnimationPending(false);
@@ -73,12 +77,14 @@ public class ActivityLaunchAnimator {
Callback callback,
NotificationPanelViewController notificationPanel,
NotificationShadeDepthController depthController,
- NotificationListContainer container) {
+ NotificationListContainer container,
+ Executor mainExecutor) {
mNotificationPanel = notificationPanel;
mNotificationContainer = container;
mDepthController = depthController;
mNotificationShadeWindowViewController = notificationShadeWindowViewController;
mCallback = callback;
+ mMainExecutor = mainExecutor;
mWindowCornerRadius = ScreenDecorationsUtils
.getWindowCornerRadius(mNotificationShadeWindowViewController.getView()
.getResources());
@@ -91,7 +97,7 @@ public class ActivityLaunchAnimator {
return null;
}
AnimationRunner animationRunner = new AnimationRunner(
- (ExpandableNotificationRow) sourceView);
+ (ExpandableNotificationRow) sourceView, mMainExecutor);
return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION,
ANIMATION_DURATION - 150 /* statusBarTransitionDelay */);
}
@@ -134,17 +140,18 @@ public class ActivityLaunchAnimator {
class AnimationRunner extends IRemoteAnimationRunner.Stub {
- private final ExpandableNotificationRow mSourceNotification;
- private final ExpandAnimationParameters mParams;
+ private final ExpandAnimationParameters mParams = new ExpandAnimationParameters();
private final Rect mWindowCrop = new Rect();
private final float mNotificationCornerRadius;
+ private final Executor mMainExecutor;
+ @Nullable private ExpandableNotificationRow mSourceNotification;
+ @Nullable private SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier;
private float mCornerRadius;
private boolean mIsFullScreenLaunch = true;
- private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier;
- public AnimationRunner(ExpandableNotificationRow sourceNofitication) {
- mSourceNotification = sourceNofitication;
- mParams = new ExpandAnimationParameters();
+ AnimationRunner(ExpandableNotificationRow sourceNotification, Executor mainExecutor) {
+ mMainExecutor = mainExecutor;
+ mSourceNotification = sourceNotification;
mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification);
mNotificationCornerRadius = Math.max(mSourceNotification.getCurrentTopRoundness(),
mSourceNotification.getCurrentBottomRoundness());
@@ -155,13 +162,15 @@ public class ActivityLaunchAnimator {
RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
throws RemoteException {
- mSourceNotification.post(() -> {
+ mMainExecutor.execute(() -> {
RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget(
remoteAnimationTargets);
- if (primary == null) {
+ if (primary == null || mSourceNotification == null) {
setAnimationPending(false);
invokeCallback(iRemoteAnimationFinishedCallback);
mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mSourceNotification = null;
+ mSyncRtTransactionApplier = null;
return;
}
@@ -172,28 +181,14 @@ public class ActivityLaunchAnimator {
if (!mIsFullScreenLaunch) {
mNotificationPanel.collapseWithDuration(ANIMATION_DURATION);
}
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- mParams.startPosition = mSourceNotification.getLocationOnScreen();
- mParams.startTranslationZ = mSourceNotification.getTranslationZ();
- mParams.startClipTopAmount = mSourceNotification.getClipTopAmount();
- if (mSourceNotification.isChildInGroup()) {
- int parentClip = mSourceNotification
- .getNotificationParent().getClipTopAmount();
- mParams.parentStartClipTopAmount = parentClip;
- // We need to calculate how much the child is clipped by the parent
- // because children always have 0 clipTopAmount
- if (parentClip != 0) {
- float childClip = parentClip
- - mSourceNotification.getTranslationY();
- if (childClip > 0.0f) {
- mParams.startClipTopAmount = (int) Math.ceil(childClip);
- }
- }
- }
- int targetWidth = primary.sourceContainerBounds.width();
- int notificationHeight = mSourceNotification.getActualHeight()
+ mParams.initFrom(mSourceNotification);
+ final int targetWidth = primary.sourceContainerBounds.width();
+ final int notificationHeight;
+ final int notificationWidth;
+ notificationHeight = mSourceNotification.getActualHeight()
- mSourceNotification.getClipBottomAmount();
- int notificationWidth = mSourceNotification.getWidth();
+ notificationWidth = mSourceNotification.getWidth();
+ ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
anim.setDuration(ANIMATION_DURATION);
anim.setInterpolator(Interpolators.LINEAR);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@@ -231,6 +226,11 @@ public class ActivityLaunchAnimator {
});
}
+ @Nullable
+ ExpandableNotificationRow getRow() {
+ return mSourceNotification;
+ }
+
private void invokeCallback(IRemoteAnimationFinishedCallback callback) {
try {
callback.onAnimationFinished();
@@ -253,7 +253,9 @@ public class ActivityLaunchAnimator {
private void setExpandAnimationRunning(boolean running) {
mNotificationPanel.setLaunchingNotification(running);
- mSourceNotification.setExpandAnimationRunning(running);
+ if (mSourceNotification != null) {
+ mSourceNotification.setExpandAnimationRunning(running);
+ }
mNotificationShadeWindowViewController.setExpandAnimationRunning(running);
mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
mAnimationRunning = running;
@@ -261,6 +263,8 @@ public class ActivityLaunchAnimator {
mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
applyParamsToNotification(null);
applyParamsToNotificationShade(null);
+ mSourceNotification = null;
+ mSyncRtTransactionApplier = null;
}
}
@@ -272,7 +276,9 @@ public class ActivityLaunchAnimator {
}
private void applyParamsToNotification(ExpandAnimationParameters params) {
- mSourceNotification.applyExpandAnimationParams(params);
+ if (mSourceNotification != null) {
+ mSourceNotification.applyExpandAnimationParams(params);
+ }
}
private void applyParamsToWindow(RemoteAnimationTarget app) {
@@ -287,14 +293,18 @@ public class ActivityLaunchAnimator {
.withCornerRadius(mCornerRadius)
.withVisibility(true)
.build();
- mSyncRtTransactionApplier.scheduleApply(true /* earlyWakeup */, params);
+ if (mSyncRtTransactionApplier != null) {
+ mSyncRtTransactionApplier.scheduleApply(true /* earlyWakeup */, params);
+ }
}
@Override
public void onAnimationCancelled() throws RemoteException {
- mSourceNotification.post(() -> {
+ mMainExecutor.execute(() -> {
setAnimationPending(false);
mCallback.onLaunchAnimationCancelled();
+ mSourceNotification = null;
+ mSyncRtTransactionApplier = null;
});
}
};
@@ -359,6 +369,28 @@ public class ActivityLaunchAnimator {
public float getStartTranslationZ() {
return startTranslationZ;
}
+
+ /** Initialize with data pulled from the row. */
+ void initFrom(@Nullable ExpandableNotificationRow row) {
+ if (row == null) {
+ return;
+ }
+ startPosition = row.getLocationOnScreen();
+ startTranslationZ = row.getTranslationZ();
+ startClipTopAmount = row.getClipTopAmount();
+ if (row.isChildInGroup()) {
+ int parentClip = row.getNotificationParent().getClipTopAmount();
+ parentStartClipTopAmount = parentClip;
+ // We need to calculate how much the child is clipped by the parent
+ // because children always have 0 clipTopAmount
+ if (parentClip != 0) {
+ float childClip = parentClip - row.getTranslationY();
+ if (childClip > 0.0f) {
+ startClipTopAmount = (int) Math.ceil(childClip);
+ }
+ }
+ }
+ }
}
public interface Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
index 148cdea92052..57b41f36e51f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
@@ -63,44 +63,52 @@ public class DynamicChildBindController {
}
/**
- * Update the child content views, unbinding content views on children that won't be visible
- * and binding content views on children that will be visible eventually.
+ * Update the content views, unbinding content views on children that won't be visible
+ * and binding content views on children that will be visible eventually and previously unbound
+ * children that are no longer children.
*
- * @param groupNotifs map of notification summaries to their children
+ * @param groupNotifs map of top-level notifs to their children, if any
*/
- public void updateChildContentViews(
+ public void updateContentViews(
Map<NotificationEntry, List<NotificationEntry>> groupNotifs) {
for (NotificationEntry entry : groupNotifs.keySet()) {
List<NotificationEntry> children = groupNotifs.get(entry);
+ if (children == null) {
+ if (!hasContent(entry)) {
+ // Case where child is updated to be top level
+ bindContent(entry);
+ }
+ continue;
+ }
for (int j = 0; j < children.size(); j++) {
NotificationEntry childEntry = children.get(j);
if (j >= mChildBindCutoff) {
- if (hasChildContent(childEntry)) {
- freeChildContent(childEntry);
+ if (hasContent(childEntry)) {
+ freeContent(childEntry);
}
} else {
- if (!hasChildContent(childEntry)) {
- bindChildContent(childEntry);
+ if (!hasContent(childEntry)) {
+ bindContent(childEntry);
}
}
}
}
}
- private boolean hasChildContent(NotificationEntry entry) {
+ private boolean hasContent(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
return row.getPrivateLayout().getContractedChild() != null
|| row.getPrivateLayout().getExpandedChild() != null;
}
- private void freeChildContent(NotificationEntry entry) {
+ private void freeContent(NotificationEntry entry) {
RowContentBindParams params = mStage.getStageParams(entry);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
mStage.requestRebind(entry, null);
}
- private void bindChildContent(NotificationEntry entry) {
+ private void bindContent(NotificationEntry entry) {
RowContentBindParams params = mStage.getStageParams(entry);
params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
index ba1b23bd80ed..5748c4aa0b13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationActivityStarter.java
@@ -33,6 +33,8 @@ public interface NotificationActivityStarter {
void startNotificationGutsIntent(Intent intent, int appUid,
ExpandableNotificationRow row);
+ void startHistoryIntent(boolean showHistory);
+
default boolean isCollapsingToShowActivityOverLockscreen() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java
new file mode 100644
index 000000000000..1c2a00ed601a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationChannelHelper.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * Helps SystemUI create notification channels.
+ */
+public class NotificationChannelHelper {
+ private static final String TAG = "NotificationChannelHelper";
+
+ /** Creates a conversation channel based on the shortcut info or notification title. */
+ public static NotificationChannel createConversationChannelIfNeeded(
+ Context context,
+ INotificationManager notificationManager,
+ NotificationEntry entry,
+ NotificationChannel channel) {
+ if (!TextUtils.isEmpty(channel.getConversationId())) {
+ return channel;
+ }
+ final String conversationId = entry.getSbn().getShortcutId();
+ final String pkg = entry.getSbn().getPackageName();
+ final int appUid = entry.getSbn().getUid();
+ if (TextUtils.isEmpty(conversationId) || TextUtils.isEmpty(pkg)
+ || entry.getRanking().getShortcutInfo() == null) {
+ return channel;
+ }
+
+ // If this channel is not already a customized conversation channel, create
+ // a custom channel
+ try {
+ channel.setName(getName(entry));
+ notificationManager.createConversationNotificationChannelForPackage(
+ pkg, appUid, entry.getSbn().getKey(), channel,
+ conversationId);
+ channel = notificationManager.getConversationNotificationChannel(
+ context.getOpPackageName(), UserHandle.getUserId(appUid), pkg,
+ channel.getId(), false, conversationId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not create conversation channel", e);
+ }
+ return channel;
+ }
+
+ private static String getName(NotificationEntry entry) {
+ if (entry.getRanking().getShortcutInfo().getShortLabel() != null) {
+ return entry.getRanking().getShortcutInfo().getShortLabel().toString();
+ }
+ Bundle extras = entry.getSbn().getNotification().extras;
+ String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = extras.getString(Notification.EXTRA_TITLE);
+ }
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = "fallback";
+ }
+ return nameString;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 4e6df0ad1ba4..d364689a65d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -23,11 +23,14 @@ import android.view.View;
import com.android.systemui.DejankUtils;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.Optional;
+import javax.inject.Inject;
+
/**
* Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret,
* app ops icon, etc) are handled elsewhere.
@@ -35,15 +38,19 @@ import java.util.Optional;
public final class NotificationClicker implements View.OnClickListener {
private static final String TAG = "NotificationClicker";
- private final Optional<StatusBar> mStatusBar;
private final BubbleController mBubbleController;
+ private final NotificationClickerLogger mLogger;
+ private final Optional<StatusBar> mStatusBar;
private final NotificationActivityStarter mNotificationActivityStarter;
- public NotificationClicker(Optional<StatusBar> statusBar,
+ private NotificationClicker(
BubbleController bubbleController,
+ NotificationClickerLogger logger,
+ Optional<StatusBar> statusBar,
NotificationActivityStarter notificationActivityStarter) {
- mStatusBar = statusBar;
mBubbleController = bubbleController;
+ mLogger = logger;
+ mStatusBar = statusBar;
mNotificationActivityStarter = notificationActivityStarter;
}
@@ -58,25 +65,26 @@ public final class NotificationClicker implements View.OnClickListener {
SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- final StatusBarNotification sbn = row.getEntry().getSbn();
- if (sbn == null) {
- Log.e(TAG, "NotificationClicker called on an unclickable notification,");
- return;
- }
+ final NotificationEntry entry = row.getEntry();
+ mLogger.logOnClick(entry);
// Check if the notification is displaying the menu, if so slide notification back
if (isMenuVisible(row)) {
+ mLogger.logMenuVisible(entry);
row.animateTranslateNotification(0);
return;
} else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
+ mLogger.logParentMenuVisible(entry);
row.getNotificationParent().animateTranslateNotification(0);
return;
} else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
// We never want to open the app directly if the user clicks in between
// the notifications.
+ mLogger.logChildrenExpanded(entry);
return;
} else if (row.areGutsExposed()) {
// ignore click if guts are exposed
+ mLogger.logGutsExposed(entry);
return;
}
@@ -88,7 +96,7 @@ public final class NotificationClicker implements View.OnClickListener {
mBubbleController.collapseStack();
}
- mNotificationActivityStarter.onNotificationClicked(sbn, row);
+ mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
}
private boolean isMenuVisible(ExpandableNotificationRow row) {
@@ -107,4 +115,30 @@ public final class NotificationClicker implements View.OnClickListener {
row.setOnClickListener(null);
}
}
+
+ /** Daggerized builder for NotificationClicker. */
+ public static class Builder {
+ private final BubbleController mBubbleController;
+ private final NotificationClickerLogger mLogger;
+
+ @Inject
+ public Builder(
+ BubbleController bubbleController,
+ NotificationClickerLogger logger) {
+ mBubbleController = bubbleController;
+ mLogger = logger;
+ }
+
+ /** Builds an instance. */
+ public NotificationClicker build(
+ Optional<StatusBar> statusBar,
+ NotificationActivityStarter notificationActivityStarter
+ ) {
+ return new NotificationClicker(
+ mBubbleController,
+ mLogger,
+ statusBar,
+ notificationActivityStarter);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
new file mode 100644
index 000000000000..fbf033bd2291
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+class NotificationClickerLogger @Inject constructor(
+ @NotifInteractionLog private val buffer: LogBuffer
+) {
+ fun logOnClick(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ str2 = entry.ranking.channel.id
+ }, {
+ "CLICK $str1 (channel=$str2)"
+ })
+ }
+
+ fun logMenuVisible(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; menu is visible"
+ })
+ }
+
+ fun logParentMenuVisible(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; parent menu is visible"
+ })
+ }
+
+ fun logChildrenExpanded(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; children are expanded"
+ })
+ }
+
+ fun logGutsExposed(entry: NotificationEntry) {
+ buffer.log(TAG, LogLevel.DEBUG, {
+ str1 = entry.key
+ }, {
+ "Ignoring click on $str1; guts are exposed"
+ })
+ }
+}
+
+private const val TAG = "NotificationClicker"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index d37e16b17620..f1cb783742bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -515,7 +515,7 @@ public class NotificationEntryManager implements
// always cancelled. We only remove them if they were dismissed by the user.
return;
}
- List<NotificationEntry> childEntries = entry.getChildren();
+ List<NotificationEntry> childEntries = entry.getAttachedNotifChildren();
if (childEntries == null) {
return;
}
@@ -619,7 +619,8 @@ public class NotificationEntryManager implements
entry.setSbn(notification);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
- } mGroupManager.onEntryUpdated(entry, oldSbn);
+ }
+ mGroupManager.onEntryUpdated(entry, oldSbn);
mLogger.logNotifUpdated(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
@@ -699,7 +700,8 @@ public class NotificationEntryManager implements
entry,
oldImportances.get(entry.getKey()),
oldAdjustments.get(entry.getKey()),
- NotificationUiAdjustment.extractFromNotificationEntry(entry));
+ NotificationUiAdjustment.extractFromNotificationEntry(entry),
+ mInflationCallback);
}
updateNotifications("updateNotificationRanking");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index 2c747bdcf7b6..81494eddd989 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
import java.util.ArrayList;
import java.util.Collections;
@@ -36,6 +37,7 @@ public class GroupEntry extends ListEntry {
private final List<NotificationEntry> mUnmodifiableChildren =
Collections.unmodifiableList(mChildren);
+ private int mUntruncatedChildCount;
@VisibleForTesting
public GroupEntry(String key) {
@@ -62,6 +64,24 @@ public class GroupEntry extends ListEntry {
mSummary = summary;
}
+ /**
+ * @see #getUntruncatedChildCount()
+ */
+ public void setUntruncatedChildCount(int childCount) {
+ mUntruncatedChildCount = childCount;
+ }
+
+ /**
+ * Get the untruncated number of children from the data model, including those that will not
+ * have views bound. This includes children that {@link PreparationCoordinator} will filter out
+ * entirely when they are beyond the last visible child.
+ *
+ * TODO: This should move to some shared class between the model and view hierarchy
+ */
+ public int getUntruncatedChildCount() {
+ return mUntruncatedChildCount;
+ }
+
void clearChildren() {
mChildren.clear();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt
index cf670bd5a424..339809e0770b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifViewManager.kt
@@ -113,7 +113,7 @@ class NotifViewManager @Inject constructor(
} else if (entries[idx] is GroupEntry) {
// A top-level entry exists. If it's a group, diff the children
val groupChildren = (entries[idx] as GroupEntry).children
- listItem.notificationChildren?.forEach { listChild ->
+ listItem.attachedChildren?.forEach { listChild ->
if (!groupChildren.contains(listChild.entry)) {
listItem.removeChildNotification(listChild)
@@ -155,8 +155,8 @@ class NotifViewManager @Inject constructor(
for ((idx, childEntry) in entry.children.withIndex()) {
val childListItem = rowRegistry.requireView(childEntry)
// Child hasn't been added yet. add it!
- if (listItem.notificationChildren == null ||
- !listItem.notificationChildren.contains(childListItem)) {
+ if (listItem.attachedChildren == null ||
+ !listItem.attachedChildren.contains(childListItem)) {
// TODO: old code here just Log.wtf()'d here. This might wreak havoc
if (childListItem.view.parent != null) {
throw IllegalStateException("trying to add a notification child that " +
@@ -179,6 +179,7 @@ class NotifViewManager @Inject constructor(
stabilityManager,
null /*TODO: stability callback */
)
+ listItem.setUntruncatedChildCount(entry.untruncatedChildCount)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 749c3e4c9d0d..cb0c2838c24d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -378,6 +378,7 @@ public final class NotificationEntry extends ListEntry {
/**
* Returns the data needed for a bubble for this notification, if it exists.
*/
+ @Nullable
public Notification.BubbleMetadata getBubbleMetadata() {
return mBubbleMetadata;
}
@@ -385,7 +386,7 @@ public final class NotificationEntry extends ListEntry {
/**
* Sets bubble metadata for this notification.
*/
- public void setBubbleMetadata(Notification.BubbleMetadata metadata) {
+ public void setBubbleMetadata(@Nullable Notification.BubbleMetadata metadata) {
mBubbleMetadata = metadata;
}
@@ -434,13 +435,18 @@ public final class NotificationEntry extends ListEntry {
mRowController = controller;
}
- @Nullable
- public List<NotificationEntry> getChildren() {
+ /**
+ * Get the children that are actually attached to this notification's row.
+ *
+ * TODO: Seems like most callers here should probably be using
+ * {@link com.android.systemui.statusbar.phone.NotificationGroupManager#getChildren}
+ */
+ public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
if (row == null) {
return null;
}
- List<ExpandableNotificationRow> rowChildren = row.getNotificationChildren();
+ List<ExpandableNotificationRow> rowChildren = row.getAttachedChildren();
if (rowChildren == null) {
return null;
}
@@ -748,7 +754,7 @@ public final class NotificationEntry extends ListEntry {
return false;
}
- List<NotificationEntry> children = getChildren();
+ List<NotificationEntry> children = getAttachedNotifChildren();
if (children != null && children.size() > 0) {
for (int i = 0; i < children.size(); i++) {
NotificationEntry child = children.get(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index ec17f4ed868c..9738bcc69279 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -34,12 +34,13 @@ import com.android.systemui.statusbar.notification.stack.NotificationSectionsMan
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
+
import com.android.systemui.statusbar.phone.NotificationGroupManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import dagger.Lazy
-import java.util.Comparator
-import java.util.Objects
+import java.util.Objects;
import javax.inject.Inject
+import kotlin.Comparator
private const val TAG = "NotifRankingManager"
@@ -90,19 +91,13 @@ open class NotificationRankingManager @Inject constructor(
val aIsHighPriority = a.isHighPriority()
val bIsHighPriority = b.isHighPriority()
-
when {
aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
// Provide consistent ranking with headsUpManager
aHeadsUp -> headsUpManager.compare(a, b)
- usePeopleFiltering && aPersonType != bPersonType -> when (aPersonType) {
- TYPE_IMPORTANT_PERSON -> -1
- TYPE_PERSON -> when (bPersonType) {
- TYPE_IMPORTANT_PERSON -> 1
- else -> -1
- }
- else -> 1
- }
+
+ usePeopleFiltering && aPersonType != bPersonType ->
+ peopleNotificationIdentifier.compareTo(aPersonType, bPersonType)
// Upsort current media notification.
aMedia != bMedia -> if (aMedia) -1 else 1
// Upsort PRIORITY_MAX system notifications
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
index 2a3b2b7d815d..3fde2ed249d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.java
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY;
-import static com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager.alertAgain;
+import static com.android.systemui.statusbar.notification.interruption.HeadsUpController.alertAgain;
import android.annotation.Nullable;
@@ -29,7 +29,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpViewBinder;
+import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 0e8dd5e24e91..4159d43e34ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -16,11 +16,15 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
+
import android.annotation.IntDef;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -40,6 +44,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -60,22 +65,47 @@ public class PreparationCoordinator implements Coordinator {
private final NotifInflationErrorManager mNotifErrorManager;
private final NotifViewBarn mViewBarn;
private final Map<NotificationEntry, Integer> mInflationStates = new ArrayMap<>();
+
+ /**
+ * The set of notifications that are currently inflating something. Note that this is
+ * separate from inflation state as a view could either be uninflated or inflated and still be
+ * inflating something.
+ */
+ private final Set<NotificationEntry> mInflatingNotifs = new ArraySet<>();
+
private final IStatusBarService mStatusBarService;
+ /**
+ * The number of children in a group we actually keep inflated since we don't actually show
+ * all the children and don't need every child inflated at all times.
+ */
+ private final int mChildBindCutoff;
+
@Inject
public PreparationCoordinator(
PreparationCoordinatorLogger logger,
NotifInflaterImpl notifInflater,
NotifInflationErrorManager errorManager,
NotifViewBarn viewBarn,
- IStatusBarService service
- ) {
+ IStatusBarService service) {
+ this(logger, notifInflater, errorManager, viewBarn, service, CHILD_BIND_CUTOFF);
+ }
+
+ @VisibleForTesting
+ PreparationCoordinator(
+ PreparationCoordinatorLogger logger,
+ NotifInflaterImpl notifInflater,
+ NotifInflationErrorManager errorManager,
+ NotifViewBarn viewBarn,
+ IStatusBarService service,
+ int childBindCutoff) {
mLogger = logger;
mNotifInflater = notifInflater;
mNotifErrorManager = errorManager;
mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
mViewBarn = viewBarn;
mStatusBarService = service;
+ mChildBindCutoff = childBindCutoff;
}
@Override
@@ -96,6 +126,8 @@ public class PreparationCoordinator implements Coordinator {
@Override
public void onEntryUpdated(NotificationEntry entry) {
+ abortInflation(entry, "entryUpdated");
+ mInflatingNotifs.remove(entry);
@InflationState int state = getInflationState(entry);
if (state == STATE_INFLATED) {
mInflationStates.put(entry, STATE_INFLATED_INVALID);
@@ -113,6 +145,7 @@ public class PreparationCoordinator implements Coordinator {
@Override
public void onEntryCleanUp(NotificationEntry entry) {
mInflationStates.remove(entry);
+ mInflatingNotifs.remove(entry);
mViewBarn.removeViewForEntry(entry);
}
};
@@ -133,23 +166,11 @@ public class PreparationCoordinator implements Coordinator {
private final NotifFilter mNotifInflatingFilter = new NotifFilter(TAG + "Inflating") {
/**
- * Filters out notifications that haven't been inflated yet
+ * Filters out notifications that aren't inflated
*/
@Override
public boolean shouldFilterOut(NotificationEntry entry, long now) {
- @InflationState int state = getInflationState(entry);
- return (state != STATE_INFLATED) && (state != STATE_INFLATED_INVALID);
- }
- };
-
- private final NotifInflater.InflationCallback mInflationCallback =
- new NotifInflater.InflationCallback() {
- @Override
- public void onInflationFinished(NotificationEntry entry) {
- mLogger.logNotifInflated(entry.getKey());
- mViewBarn.registerViewForEntry(entry, entry.getRow());
- mInflationStates.put(entry, STATE_INFLATED);
- mNotifInflatingFilter.invalidateList();
+ return !isInflated(entry);
}
};
@@ -187,19 +208,42 @@ public class PreparationCoordinator implements Coordinator {
ListEntry entry = entries.get(i);
if (entry instanceof GroupEntry) {
GroupEntry groupEntry = (GroupEntry) entry;
- inflateNotifRequiredViews(groupEntry.getSummary());
- List<NotificationEntry> children = groupEntry.getChildren();
- for (int j = 0, groupSize = children.size(); j < groupSize; j++) {
- inflateNotifRequiredViews(children.get(j));
- }
+ groupEntry.setUntruncatedChildCount(groupEntry.getChildren().size());
+ inflateRequiredGroupViews(groupEntry);
} else {
NotificationEntry notifEntry = (NotificationEntry) entry;
- inflateNotifRequiredViews(notifEntry);
+ inflateRequiredNotifViews(notifEntry);
+ }
+ }
+ }
+
+ private void inflateRequiredGroupViews(GroupEntry groupEntry) {
+ NotificationEntry summary = groupEntry.getSummary();
+ List<NotificationEntry> children = groupEntry.getChildren();
+ inflateRequiredNotifViews(summary);
+ for (int j = 0; j < children.size(); j++) {
+ NotificationEntry child = children.get(j);
+ boolean childShouldBeBound = j < mChildBindCutoff;
+ if (childShouldBeBound) {
+ inflateRequiredNotifViews(child);
+ } else {
+ if (mInflatingNotifs.contains(child)) {
+ abortInflation(child, "Past last visible group child");
+ }
+ if (isInflated(child)) {
+ // TODO: May want to put an animation hint here so view manager knows to treat
+ // this differently from a regular removal animation
+ freeNotifViews(child);
+ }
}
}
}
- private void inflateNotifRequiredViews(NotificationEntry entry) {
+ private void inflateRequiredNotifViews(NotificationEntry entry) {
+ if (mInflatingNotifs.contains(entry)) {
+ // Already inflating this entry
+ return;
+ }
@InflationState int state = mInflationStates.get(entry);
switch (state) {
case STATE_UNINFLATED:
@@ -217,16 +261,38 @@ public class PreparationCoordinator implements Coordinator {
private void inflateEntry(NotificationEntry entry, String reason) {
abortInflation(entry, reason);
- mNotifInflater.inflateViews(entry, mInflationCallback);
+ mInflatingNotifs.add(entry);
+ mNotifInflater.inflateViews(entry, this::onInflationFinished);
}
private void rebind(NotificationEntry entry, String reason) {
- mNotifInflater.rebindViews(entry, mInflationCallback);
+ mInflatingNotifs.add(entry);
+ mNotifInflater.rebindViews(entry, this::onInflationFinished);
}
private void abortInflation(NotificationEntry entry, String reason) {
mLogger.logInflationAborted(entry.getKey(), reason);
entry.abortTask();
+ mInflatingNotifs.remove(entry);
+ }
+
+ private void onInflationFinished(NotificationEntry entry) {
+ mLogger.logNotifInflated(entry.getKey());
+ mInflatingNotifs.remove(entry);
+ mViewBarn.registerViewForEntry(entry, entry.getRow());
+ mInflationStates.put(entry, STATE_INFLATED);
+ mNotifInflatingFilter.invalidateList();
+ }
+
+ private void freeNotifViews(NotificationEntry entry) {
+ mViewBarn.removeViewForEntry(entry);
+ entry.setRow(null);
+ mInflationStates.put(entry, STATE_UNINFLATED);
+ }
+
+ private boolean isInflated(NotificationEntry entry) {
+ @InflationState int state = getInflationState(entry);
+ return (state == STATE_INFLATED) || (state == STATE_INFLATED_INVALID);
}
private @InflationState int getInflationState(NotificationEntry entry) {
@@ -241,7 +307,7 @@ public class PreparationCoordinator implements Coordinator {
value = {STATE_UNINFLATED, STATE_INFLATED_INVALID, STATE_INFLATED, STATE_ERROR})
@interface InflationState {}
- /** The notification has never been inflated before. */
+ /** The notification has no views attached. */
private static final int STATE_UNINFLATED = 0;
/** The notification is inflated. */
@@ -255,4 +321,13 @@ public class PreparationCoordinator implements Coordinator {
/** The notification errored out while inflating */
private static final int STATE_ERROR = -1;
+
+ /**
+ * How big the buffer of extra views we keep around to be ready to show when we do need to
+ * dynamically inflate a row.
+ */
+ private static final int EXTRA_VIEW_BUFFER_COUNT = 1;
+
+ private static final int CHILD_BIND_CUTOFF =
+ NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED + EXTRA_VIEW_BUFFER_COUNT;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
new file mode 100644
index 000000000000..73c0fdc56b8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/LowPriorityInflationHelper.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.inflation;
+
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class that provide methods to help check when we need to inflate a low priority version
+ * ot notification content.
+ */
+@Singleton
+public class LowPriorityInflationHelper {
+ private final FeatureFlags mFeatureFlags;
+ private final NotificationGroupManager mGroupManager;
+ private final RowContentBindStage mRowContentBindStage;
+
+ @Inject
+ LowPriorityInflationHelper(
+ FeatureFlags featureFlags,
+ NotificationGroupManager groupManager,
+ RowContentBindStage rowContentBindStage) {
+ mFeatureFlags = featureFlags;
+ mGroupManager = groupManager;
+ mRowContentBindStage = rowContentBindStage;
+ }
+
+ /**
+ * Check if we inflated the wrong version of the view and if we need to reinflate the
+ * content views to be their low priority version or not.
+ *
+ * Whether we inflate the low priority view or not depends on the notification being visually
+ * part of a group. Since group membership is determined AFTER inflation, we're forced to check
+ * again at a later point in the pipeline to see if we inflated the wrong view and reinflate
+ * the correct one here.
+ *
+ * TODO: The group manager should run before inflation so that we don't deal with this
+ */
+ public void recheckLowPriorityViewAndInflate(
+ NotificationEntry entry,
+ ExpandableNotificationRow row) {
+ RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+ final boolean shouldBeLowPriority = shouldUseLowPriorityView(entry);
+ if (!row.isRemoved() && row.isLowPriority() != shouldBeLowPriority) {
+ params.setUseLowPriority(shouldBeLowPriority);
+ mRowContentBindStage.requestRebind(entry,
+ en -> row.setIsLowPriority(shouldBeLowPriority));
+ }
+ }
+
+ /**
+ * Whether the notification should inflate a low priority version of its content views.
+ */
+ public boolean shouldUseLowPriorityView(NotificationEntry entry) {
+ boolean isGroupChild;
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ isGroupChild = (entry.getParent() != GroupEntry.ROOT_ENTRY);
+ } else {
+ isGroupChild = mGroupManager.isChildInGroupWithSummary(entry.getSbn());
+ }
+ return entry.isAmbient() && !isGroupChild;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
index f4c4924b4b9a..710b137d2795 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
@@ -51,5 +51,6 @@ public interface NotificationRowBinder {
NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
- NotificationUiAdjustment newAdjustment);
+ NotificationUiAdjustment newAdjustment,
+ NotificationRowContentBinder.InflationCallback callback);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 73f12f86e52e..673aa3903156 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -64,6 +64,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
private final ExpandableNotificationRowComponent.Builder
mExpandableNotificationRowComponentBuilder;
private final IconManager mIconManager;
+ private final LowPriorityInflationHelper mLowPriorityInflationHelper;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
@@ -81,7 +82,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
NotificationInterruptStateProvider notificationInterruptionStateProvider,
Provider<RowInflaterTask> rowInflaterTaskProvider,
ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
- IconManager iconManager) {
+ IconManager iconManager,
+ LowPriorityInflationHelper lowPriorityInflationHelper) {
mContext = context;
mNotifBindPipeline = notifBindPipeline;
mRowContentBindStage = rowContentBindStage;
@@ -92,6 +94,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
mRowInflaterTaskProvider = rowInflaterTaskProvider;
mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
mIconManager = iconManager;
+ mLowPriorityInflationHelper = lowPriorityInflationHelper;
}
/**
@@ -180,13 +183,14 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
- NotificationUiAdjustment newAdjustment) {
+ NotificationUiAdjustment newAdjustment,
+ NotificationRowContentBinder.InflationCallback callback) {
if (NotificationUiAdjustment.needReinflate(oldAdjustment, newAdjustment)) {
if (entry.rowExists()) {
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
- inflateContentViews(entry, row, null /* callback */);
+ inflateContentViews(entry, row, callback);
} else {
// Once the RowInflaterTask is done, it will pick up the updated entry, so
// no-op here.
@@ -221,14 +225,18 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
private void inflateContentViews(
NotificationEntry entry,
ExpandableNotificationRow row,
- NotificationRowContentBinder.InflationCallback inflationCallback) {
+ @Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
final boolean useIncreasedCollapsedHeight =
mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
- final boolean isLowPriority = entry.isAmbient();
+ // If this is our first time inflating, we don't actually know the groupings for real
+ // yet, so we might actually inflate a low priority content view incorrectly here and have
+ // to correct it later in the pipeline. On subsequent inflations (i.e. updates), this
+ // should inflate the correct view.
+ final boolean isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry);
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
- params.setUseLowPriority(entry.isAmbient());
+ params.setUseLowPriority(isLowPriority);
// TODO: Replace this API with RowContentBindParams directly. Also move to a separate
// redaction controller.
@@ -238,7 +246,9 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
mRowContentBindStage.requestRebind(entry, en -> {
row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setIsLowPriority(isLowPriority);
- inflationCallback.onAsyncInflationFinished(en);
+ if (inflationCallback != null) {
+ inflationCallback.onAsyncInflationFinished(en);
+ }
});
}
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 565a082533a7..1dbfa32cdf41 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
@@ -24,12 +24,11 @@ import android.os.Handler;
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.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -45,7 +44,6 @@ import com.android.systemui.statusbar.notification.collection.provider.HighPrior
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -53,13 +51,14 @@ import com.android.systemui.statusbar.notification.logging.NotificationPanelLogg
import com.android.systemui.statusbar.notification.logging.NotificationPanelLoggerImpl;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.PriorityOnboardingDialogController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
import java.util.concurrent.Executor;
+import javax.inject.Provider;
import javax.inject.Singleton;
import dagger.Binds;
@@ -109,7 +108,9 @@ public interface NotificationsModule {
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
LauncherApps launcherApps,
- ShortcutManager shortcutManager) {
+ ShortcutManager shortcutManager,
+ CurrentUserContextTracker contextTracker,
+ Provider<PriorityOnboardingDialogController.Builder> builderProvider) {
return new NotificationGutsManager(
context,
visualStabilityManager,
@@ -119,7 +120,9 @@ public interface NotificationsModule {
highPriorityProvider,
notificationManager,
launcherApps,
- shortcutManager);
+ shortcutManager,
+ contextTracker,
+ builderProvider);
}
/** Provides an instance of {@link VisualStabilityManager} */
@@ -130,27 +133,6 @@ public interface NotificationsModule {
return new VisualStabilityManager(notificationEntryManager, handler);
}
- /** Provides an instance of {@link NotificationAlertingManager} */
- @Singleton
- @Provides
- static NotificationAlertingManager provideNotificationAlertingManager(
- NotificationEntryManager notificationEntryManager,
- NotificationRemoteInputManager remoteInputManager,
- VisualStabilityManager visualStabilityManager,
- StatusBarStateController statusBarStateController,
- NotificationInterruptStateProvider notificationInterruptStateProvider,
- NotificationListener notificationListener,
- HeadsUpManager headsUpManager) {
- return new NotificationAlertingManager(
- notificationEntryManager,
- remoteInputManager,
- visualStabilityManager,
- statusBarStateController,
- notificationInterruptStateProvider,
- notificationListener,
- headsUpManager);
- }
-
/** Provides an instance of {@link NotificationLogger} */
@Singleton
@Provides
@@ -177,13 +159,6 @@ public interface NotificationsModule {
return new NotificationPanelLoggerImpl();
}
- /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
- @Singleton
- @Provides
- static UiEventLogger provideUiEventLogger() {
- return new UiEventLoggerImpl();
- }
-
/** Provides an instance of {@link NotificationBlockingHelperManager} */
@Singleton
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java
deleted file mode 100644
index a7b1f37edf0e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpBindController.java
+++ /dev/null
@@ -1,95 +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.statusbar.notification.headsup;
-
-import androidx.annotation.NonNull;
-
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.HeadsUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * Controller class for old pipeline heads up view binding. It listens to
- * {@link NotificationEntryManager} entry events and appropriately binds or unbinds the heads up
- * view.
- *
- * This has a subtle contract with {@link NotificationAlertingManager} where this controller handles
- * the heads up binding, but {@link NotificationAlertingManager} listens for general inflation
- * events to actually mark it heads up/update. In the new pipeline, we combine the classes.
- * See {@link HeadsUpCoordinator}.
- */
-@Singleton
-public class HeadsUpBindController {
- private final HeadsUpViewBinder mHeadsUpViewBinder;
- private final NotificationInterruptStateProvider mInterruptStateProvider;
-
- @Inject
- HeadsUpBindController(
- HeadsUpViewBinder headsUpViewBinder,
- NotificationInterruptStateProvider notificationInterruptStateProvider) {
- mInterruptStateProvider = notificationInterruptStateProvider;
- mHeadsUpViewBinder = headsUpViewBinder;
- }
-
- /**
- * Attach this controller and add its listeners.
- */
- public void attach(
- NotificationEntryManager entryManager,
- HeadsUpManager headsUpManager) {
- entryManager.addCollectionListener(mCollectionListener);
- headsUpManager.addListener(mOnHeadsUpChangedListener);
- }
-
- private NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- if (mInterruptStateProvider.shouldHeadsUp(entry)) {
- mHeadsUpViewBinder.bindHeadsUpView(entry, null);
- }
- }
-
- @Override
- public void onEntryUpdated(NotificationEntry entry) {
- if (mInterruptStateProvider.shouldHeadsUp(entry)) {
- mHeadsUpViewBinder.bindHeadsUpView(entry, null);
- }
- }
-
- @Override
- public void onEntryCleanUp(NotificationEntry entry) {
- mHeadsUpViewBinder.abortBindCallback(entry);
- }
- };
-
- private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
- @Override
- public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
- if (!isHeadsUp) {
- mHeadsUpViewBinder.unbindHeadsUpView(entry);
- }
- }
- };
-}
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 5fac5b1cf159..c9754048e1d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.BubbleController
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationListener
@@ -28,7 +27,7 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.NotificationListController
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
-import com.android.systemui.statusbar.notification.headsup.HeadsUpBindController
+import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
@@ -36,7 +35,7 @@ import com.android.systemui.statusbar.phone.NotificationGroupManager
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.notification.headsup.HeadsUpViewBinder
+import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.policy.RemoteInputUriController
import dagger.Lazy
import java.io.FileDescriptor
@@ -62,12 +61,12 @@ class NotificationsControllerImpl @Inject constructor(
private val deviceProvisionedController: DeviceProvisionedController,
private val notificationRowBinder: NotificationRowBinderImpl,
private val remoteInputUriController: RemoteInputUriController,
- private val bubbleController: BubbleController,
private val groupManager: NotificationGroupManager,
private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
private val headsUpManager: HeadsUpManager,
- private val headsUpBindController: HeadsUpBindController,
- private val headsUpViewBinder: HeadsUpViewBinder
+ private val headsUpController: HeadsUpController,
+ private val headsUpViewBinder: HeadsUpViewBinder,
+ private val clickerBuilder: NotificationClicker.Builder
) : NotificationsController {
override fun initialize(
@@ -87,10 +86,7 @@ class NotificationsControllerImpl @Inject constructor(
listController.bind()
notificationRowBinder.setNotificationClicker(
- NotificationClicker(
- Optional.of(statusBar),
- bubbleController,
- notificationActivityStarter))
+ clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter))
notificationRowBinder.setUpWithPresenter(
presenter,
listContainer,
@@ -112,7 +108,7 @@ class NotificationsControllerImpl @Inject constructor(
groupAlertTransferHelper.bind(entryManager, groupManager)
headsUpManager.addListener(groupManager)
headsUpManager.addListener(groupAlertTransferHelper)
- headsUpBindController.attach(entryManager, headsUpManager)
+ headsUpController.attach(entryManager, headsUpManager)
groupManager.setHeadsUpManager(headsUpManager)
groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
index 5d070981f81b..9b6ae9a7f99d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpController.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,117 +22,117 @@ import android.app.Notification;
import android.service.notification.StatusBarNotification;
import android.util.Log;
-import com.android.internal.statusbar.NotificationVisibility;
+import androidx.annotation.NonNull;
+
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.dagger.NotificationsModule;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-/** Handles heads-up and pulsing behavior driven by notification changes. */
-public class NotificationAlertingManager {
-
- private static final String TAG = "NotifAlertManager";
+import javax.inject.Inject;
+import javax.inject.Singleton;
+/**
+ * Controller class for old pipeline heads up logic. It listens to {@link NotificationEntryManager}
+ * entry events and appropriately binds or unbinds the heads up view and promotes it to the top
+ * of the screen.
+ */
+@Singleton
+public class HeadsUpController {
+ private final HeadsUpViewBinder mHeadsUpViewBinder;
+ private final NotificationInterruptStateProvider mInterruptStateProvider;
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisualStabilityManager mVisualStabilityManager;
private final StatusBarStateController mStatusBarStateController;
- private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final NotificationListener mNotificationListener;
+ private final HeadsUpManager mHeadsUpManager;
- private HeadsUpManager mHeadsUpManager;
-
- /**
- * Injected constructor. See {@link NotificationsModule}.
- */
- public NotificationAlertingManager(
- NotificationEntryManager notificationEntryManager,
+ @Inject
+ HeadsUpController(
+ HeadsUpViewBinder headsUpViewBinder,
+ NotificationInterruptStateProvider notificationInterruptStateProvider,
+ HeadsUpManager headsUpManager,
NotificationRemoteInputManager remoteInputManager,
- VisualStabilityManager visualStabilityManager,
StatusBarStateController statusBarStateController,
- NotificationInterruptStateProvider notificationInterruptionStateProvider,
- NotificationListener notificationListener,
- HeadsUpManager headsUpManager) {
+ VisualStabilityManager visualStabilityManager,
+ NotificationListener notificationListener) {
+ mHeadsUpViewBinder = headsUpViewBinder;
+ mHeadsUpManager = headsUpManager;
+ mInterruptStateProvider = notificationInterruptStateProvider;
mRemoteInputManager = remoteInputManager;
- mVisualStabilityManager = visualStabilityManager;
mStatusBarStateController = statusBarStateController;
- mNotificationInterruptStateProvider = notificationInterruptionStateProvider;
+ mVisualStabilityManager = visualStabilityManager;
mNotificationListener = notificationListener;
- mHeadsUpManager = headsUpManager;
+ }
- notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- showAlertingView(entry);
- }
+ /**
+ * Attach this controller and add its listeners.
+ */
+ public void attach(
+ NotificationEntryManager entryManager,
+ HeadsUpManager headsUpManager) {
+ entryManager.addCollectionListener(mCollectionListener);
+ headsUpManager.addListener(mOnHeadsUpChangedListener);
+ }
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- updateAlertState(entry);
+ private NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ if (mInterruptStateProvider.shouldHeadsUp(entry)) {
+ mHeadsUpViewBinder.bindHeadsUpView(
+ entry, HeadsUpController.this::showAlertingView);
}
+ }
- @Override
- public void onEntryRemoved(
- NotificationEntry entry,
- NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- stopAlerting(entry.getKey());
- }
- });
- }
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ updateHunState(entry);
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry, int reason) {
+ stopAlerting(entry);
+ }
+
+ @Override
+ public void onEntryCleanUp(NotificationEntry entry) {
+ mHeadsUpViewBinder.abortBindCallback(entry);
+ }
+ };
/**
- * Adds the entry to the respective alerting manager if the content view was inflated and
- * the entry should still alert.
+ * Adds the entry to the HUN manager and show it for the first time.
*/
private void showAlertingView(NotificationEntry entry) {
- // TODO: Instead of this back and forth, we should listen to changes in heads up and
- // cancel on-going heads up view inflation using the bind pipeline.
- if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) {
- mHeadsUpManager.showNotification(entry);
- if (!mStatusBarStateController.isDozing()) {
- // Mark as seen immediately
- setNotificationShown(entry.getSbn());
- }
+ mHeadsUpManager.showNotification(entry);
+ if (!mStatusBarStateController.isDozing()) {
+ // Mark as seen immediately
+ setNotificationShown(entry.getSbn());
}
}
- private void updateAlertState(NotificationEntry entry) {
- boolean alertAgain = alertAgain(entry, entry.getSbn().getNotification());
+ private void updateHunState(NotificationEntry entry) {
+ boolean hunAgain = alertAgain(entry, entry.getSbn().getNotification());
// includes check for whether this notification should be filtered:
- boolean shouldAlert = mNotificationInterruptStateProvider.shouldHeadsUp(entry);
- final boolean wasAlerting = mHeadsUpManager.isAlerting(entry.getKey());
- if (wasAlerting) {
- if (shouldAlert) {
- mHeadsUpManager.updateNotification(entry.getKey(), alertAgain);
+ boolean shouldHeadsUp = mInterruptStateProvider.shouldHeadsUp(entry);
+ final boolean wasHeadsUp = mHeadsUpManager.isAlerting(entry.getKey());
+ if (wasHeadsUp) {
+ if (shouldHeadsUp) {
+ mHeadsUpManager.updateNotification(entry.getKey(), hunAgain);
} else if (!mHeadsUpManager.isEntryAutoHeadsUpped(entry.getKey())) {
// We don't want this to be interrupting anymore, let's remove it
mHeadsUpManager.removeNotification(entry.getKey(), false /* removeImmediately */);
}
- } else if (shouldAlert && alertAgain) {
- // This notification was updated to be alerting, show it!
- mHeadsUpManager.showNotification(entry);
+ } else if (shouldHeadsUp && hunAgain) {
+ mHeadsUpViewBinder.bindHeadsUpView(entry, mHeadsUpManager::showNotification);
}
}
- /**
- * Checks whether an update for a notification warrants an alert for the user.
- *
- * @param oldEntry the entry for this notification.
- * @param newNotification the new notification for this entry.
- * @return whether this notification should alert the user.
- */
- public static boolean alertAgain(
- NotificationEntry oldEntry, Notification newNotification) {
- return oldEntry == null || !oldEntry.hasInterrupted()
- || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
- }
-
private void setNotificationShown(StatusBarNotification n) {
try {
mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
@@ -141,10 +141,11 @@ public class NotificationAlertingManager {
}
}
- private void stopAlerting(final String key) {
- // Attempt to remove notifications from their alert manager.
+ private void stopAlerting(NotificationEntry entry) {
+ // Attempt to remove notifications from their HUN manager.
// Though the remove itself may fail, it lets the manager know to remove as soon as
// possible.
+ String key = entry.getKey();
if (mHeadsUpManager.isAlerting(key)) {
// A cancel() in response to a remote input shouldn't be delayed, as it makes the
// sending look longer than it takes.
@@ -157,4 +158,28 @@ public class NotificationAlertingManager {
mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
}
}
+
+ /**
+ * Checks whether an update for a notification warrants an alert for the user.
+ *
+ * @param oldEntry the entry for this notification.
+ * @param newNotification the new notification for this entry.
+ * @return whether this notification should alert the user.
+ */
+ public static boolean alertAgain(
+ NotificationEntry oldEntry, Notification newNotification) {
+ return oldEntry == null || !oldEntry.hasInterrupted()
+ || (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
+ }
+
+ private OnHeadsUpChangedListener mOnHeadsUpChangedListener = new OnHeadsUpChangedListener() {
+ @Override
+ public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
+ if (!isHeadsUp && !entry.getRow().isRemoved()) {
+ mHeadsUpViewBinder.unbindHeadsUpView(entry);
+ }
+ }
+ };
+
+ private static final String TAG = "HeadsUpBindController";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 37acfa8dc0a4..ff139957031a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/headsup/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.headsup;
+package com.android.systemui.statusbar.notification.interruption;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
@@ -42,7 +42,7 @@ import javax.inject.Singleton;
* content view.
*
* TODO: This should be moved into {@link HeadsUpCoordinator} when the old pipeline is deprecated
- * (i.e. when {@link HeadsUpBindController} is removed).
+ * (i.e. when {@link HeadsUpController} is removed).
*/
@Singleton
public class HeadsUpViewBinder {
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 5879c15c2493..d36627fb849d 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
@@ -20,6 +20,7 @@ import android.annotation.IntDef
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_FULL_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
@@ -33,21 +34,28 @@ interface PeopleNotificationIdentifier {
/**
* Identifies if the given notification can be classified as a "People" notification.
*
- * @return [TYPE_NON_PERSON] if not a people notification, [TYPE_PERSON] if a standard people
- * notification, and [TYPE_IMPORTANT_PERSON] if an "important" people notification.
+ * @return [TYPE_NON_PERSON] if not a people notification, [TYPE_PERSON] if it is a people
+ * notification that doesn't use shortcuts, [TYPE_FULL_PERSON] if it is a person notification
+ * that users shortcuts, and [TYPE_IMPORTANT_PERSON] if an "important" people notification
+ * that users shortcuts.
*/
@PeopleNotificationType
fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int
+ fun compareTo(@PeopleNotificationType a: Int,
+ @PeopleNotificationType b: Int): Int
+
companion object {
@Retention(AnnotationRetention.SOURCE)
- @IntDef(prefix = ["TYPE_"], value = [TYPE_NON_PERSON, TYPE_PERSON, TYPE_IMPORTANT_PERSON])
+ @IntDef(prefix = ["TYPE_"], value = [TYPE_NON_PERSON, TYPE_PERSON, TYPE_FULL_PERSON,
+ TYPE_IMPORTANT_PERSON])
annotation class PeopleNotificationType
const val TYPE_NON_PERSON = 0
const val TYPE_PERSON = 1
- const val TYPE_IMPORTANT_PERSON = 2
+ const val TYPE_FULL_PERSON = 2
+ const val TYPE_IMPORTANT_PERSON = 3
}
}
@@ -69,6 +77,11 @@ class PeopleNotificationIdentifierImpl @Inject constructor(
}
}
+ override fun compareTo(@PeopleNotificationType a: Int,
+ @PeopleNotificationType b: Int): Int {
+ return b.compareTo(a);
+ }
+
/**
* Given two [PeopleNotificationType]s, determine the upper bound. Used to constrain a
* notification to a type given multiple signals, i.e. notification groups, where each child
@@ -84,8 +97,9 @@ class PeopleNotificationIdentifierImpl @Inject constructor(
private val Ranking.personTypeInfo
get() = when {
!isConversation -> TYPE_NON_PERSON
+ shortcutInfo == null -> TYPE_PERSON
channel?.isImportantConversation == true -> TYPE_IMPORTANT_PERSON
- else -> TYPE_PERSON
+ else -> TYPE_FULL_PERSON
}
private fun extractPersonTypeInfo(sbn: StatusBarNotification) =
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 b9dd97482d88..92b597b01559 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
@@ -331,19 +331,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
.setDuration(ACTIVATE_ANIMATION_LENGTH);
}
- @Override
- public boolean performClick() {
- if (!mNeedsDimming || (mAccessibilityManager != null
- && mAccessibilityManager.isTouchExplorationEnabled())) {
- return super.performClick();
- }
- return false;
- }
-
- boolean superPerformClick() {
- return super.performClick();
- }
-
/**
* Cancels the hotspot and makes the notification inactive.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
index 2f0e433b3927..dd30c890e75b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewController.java
@@ -72,7 +72,7 @@ public class ActivatableNotificationViewController {
} else {
mView.makeInactive(true /* animate */);
}
- }, mView::superPerformClick, mView::handleSlideBack,
+ }, mView::performClick, mView::handleSlideBack,
mFalsingManager::onNotificationDoubleTap);
mView.setOnTouchListener(mTouchHandler);
mView.setTouchHandler(mTouchHandler);
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 998230f205ab..f8844c715230 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
@@ -70,6 +70,7 @@ 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.BubbleController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -239,7 +240,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private ExpandableNotificationRow mNotificationParent;
private OnExpandClickListener mOnExpandClickListener;
private View.OnClickListener mOnAppOpsClickListener;
- private boolean mIsChildInGroup;
// Listener will be called when receiving a long click event.
// Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -410,7 +410,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView());
setIconAnimationRunningForChild(running, mChildrenContainer.getLowPriorityHeaderView());
List<ExpandableNotificationRow> notificationChildren =
- mChildrenContainer.getNotificationChildren();
+ mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i);
child.setIconAnimationRunning(running);
@@ -535,6 +535,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return isNonblockable;
}
+ private boolean isConversation() {
+ return mPeopleNotificationIdentifier
+ .getPeopleNotificationType(mEntry.getSbn(), mEntry.getRanking())
+ != PeopleNotificationIdentifier.TYPE_NON_PERSON;
+ }
+
public void onNotificationUpdated() {
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
@@ -547,7 +553,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mMenuRow.setAppName(mAppName);
}
if (mIsSummaryWithChildren) {
- mChildrenContainer.recreateNotificationHeader(mExpandClickListener);
+ mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
mChildrenContainer.onNotificationUpdated();
}
if (mIconAnimationRunning) {
@@ -559,7 +565,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mNotificationParent != null) {
mNotificationParent.updateChildrenHeaderAppearance();
}
- onChildrenCountChanged();
+ onAttachedChildrenCountChanged();
// The public layouts expand button is always visible
mPublicLayout.updateExpandButtons(true);
updateLimits();
@@ -579,6 +585,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
}
+ /** Call when bubble state has changed and the button on the notification should be updated. */
+ public void updateBubbleButton() {
+ for (NotificationContentView l : mLayouts) {
+ l.updateBubbleButton(mEntry);
+ }
+ }
+
@VisibleForTesting
void updateShelfIconColor() {
StatusBarIconView expandedIcon = mEntry.getIcons().getShelfIcon();
@@ -763,6 +776,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
+ * @see NotificationChildrenContainer#setUntruncatedChildCount(int)
+ */
+ public void setUntruncatedChildCount(int childCount) {
+ if (mChildrenContainer == null) {
+ mChildrenContainerStub.inflate();
+ }
+ mChildrenContainer.setUntruncatedChildCount(childCount);
+ }
+
+ /**
* Add a child notification to this view.
*
* @param row the row to add
@@ -773,7 +796,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainerStub.inflate();
}
mChildrenContainer.addNotification(row, childIndex);
- onChildrenCountChanged();
+ onAttachedChildrenCountChanged();
row.setIsChildInGroup(true, this);
}
@@ -792,7 +815,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mChildrenContainer != null) {
mChildrenContainer.removeNotification(row);
}
- onChildrenCountChanged();
+ onAttachedChildrenCountChanged();
row.setIsChildInGroup(false, null);
row.setBottomRoundness(0.0f, false /* animate */);
}
@@ -830,15 +853,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
mNotificationParent = isChildInGroup ? parent : null;
mPrivateLayout.setIsChildInGroup(isChildInGroup);
- // TODO: Move inflation logic out of this call
- if (mIsChildInGroup != isChildInGroup) {
- mIsChildInGroup = isChildInGroup;
- if (!isRemoved() && mIsLowPriority) {
- RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
- params.setUseLowPriority(mIsLowPriority);
- mRowContentBindStage.requestRebind(mEntry, null /* callback */);
- }
- }
+
resetBackgroundAlpha();
updateBackgroundForGroupState();
updateClickAndFocus();
@@ -886,15 +901,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mChildrenExpanded;
}
- public List<ExpandableNotificationRow> getNotificationChildren() {
- return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren();
- }
-
- public int getNumberOfNotificationChildren() {
- if (mChildrenContainer == null) {
- return 0;
- }
- return mChildrenContainer.getNotificationChildren().size();
+ public List<ExpandableNotificationRow> getAttachedChildren() {
+ return mChildrenContainer == null ? null : mChildrenContainer.getAttachedChildren();
}
/**
@@ -1028,7 +1036,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
setChronometerRunning(running, mPublicLayout);
if (mChildrenContainer != null) {
List<ExpandableNotificationRow> notificationChildren =
- mChildrenContainer.getNotificationChildren();
+ mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i);
child.setChronometerRunning(running);
@@ -1086,6 +1094,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateClickAndFocus();
}
+ /** The click listener for the bubble button. */
+ public View.OnClickListener getBubbleClickListener() {
+ return new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Dependency.get(BubbleController.class)
+ .onUserChangedBubble(mEntry, !mEntry.isBubble() /* createBubble */);
+ mHeadsUpManager.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+ }
+ };
+ }
+
private void updateClickAndFocus() {
boolean normalChild = !isChildInGroup() || isGroupExpanded();
boolean clickable = mOnClickListener != null && normalChild;
@@ -1121,6 +1141,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
items.add(NotificationMenuRow.createConversationItem(mContext));
+ items.add(NotificationMenuRow.createPartialConversationItem(mContext));
items.add(NotificationMenuRow.createInfoItem(mContext));
items.add(NotificationMenuRow.createSnoozeItem(mContext));
items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1228,7 +1249,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mUpdateBackgroundOnUpdate = true;
reInflateViews();
if (mChildrenContainer != null) {
- for (ExpandableNotificationRow child : mChildrenContainer.getNotificationChildren()) {
+ for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) {
child.onUiModeChanged();
}
}
@@ -1267,7 +1288,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mNotificationColor;
}
- private void updateNotificationColor() {
+ public void updateNotificationColor() {
Configuration currentConfig = getResources().getConfiguration();
boolean nightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
== Configuration.UI_MODE_NIGHT_YES;
@@ -1286,8 +1307,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void removeAllChildren() {
- List<ExpandableNotificationRow> notificationChildren
- = mChildrenContainer.getNotificationChildren();
+ List<ExpandableNotificationRow> notificationChildren =
+ mChildrenContainer.getAttachedChildren();
ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren);
for (int i = 0; i < clonedList.size(); i++) {
ExpandableNotificationRow row = clonedList.get(i);
@@ -1297,7 +1318,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mChildrenContainer.removeNotification(row);
row.setIsChildInGroup(false, null);
}
- onChildrenCountChanged();
+ onAttachedChildrenCountChanged();
}
@Override
@@ -1308,7 +1329,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void setForceUnlocked(boolean forceUnlocked) {
mForceUnlocked = forceUnlocked;
if (mIsSummaryWithChildren) {
- List<ExpandableNotificationRow> notificationChildren = getNotificationChildren();
+ List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
for (ExpandableNotificationRow child : notificationChildren) {
child.setForceUnlocked(forceUnlocked);
}
@@ -1324,7 +1345,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mEntry.getIcons().getStatusBarIcon().setDismissed();
if (isChildInGroup()) {
List<ExpandableNotificationRow> notificationChildren =
- mNotificationParent.getNotificationChildren();
+ mNotificationParent.getAttachedChildren();
int i = notificationChildren.indexOf(this);
if (i != -1 && i < notificationChildren.size() - 1) {
mChildAfterViewWhenDismissed = notificationChildren.get(i + 1);
@@ -1613,6 +1634,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mFalsingManager = falsingManager;
mStatusbarStateController = statusBarStateController;
mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+ for (NotificationContentView l : mLayouts) {
+ l.setPeopleNotificationIdentifier(mPeopleNotificationIdentifier);
+ }
}
private void initDimens() {
@@ -1814,6 +1838,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void resetTranslation() {
+ if (mMenuRow != null && mMenuRow.isMenuVisible()) {
+ return;
+ }
+
if (mTranslateAnim != null) {
mTranslateAnim.cancel();
}
@@ -2328,12 +2356,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return mGroupManager.isGroupExpanded(mEntry.getSbn());
}
- private void onChildrenCountChanged() {
+ private void onAttachedChildrenCountChanged() {
mIsSummaryWithChildren = mChildrenContainer != null
&& mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) {
- mChildrenContainer.recreateNotificationHeader(mExpandClickListener
- );
+ mChildrenContainer.recreateNotificationHeader(mExpandClickListener, isConversation());
}
getShowingLayout().updateBackgroundColor(false /* animate */);
mPrivateLayout.updateExpandButtons(isExpandable());
@@ -2361,7 +2388,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// If this is a summary, then add in the children notification channels for the
// same user and pkg.
if (mIsSummaryWithChildren) {
- final List<ExpandableNotificationRow> childrenRows = getNotificationChildren();
+ final List<ExpandableNotificationRow> childrenRows = getAttachedChildren();
final int numChildren = childrenRows.size();
for (int i = 0; i < numChildren; i++) {
final ExpandableNotificationRow childRow = childrenRows.get(i);
@@ -2468,7 +2495,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mHideSensitiveForIntrinsicHeight = hideSensitive;
if (mIsSummaryWithChildren) {
List<ExpandableNotificationRow> notificationChildren =
- mChildrenContainer.getNotificationChildren();
+ mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i);
child.setHideSensitiveForIntrinsicHeight(hideSensitive);
@@ -2806,7 +2833,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateBackgroundForGroupState();
if (mIsSummaryWithChildren) {
List<ExpandableNotificationRow> notificationChildren =
- mChildrenContainer.getNotificationChildren();
+ mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i);
child.updateBackgroundForGroupState();
@@ -2831,7 +2858,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mShowNoBackground = !mShowGroupBackgroundWhenExpanded && isGroupExpanded()
&& !isGroupExpansionChanging() && !isUserLocked();
mChildrenContainer.updateHeaderForExpansion(mShowNoBackground);
- List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren();
+ List<ExpandableNotificationRow> children = mChildrenContainer.getAttachedChildren();
for (int i = 0; i < children.size(); i++) {
children.get(i).updateBackgroundForGroupState();
}
@@ -3241,7 +3268,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
pw.print(", alpha: " + mChildrenContainer.getAlpha());
pw.print(", translationY: " + mChildrenContainer.getTranslationY());
pw.println();
- List<ExpandableNotificationRow> notificationChildren = getNotificationChildren();
+ List<ExpandableNotificationRow> notificationChildren = getAttachedChildren();
pw.println(" Children: " + notificationChildren.size());
pw.println(" {");
for(ExpandableNotificationRow child : notificationChildren) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index ee3b753ab926..5797944298d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -590,7 +590,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
// handling reset for child notifications
if (this instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) this;
- List<ExpandableNotificationRow> children = row.getNotificationChildren();
+ List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (row.isSummaryWithChildren() && children != null) {
for (ExpandableNotificationRow childRow : children) {
childRow.resetViewState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 41b248fb9634..3ec8c2374718 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -29,6 +29,7 @@ public class FooterView extends StackScrollerDecorView {
private final int mClearAllTopPadding;
private FooterViewButton mDismissButton;
private FooterViewButton mManageButton;
+ private boolean mShowHistory;
public FooterView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -72,15 +73,30 @@ public class FooterView extends StackScrollerDecorView {
|| touchY > mContent.getY() + mContent.getHeight();
}
+ public void showHistory(boolean showHistory) {
+ mShowHistory = showHistory;
+ if (mShowHistory) {
+ mManageButton.setText(R.string.manage_notifications_history_text);
+ mManageButton.setContentDescription(
+ mContext.getString(R.string.manage_notifications_history_text));
+ } else {
+ mManageButton.setText(R.string.manage_notifications_text);
+ mManageButton.setContentDescription(
+ mContext.getString(R.string.manage_notifications_text));
+ }
+ }
+
+ public boolean isHistoryShown() {
+ return mShowHistory;
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mDismissButton.setText(R.string.clear_all_notifications_text);
mDismissButton.setContentDescription(
mContext.getString(R.string.accessibility_clear_all));
- mManageButton.setText(R.string.manage_notifications_history_text);
- mManageButton.setContentDescription(
- mContext.getString(R.string.manage_notifications_history_text));
+ showHistory(mShowHistory);
}
public boolean isButtonVisible() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
new file mode 100644
index 000000000000..32477a6f39b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.widget.ConversationLayout;
+import com.android.systemui.R;
+
+/**
+ * A hybrid view which may contain information about one ore more conversations.
+ */
+public class HybridConversationNotificationView extends HybridNotificationView {
+
+ private ImageView mConversationIconView;
+ private TextView mConversationSenderName;
+ private View mConversationFacePile;
+ private int mConversationIconSize;
+ private int mFacePileSize;
+ private int mFacePileProtectionWidth;
+
+ public HybridConversationNotificationView(Context context) {
+ this(context, null);
+ }
+
+ public HybridConversationNotificationView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public HybridConversationNotificationView(
+ Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public HybridConversationNotificationView(
+ Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
+ mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
+ mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
+ mFacePileSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size);
+ mConversationIconSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size);
+ mFacePileProtectionWidth = getResources().getDimensionPixelSize(
+ R.dimen.conversation_single_line_face_pile_protection_width);
+ mTransformationHelper.addViewTransformingToSimilar(mConversationIconView);
+ }
+
+ @Override
+ public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
+ @Nullable View contentView) {
+ if (!(contentView instanceof ConversationLayout)) {
+ super.bind(title, text, contentView);
+ return;
+ }
+
+ ConversationLayout conversationLayout = (ConversationLayout) contentView;
+ Icon conversationIcon = conversationLayout.getConversationIcon();
+ if (conversationIcon != null) {
+ mConversationFacePile.setVisibility(GONE);
+ mConversationIconView.setVisibility(VISIBLE);
+ mConversationIconView.setImageIcon(conversationIcon);
+ } else {
+ // If there isn't an icon, generate a "face pile" based on the sender avatars
+ mConversationIconView.setVisibility(GONE);
+ mConversationFacePile.setVisibility(VISIBLE);
+
+ mConversationFacePile =
+ requireViewById(com.android.internal.R.id.conversation_face_pile);
+ ImageView facePileBottomBg = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom_background);
+ ImageView facePileBottom = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom);
+ ImageView facePileTop = mConversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_top);
+ conversationLayout.bindFacePile(facePileBottomBg, facePileBottom, facePileTop);
+ setSize(mConversationFacePile, mFacePileSize);
+ setSize(facePileBottom, mConversationIconSize);
+ setSize(facePileTop, mConversationIconSize);
+ setSize(facePileBottomBg, mConversationIconSize + 2 * mFacePileProtectionWidth);
+ mTransformationHelper.addViewTransformingToSimilar(facePileTop);
+ mTransformationHelper.addViewTransformingToSimilar(facePileBottom);
+ mTransformationHelper.addViewTransformingToSimilar(facePileBottomBg);
+ }
+ CharSequence conversationTitle = conversationLayout.getConversationTitle();
+ if (TextUtils.isEmpty(conversationTitle)) {
+ conversationTitle = title;
+ }
+ if (conversationLayout.isOneToOne()) {
+ mConversationSenderName.setVisibility(GONE);
+ } else {
+ mConversationSenderName.setVisibility(VISIBLE);
+ mConversationSenderName.setText(conversationLayout.getConversationSenderName());
+ }
+ CharSequence conversationText = conversationLayout.getConversationText();
+ if (TextUtils.isEmpty(conversationText)) {
+ conversationText = text;
+ }
+ super.bind(conversationTitle, conversationText, conversationLayout);
+ }
+
+ private static void setSize(View view, int size) {
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
+ lp.width = size;
+ lp.height = size;
+ view.setLayoutParams(lp);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index fe819574f3b6..0ccebc130b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -16,18 +16,20 @@
package com.android.systemui.statusbar.notification.row;
+import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
+import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+import com.android.internal.widget.ConversationLayout;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.NotificationDozeHelper;
-import com.android.systemui.statusbar.notification.NotificationUtils;
/**
* A class managing hybrid groups that include {@link HybridNotificationView} and the notification
@@ -36,41 +38,41 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
public class HybridGroupManager {
private final Context mContext;
- private final ViewGroup mParent;
private float mOverflowNumberSize;
private int mOverflowNumberPadding;
private int mOverflowNumberColor;
- public HybridGroupManager(Context ctx, ViewGroup parent) {
+ public HybridGroupManager(Context ctx) {
mContext = ctx;
- mParent = parent;
initDimens();
}
public void initDimens() {
Resources res = mContext.getResources();
- mOverflowNumberSize = res.getDimensionPixelSize(
- R.dimen.group_overflow_number_size);
- mOverflowNumberPadding = res.getDimensionPixelSize(
- R.dimen.group_overflow_number_padding);
+ mOverflowNumberSize = res.getDimensionPixelSize(R.dimen.group_overflow_number_size);
+ mOverflowNumberPadding = res.getDimensionPixelSize(R.dimen.group_overflow_number_padding);
}
- private HybridNotificationView inflateHybridViewWithStyle(int style) {
+ private HybridNotificationView inflateHybridViewWithStyle(int style,
+ View contentView, ViewGroup parent) {
LayoutInflater inflater = new ContextThemeWrapper(mContext, style)
.getSystemService(LayoutInflater.class);
- HybridNotificationView hybrid = (HybridNotificationView) inflater.inflate(
- R.layout.hybrid_notification, mParent, false);
- mParent.addView(hybrid);
+ int layout = contentView instanceof ConversationLayout
+ ? R.layout.hybrid_conversation_notification
+ : R.layout.hybrid_notification;
+ HybridNotificationView hybrid = (HybridNotificationView)
+ inflater.inflate(layout, parent, false);
+ parent.addView(hybrid);
return hybrid;
}
- private TextView inflateOverflowNumber() {
+ private TextView inflateOverflowNumber(ViewGroup parent) {
LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class);
TextView numberView = (TextView) inflater.inflate(
- R.layout.hybrid_overflow_number, mParent, false);
- mParent.addView(numberView);
+ R.layout.hybrid_overflow_number, parent, false);
+ parent.addView(numberView);
updateOverFlowNumberColor(numberView);
return numberView;
}
@@ -87,22 +89,26 @@ public class HybridGroupManager {
}
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
- Notification notification) {
- return bindFromNotificationWithStyle(reusableView, notification,
- R.style.HybridNotification);
+ View contentView, StatusBarNotification notification,
+ ViewGroup parent) {
+ return bindFromNotificationWithStyle(reusableView, contentView, notification,
+ R.style.HybridNotification, parent);
}
private HybridNotificationView bindFromNotificationWithStyle(
- HybridNotificationView reusableView, Notification notification, int style) {
+ HybridNotificationView reusableView, View contentView,
+ StatusBarNotification notification,
+ int style, ViewGroup parent) {
if (reusableView == null) {
- reusableView = inflateHybridViewWithStyle(style);
+ reusableView = inflateHybridViewWithStyle(style, contentView, parent);
}
- CharSequence titleText = resolveTitle(notification);
- CharSequence contentText = resolveText(notification);
- reusableView.bind(titleText, contentText);
+ CharSequence titleText = resolveTitle(notification.getNotification());
+ CharSequence contentText = resolveText(notification.getNotification());
+ reusableView.bind(titleText, contentText, contentView);
return reusableView;
}
+ @Nullable
private CharSequence resolveText(Notification notification) {
CharSequence contentText = notification.extras.getCharSequence(Notification.EXTRA_TEXT);
if (contentText == null) {
@@ -111,6 +117,7 @@ public class HybridGroupManager {
return contentText;
}
+ @Nullable
private CharSequence resolveTitle(Notification notification) {
CharSequence titleText = notification.extras.getCharSequence(Notification.EXTRA_TITLE);
if (titleText == null) {
@@ -119,9 +126,10 @@ public class HybridGroupManager {
return titleText;
}
- public TextView bindOverflowNumber(TextView reusableView, int number) {
+ public TextView bindOverflowNumber(TextView reusableView, int number,
+ ViewGroup parent) {
if (reusableView == null) {
- reusableView = inflateOverflowNumber();
+ reusableView = inflateOverflowNumber(parent);
}
String text = mContext.getResources().getString(
R.string.notification_group_overflow_indicator, number);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index be25d6377912..207144931c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -36,8 +36,7 @@ import com.android.systemui.statusbar.notification.TransformState;
public class HybridNotificationView extends AlphaOptimizedLinearLayout
implements TransformableView {
- private ViewTransformationHelper mTransformationHelper;
-
+ protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper();
protected TextView mTitleView;
protected TextView mTextView;
@@ -69,9 +68,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mTitleView = (TextView) findViewById(R.id.notification_title);
- mTextView = (TextView) findViewById(R.id.notification_text);
- mTransformationHelper = new ViewTransformationHelper();
+ mTitleView = findViewById(R.id.notification_title);
+ mTextView = findViewById(R.id.notification_text);
mTransformationHelper.setCustomTransformation(
new ViewTransformationHelper.CustomTransformation() {
@Override
@@ -106,11 +104,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
}
- public void bind(CharSequence title) {
- bind(title, null);
- }
-
- public void bind(CharSequence title, CharSequence text) {
+ public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
+ @Nullable View contentView) {
mTitleView.setText(title);
mTitleView.setVisibility(TextUtils.isEmpty(title) ? GONE : VISIBLE);
if (TextUtils.isEmpty(text)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 9d5443729d45..582e3e5b6c34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -132,7 +132,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mConversationProcessor,
row,
bindParams.isLowPriority,
- bindParams.isChildInGroup,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
callback,
@@ -156,7 +155,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
InflationProgress result = createRemoteViews(reInflateFlags,
builder,
bindParams.isLowPriority,
- bindParams.isChildInGroup,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
packageContext);
@@ -285,11 +283,9 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
private static InflationProgress createRemoteViews(@InflationFlag int reInflateFlags,
- Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup,
- boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight,
- Context packageContext) {
+ Notification.Builder builder, boolean isLowPriority, boolean usesIncreasedHeight,
+ boolean usesIncreasedHeadsUpHeight, Context packageContext) {
InflationProgress result = new InflationProgress();
- isLowPriority = isLowPriority && !isChildInGroup;
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
@@ -702,7 +698,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final Context mContext;
private final boolean mInflateSynchronously;
private final boolean mIsLowPriority;
- private final boolean mIsChildInGroup;
private final boolean mUsesIncreasedHeight;
private final InflationCallback mCallback;
private final boolean mUsesIncreasedHeadsUpHeight;
@@ -728,7 +723,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
ConversationNotificationProcessor conversationProcessor,
ExpandableNotificationRow row,
boolean isLowPriority,
- boolean isChildInGroup,
boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
@@ -743,7 +737,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteViewCache = cache;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
- mIsChildInGroup = isChildInGroup;
mUsesIncreasedHeight = usesIncreasedHeight;
mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
mRemoteViewClickHandler = remoteViewClickHandler;
@@ -781,7 +774,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mConversationProcessor.processNotification(mEntry, recoveredBuilder);
}
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
- recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
+ recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
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 3c3f1b21fb3c..e9849ec84987 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
@@ -16,13 +16,18 @@
package com.android.systemui.statusbar.notification.row;
+
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -47,6 +52,7 @@ import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -118,6 +124,7 @@ public class NotificationContentView extends FrameLayout {
private NotificationGroupManager mGroupManager;
private RemoteInputController mRemoteInputController;
private Runnable mExpandedVisibleListener;
+ private PeopleNotificationIdentifier mPeopleIdentifier;
/**
* List of listeners for when content views become inactive (i.e. not the showing view).
*/
@@ -170,7 +177,7 @@ public class NotificationContentView extends FrameLayout {
public NotificationContentView(Context context, AttributeSet attrs) {
super(context, attrs);
- mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ mHybridGroupManager = new HybridGroupManager(getContext());
mMediaTransferManager = new MediaTransferManager(getContext());
mSmartReplyConstants = Dependency.get(SmartReplyConstants.class);
mSmartReplyController = Dependency.get(SmartReplyController.class);
@@ -454,6 +461,9 @@ public class NotificationContentView extends FrameLayout {
mExpandedChild = child;
mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ if (mContainingNotification != null) {
+ applyBubbleAction(mExpandedChild, mContainingNotification.getEntry());
+ }
}
/**
@@ -493,6 +503,9 @@ public class NotificationContentView extends FrameLayout {
mHeadsUpChild = child;
mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
+ if (mContainingNotification != null) {
+ applyBubbleAction(mHeadsUpChild, mContainingNotification.getEntry());
+ }
}
@Override
@@ -1138,6 +1151,8 @@ public class NotificationContentView extends FrameLayout {
mForceSelectNextLayout = true;
mPreviousExpandedRemoteInputIntent = null;
mPreviousHeadsUpRemoteInputIntent = null;
+ applyBubbleAction(mExpandedChild, entry);
+ applyBubbleAction(mHeadsUpChild, entry);
}
private void updateAllSingleLineViews() {
@@ -1148,7 +1163,7 @@ public class NotificationContentView extends FrameLayout {
if (mIsChildInGroup) {
boolean isNewView = mSingleLineView == null;
mSingleLineView = mHybridGroupManager.bindFromNotification(
- mSingleLineView, mStatusBarNotification.getNotification());
+ mSingleLineView, mContractedChild, mStatusBarNotification, this);
if (isNewView) {
updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
mSingleLineView, mSingleLineView);
@@ -1308,6 +1323,58 @@ public class NotificationContentView extends FrameLayout {
return null;
}
+ /**
+ * Call to update state of the bubble button (i.e. does it show bubble or unbubble or no
+ * icon at all).
+ *
+ * @param entry the new entry to use.
+ */
+ public void updateBubbleButton(NotificationEntry entry) {
+ applyBubbleAction(mExpandedChild, entry);
+ }
+
+ private boolean isBubblesEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, 0) == 1;
+ }
+
+ private void applyBubbleAction(View layout, NotificationEntry entry) {
+ if (layout == null || mContainingNotification == null || mPeopleIdentifier == null) {
+ return;
+ }
+ ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button);
+ View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container);
+ if (bubbleButton == null || actionContainer == null) {
+ return;
+ }
+ boolean isPersonWithShortcut =
+ mPeopleIdentifier.getPeopleNotificationType(entry.getSbn(), entry.getRanking())
+ >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
+ boolean showButton = isBubblesEnabled()
+ && isPersonWithShortcut
+ && entry.getBubbleMetadata() != null;
+ if (showButton) {
+ Drawable d = mContext.getResources().getDrawable(entry.isBubble()
+ ? R.drawable.ic_stop_bubble
+ : R.drawable.ic_create_bubble);
+ mContainingNotification.updateNotificationColor();
+ final int tint = mContainingNotification.getNotificationColor();
+ d.setTint(tint);
+
+ String contentDescription = mContext.getResources().getString(entry.isBubble()
+ ? R.string.notification_conversation_unbubble
+ : R.string.notification_conversation_bubble);
+
+ bubbleButton.setContentDescription(contentDescription);
+ bubbleButton.setImageDrawable(d);
+ bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener());
+ bubbleButton.setVisibility(VISIBLE);
+ actionContainer.setVisibility(VISIBLE);
+ } else {
+ bubbleButton.setVisibility(GONE);
+ }
+ }
+
private void applySmartReplyView(
SmartRepliesAndActions smartRepliesAndActions,
NotificationEntry entry) {
@@ -1512,6 +1579,10 @@ public class NotificationContentView extends FrameLayout {
mContainingNotification = containingNotification;
}
+ public void setPeopleNotificationIdentifier(PeopleNotificationIdentifier peopleIdentifier) {
+ mPeopleIdentifier = peopleIdentifier;
+ }
+
public void requestSelectLayout(boolean needsAnimation) {
selectLayout(needsAnimation, false);
}
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 6fc1264d69e2..863951e655e9 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
@@ -17,9 +17,13 @@
package com.android.systemui.statusbar.notification.row;
import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
+import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.provider.Settings.Global.NOTIFICATION_BUBBLES;
import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
@@ -27,10 +31,12 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -38,11 +44,10 @@ import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Icon;
-import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.UserHandle;
+import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.transition.ChangeBounds;
@@ -51,7 +56,7 @@ import android.transition.TransitionManager;
import android.transition.TransitionSet;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Slog;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.ImageView;
@@ -61,13 +66,17 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
import com.android.systemui.Dependency;
+import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.lang.annotation.Retention;
import java.util.List;
+import javax.inject.Provider;
+
/**
* The guts of a conversation notification revealed when performing a long press.
*/
@@ -79,8 +88,8 @@ public class NotificationConversationInfo extends LinearLayout implements
private INotificationManager mINotificationManager;
ShortcutManager mShortcutManager;
private PackageManager mPm;
- private VisualStabilityManager mVisualStabilityManager;
private ConversationIconFactory mIconFactory;
+ private VisualStabilityManager mVisualStabilityManager;
private String mPackageName;
private String mAppName;
@@ -88,14 +97,19 @@ public class NotificationConversationInfo extends LinearLayout implements
private String mDelegatePkg;
private NotificationChannel mNotificationChannel;
private ShortcutInfo mShortcutInfo;
- private String mConversationId;
private StatusBarNotification mSbn;
+ @Nullable private Notification.BubbleMetadata mBubbleMetadata;
+ private Context mUserContext;
+ private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
private boolean mIsDeviceProvisioned;
+ private int mAppBubble;
private TextView mPriorityDescriptionView;
private TextView mDefaultDescriptionView;
private TextView mSilentDescriptionView;
+
private @Action int mSelectedAction = -1;
+ private boolean mPressedApply;
private OnSnoozeClickListener mOnSnoozeClickListener;
private OnSettingsClickListener mOnSettingsClickListener;
@@ -132,21 +146,22 @@ public class NotificationConversationInfo extends LinearLayout implements
*/
private OnClickListener mOnFavoriteClick = v -> {
- mSelectedAction = ACTION_FAVORITE;
+ setSelectedAction(ACTION_FAVORITE);
updateToggleActions(mSelectedAction, true);
};
private OnClickListener mOnDefaultClick = v -> {
- mSelectedAction = ACTION_DEFAULT;
+ setSelectedAction(ACTION_DEFAULT);
updateToggleActions(mSelectedAction, true);
};
private OnClickListener mOnMuteClick = v -> {
- mSelectedAction = ACTION_MUTE;
+ setSelectedAction(ACTION_MUTE);
updateToggleActions(mSelectedAction, true);
};
private OnClickListener mOnDone = v -> {
+ mPressedApply = true;
closeControls(v, true);
};
@@ -166,6 +181,23 @@ public class NotificationConversationInfo extends LinearLayout implements
void onClick(View v, int hoursToSnooze);
}
+ @VisibleForTesting
+ void setSelectedAction(int selectedAction) {
+ if (mSelectedAction == selectedAction) {
+ return;
+ }
+
+ mSelectedAction = selectedAction;
+ onSelectedActionChanged();
+ }
+
+ private void onSelectedActionChanged() {
+ // If the user selected Priority, maybe show the priority onboarding
+ if (mSelectedAction == ACTION_FAVORITE && shouldShowPriorityOnboarding()) {
+ showPriorityOnboarding();
+ }
+ }
+
public void bindNotification(
ShortcutManager shortcutManager,
PackageManager pm,
@@ -174,9 +206,12 @@ public class NotificationConversationInfo extends LinearLayout implements
String pkg,
NotificationChannel notificationChannel,
NotificationEntry entry,
+ Notification.BubbleMetadata bubbleMetadata,
OnSettingsClickListener onSettingsClick,
OnSnoozeClickListener onSnoozeClickListener,
ConversationIconFactory conversationIconFactory,
+ Context userContext,
+ Provider<PriorityOnboardingDialogController.Builder> builderProvider,
boolean isDeviceProvisioned) {
mSelectedAction = -1;
mINotificationManager = iNotificationManager;
@@ -192,18 +227,25 @@ public class NotificationConversationInfo extends LinearLayout implements
mIsDeviceProvisioned = isDeviceProvisioned;
mOnSnoozeClickListener = onSnoozeClickListener;
mIconFactory = conversationIconFactory;
+ mUserContext = userContext;
+ mBubbleMetadata = bubbleMetadata;
+ mBuilderProvider = builderProvider;
mShortcutManager = shortcutManager;
- mConversationId = mNotificationChannel.getConversationId();
- if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
- mConversationId = mSbn.getShortcutId(mContext);
- }
- if (TextUtils.isEmpty(mConversationId)) {
+ mShortcutInfo = entry.getRanking().getShortcutInfo();
+ if (mShortcutInfo == null) {
throw new IllegalArgumentException("Does not have required information");
}
- mShortcutInfo = entry.getRanking().getShortcutInfo();
- createConversationChannelIfNeeded();
+ mNotificationChannel = NotificationChannelHelper.createConversationChannelIfNeeded(
+ getContext(), mINotificationManager, entry, mNotificationChannel);
+
+ try {
+ mAppBubble = mINotificationManager.getBubblePreferenceForPackage(mPackageName, mAppUid);
+ } catch (RemoteException e) {
+ Log.e(TAG, "can't reach OS", e);
+ mAppBubble = BUBBLE_PREFERENCE_SELECTED;
+ }
bindHeader();
bindActions();
@@ -212,27 +254,6 @@ public class NotificationConversationInfo extends LinearLayout implements
done.setOnClickListener(mOnDone);
}
- void createConversationChannelIfNeeded() {
- // If this channel is not already a customized conversation channel, create
- // a custom channel
- if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
- try {
- // TODO: remove
- mNotificationChannel.setName(mContext.getString(
- R.string.notification_summary_message_format,
- getName(), mNotificationChannel.getName()));
- mINotificationManager.createConversationNotificationChannelForPackage(
- mPackageName, mAppUid, mSbn.getKey(), mNotificationChannel,
- mConversationId);
- mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
- mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
- mNotificationChannel.getId(), false, mConversationId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Could not create conversation channel", e);
- }
- }
- }
-
private void bindActions() {
// TODO: b/152050825
@@ -247,6 +268,11 @@ public class NotificationConversationInfo extends LinearLayout implements
snooze.setOnClickListener(mOnSnoozeClick);
*/
+ if (mAppBubble == BUBBLE_PREFERENCE_ALL) {
+ ((TextView) findViewById(R.id.default_summary)).setText(getResources().getString(
+ R.string.notification_channel_summary_default_with_bubbles, mAppName));
+ }
+
findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick);
findViewById(R.id.default_behavior).setOnClickListener(mOnDefaultClick);
findViewById(R.id.silence).setOnClickListener(mOnMuteClick);
@@ -284,54 +310,13 @@ public class NotificationConversationInfo extends LinearLayout implements
// bindName();
bindPackage();
bindIcon(mNotificationChannel.isImportantConversation());
-
}
private void bindIcon(boolean important) {
ImageView image = findViewById(R.id.conversation_icon);
- if (mShortcutInfo != null) {
- image.setImageDrawable(mIconFactory.getConversationDrawable(
- mShortcutInfo, mPackageName, mAppUid,
- important));
- } else {
- if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
- // TODO: maybe use a generic group icon, or a composite of recent senders
- image.setImageDrawable(mPm.getDefaultActivityIcon());
- } else {
- final List<Notification.MessagingStyle.Message> messages =
- Notification.MessagingStyle.Message.getMessagesFromBundleArray(
- (Parcelable[]) mSbn.getNotification().extras.get(
- Notification.EXTRA_MESSAGES));
-
- final Notification.MessagingStyle.Message latestMessage =
- Notification.MessagingStyle.findLatestIncomingMessage(messages);
- Icon personIcon = latestMessage.getSenderPerson().getIcon();
- if (personIcon != null) {
- image.setImageIcon(latestMessage.getSenderPerson().getIcon());
- } else {
- // TODO: choose something better
- image.setImageDrawable(mPm.getDefaultActivityIcon());
- }
- }
- }
- }
+ image.setImageDrawable(mIconFactory.getConversationDrawable(
+ mShortcutInfo, mPackageName, mAppUid, important));
- private void bindName() {
- TextView name = findViewById(R.id.name);
- name.setText(getName());
- }
-
- private String getName() {
- if (mShortcutInfo != null) {
- return mShortcutInfo.getShortLabel().toString();
- } else {
- Bundle extras = mSbn.getNotification().extras;
- String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
- if (TextUtils.isEmpty(nameString)) {
- nameString = extras.getString(Notification.EXTRA_TITLE);
- }
- return nameString;
- }
}
private void bindPackage() {
@@ -512,6 +497,40 @@ public class NotificationConversationInfo extends LinearLayout implements
bgHandler.post(
new UpdateChannelRunnable(mINotificationManager, mPackageName,
mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
+ }
+
+ private boolean shouldShowPriorityOnboarding() {
+ return !Prefs.getBoolean(mUserContext, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, false);
+ }
+
+ private void showPriorityOnboarding() {
+ View onboardingView = LayoutInflater.from(mContext)
+ .inflate(R.layout.priority_onboarding_half_shell, null);
+
+ boolean ignoreDnd = false;
+ try {
+ ignoreDnd = (mINotificationManager
+ .getConsolidatedNotificationPolicy().priorityConversationSenders
+ & NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT) != 0;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not check conversation senders", e);
+ }
+
+ boolean showAsBubble = mBubbleMetadata != null
+ && mBubbleMetadata.getAutoExpandBubble()
+ && Settings.Global.getInt(mContext.getContentResolver(),
+ NOTIFICATION_BUBBLES, 0) == 1;
+
+ PriorityOnboardingDialogController controller = mBuilderProvider.get()
+ .setContext(mUserContext)
+ .setView(onboardingView)
+ .setIgnoresDnd(ignoreDnd)
+ .setShowsAsBubble(showAsBubble)
+ .build();
+
+ controller.init();
+ controller.show();
}
/**
@@ -545,7 +564,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Override
public boolean shouldBeSaved() {
- return mSelectedAction == ACTION_FAVORITE || mSelectedAction == ACTION_MUTE;
+ return mPressedApply;
}
@Override
@@ -598,6 +617,10 @@ public class NotificationConversationInfo extends LinearLayout implements
!mChannelToUpdate.isImportantConversation());
if (mChannelToUpdate.isImportantConversation()) {
mChannelToUpdate.setAllowBubbles(true);
+ if (mAppBubble == BUBBLE_PREFERENCE_NONE) {
+ mINotificationManager.setBubblesAllowed(mAppPkg, mAppUid,
+ BUBBLE_PREFERENCE_SELECTED);
+ }
}
mChannelToUpdate.setImportance(Math.max(
mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
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 2487d1a898a3..75affdf20364 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
@@ -49,6 +49,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.CurrentUserContextTracker;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -67,6 +68,8 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Provider;
+
import dagger.Lazy;
/**
@@ -111,6 +114,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
private final INotificationManager mNotificationManager;
private final LauncherApps mLauncherApps;
private final ShortcutManager mShortcutManager;
+ private final CurrentUserContextTracker mContextTracker;
+ private final Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider;
/**
* Injected constructor. See {@link NotificationsModule}.
@@ -121,7 +126,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
LauncherApps launcherApps,
- ShortcutManager shortcutManager) {
+ ShortcutManager shortcutManager,
+ CurrentUserContextTracker contextTracker,
+ Provider<PriorityOnboardingDialogController.Builder> builderProvider) {
mContext = context;
mVisualStabilityManager = visualStabilityManager;
mStatusBarLazy = statusBarLazy;
@@ -131,6 +138,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mNotificationManager = notificationManager;
mLauncherApps = launcherApps;
mShortcutManager = shortcutManager;
+ mContextTracker = contextTracker;
+ mBuilderProvider = builderProvider;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -243,6 +252,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
} else if (gutsView instanceof NotificationConversationInfo) {
initializeConversationNotificationInfo(
row, (NotificationConversationInfo) gutsView);
+ } else if (gutsView instanceof PartialConversationInfo) {
+ initializePartialConversationNotificationInfo(row,
+ (PartialConversationInfo) gutsView);
}
return true;
} catch (Exception e) {
@@ -348,7 +360,47 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
}
/**
- * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+ * Sets up the {@link PartialConversationInfo} inside the notification row's guts.
+ * @param row view to set up the guts for
+ * @param notificationInfoView view to set up/bind within {@code row}
+ */
+ @VisibleForTesting
+ void initializePartialConversationNotificationInfo(
+ final ExpandableNotificationRow row,
+ PartialConversationInfo notificationInfoView) throws Exception {
+ NotificationGuts guts = row.getGuts();
+ StatusBarNotification sbn = row.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+ // Settings link is only valid for notifications that specify a non-system user
+ NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+ UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(
+ mContext, userHandle.getIdentifier());
+
+ if (!userHandle.equals(UserHandle.ALL)
+ || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+ startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ };
+ }
+
+ notificationInfoView.bindNotification(
+ pmUser,
+ mNotificationManager,
+ packageName,
+ row.getEntry().getChannel(),
+ row.getUniqueChannels(),
+ row.getEntry(),
+ onSettingsClick,
+ mDeviceProvisionedController.isDeviceProvisioned(),
+ row.getIsNonblockable());
+ }
+
+ /**
+ * Sets up the {@link ConversationInfo} inside the notification row's guts.
* @param row view to set up the guts for
* @param notificationInfoView view to set up/bind within {@code row}
*/
@@ -357,7 +409,8 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
final ExpandableNotificationRow row,
NotificationConversationInfo notificationInfoView) throws Exception {
NotificationGuts guts = row.getGuts();
- StatusBarNotification sbn = row.getEntry().getSbn();
+ NotificationEntry entry = row.getEntry();
+ StatusBarNotification sbn = entry.getSbn();
String packageName = sbn.getPackageName();
// Settings link is only valid for notifications that specify a non-system user
NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
@@ -384,7 +437,6 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
guts.resetFalsingCheck();
mOnSettingsClickListener.onSettingsClick(sbn.getKey());
startAppNotificationSettingsActivity(packageName, appUid, channel, row);
- notificationInfoView.closeControls(v, false);
};
}
ConversationIconFactory iconFactoryLoader = new ConversationIconFactory(mContext,
@@ -398,11 +450,14 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx
mNotificationManager,
mVisualStabilityManager,
packageName,
- row.getEntry().getChannel(),
- row.getEntry(),
+ entry.getChannel(),
+ entry,
+ entry.getBubbleMetadata(),
onSettingsClick,
onSnoozeClickListener,
iconFactoryLoader,
+ mContextTracker.getCurrentUserContext(),
+ mBuilderProvider,
mDeviceProvisionedController.isDeviceProvisioned());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index 83a6eb297ab3..5e1e3b255867 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -268,7 +268,9 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
NotificationEntry entry = mParent.getEntry();
int personNotifType = mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking());
- if (personNotifType != PeopleNotificationIdentifier.TYPE_NON_PERSON) {
+ if (personNotifType == PeopleNotificationIdentifier.TYPE_PERSON) {
+ mInfoItem = createPartialConversationItem(mContext);
+ } else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
@@ -667,6 +669,16 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
R.drawable.ic_settings);
}
+ static NotificationMenuItem createPartialConversationItem(Context context) {
+ Resources res = context.getResources();
+ String infoDescription = res.getString(R.string.notification_menu_gear_description);
+ PartialConversationInfo infoContent =
+ (PartialConversationInfo) LayoutInflater.from(context).inflate(
+ R.layout.partial_conversation_info, null, false);
+ return new NotificationMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings);
+ }
+
static NotificationMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 9bd8d4782672..a9f83c8b9e6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -114,11 +114,6 @@ public interface NotificationRowContentBinder {
public boolean isLowPriority;
/**
- * Bind child version of content views.
- */
- public boolean isChildInGroup;
-
- /**
* Use increased height when binding contracted view.
*/
public boolean usesIncreasedHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
new file mode 100644
index 000000000000..2189b872da43
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PartialConversationInfo.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+
+import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.transition.ChangeBounds;
+import android.transition.Fade;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The guts of a conversation notification that doesn't use valid shortcuts that is revealed when
+ * performing a long press.
+ */
+public class PartialConversationInfo extends LinearLayout implements
+ NotificationGuts.GutsContent {
+ private static final String TAG = "PartialConvoGuts";
+
+ private INotificationManager mINotificationManager;
+ private PackageManager mPm;
+ private String mPackageName;
+ private String mAppName;
+ private int mAppUid;
+ private String mDelegatePkg;
+ private NotificationChannel mNotificationChannel;
+ private StatusBarNotification mSbn;
+ private boolean mIsDeviceProvisioned;
+ private boolean mIsNonBlockable;
+ private Set<NotificationChannel> mUniqueChannelsInRow;
+ private Drawable mPkgIcon;
+
+ private @Action int mSelectedAction = -1;
+ private boolean mPressedApply;
+ private boolean mPresentingChannelEditorDialog = false;
+
+ private NotificationInfo.OnSettingsClickListener mOnSettingsClickListener;
+ private NotificationGuts mGutsContainer;
+ private ChannelEditorDialogController mChannelEditorDialogController;
+
+ @VisibleForTesting
+ boolean mSkipPost = false;
+
+ @Retention(SOURCE)
+ @IntDef({ACTION_SETTINGS})
+ private @interface Action {}
+ static final int ACTION_SETTINGS = 5;
+
+ private OnClickListener mOnDone = v -> {
+ mPressedApply = true;
+ closeControls(v, true);
+ };
+
+ public PartialConversationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public void bindNotification(
+ PackageManager pm,
+ INotificationManager iNotificationManager,
+ String pkg,
+ NotificationChannel notificationChannel,
+ Set<NotificationChannel> uniqueChannelsInRow,
+ NotificationEntry entry,
+ NotificationInfo.OnSettingsClickListener onSettingsClick,
+ boolean isDeviceProvisioned,
+ boolean isNonBlockable) {
+ mSelectedAction = -1;
+ mINotificationManager = iNotificationManager;
+ mPackageName = pkg;
+ mSbn = entry.getSbn();
+ mPm = pm;
+ mAppName = mPackageName;
+ mOnSettingsClickListener = onSettingsClick;
+ mNotificationChannel = notificationChannel;
+ mAppUid = mSbn.getUid();
+ mDelegatePkg = mSbn.getOpPkg();
+ mIsDeviceProvisioned = isDeviceProvisioned;
+ mIsNonBlockable = isNonBlockable;
+ mChannelEditorDialogController = Dependency.get(ChannelEditorDialogController.class);
+ mUniqueChannelsInRow = uniqueChannelsInRow;
+
+ bindHeader();
+ bindActions();
+
+ View turnOffButton = findViewById(R.id.turn_off_notifications);
+ turnOffButton.setOnClickListener(getTurnOffNotificationsClickListener());
+ turnOffButton.setVisibility(turnOffButton.hasOnClickListeners() && !mIsNonBlockable
+ ? VISIBLE : GONE);
+
+ View done = findViewById(R.id.done);
+ done.setOnClickListener(mOnDone);
+ }
+
+ private void bindActions() {
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+
+ TextView msg = findViewById(R.id.non_configurable_text);
+ msg.setText(getResources().getString(R.string.no_shortcut, mAppName));
+ }
+
+ private void bindHeader() {
+ bindConversationDetails();
+
+ // Delegate
+ bindDelegate();
+ }
+
+ private OnClickListener getSettingsOnClickListener() {
+ if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+ final int appUidF = mAppUid;
+ return ((View view) -> {
+ mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+ });
+ }
+ return null;
+ }
+
+ private OnClickListener getTurnOffNotificationsClickListener() {
+ return ((View view) -> {
+ if (!mPresentingChannelEditorDialog && mChannelEditorDialogController != null) {
+ mPresentingChannelEditorDialog = true;
+
+ mChannelEditorDialogController.prepareDialogForApp(mAppName, mPackageName, mAppUid,
+ mUniqueChannelsInRow, mPkgIcon, mOnSettingsClickListener);
+ mChannelEditorDialogController.setOnFinishListener(() -> {
+ mPresentingChannelEditorDialog = false;
+ closeControls(this, false);
+ });
+ mChannelEditorDialogController.show();
+ }
+ });
+ }
+
+ private void bindConversationDetails() {
+ final TextView channelName = findViewById(R.id.parent_channel_name);
+ channelName.setText(mNotificationChannel.getName());
+
+ bindGroup();
+ bindName();
+ bindPackage();
+ bindIcon();
+ }
+
+ private void bindName() {
+ TextView name = findViewById(R.id.name);
+ Bundle extras = mSbn.getNotification().extras;
+ String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = extras.getString(Notification.EXTRA_TITLE);
+ }
+ name.setText(nameString);
+ }
+
+ private void bindIcon() {
+ ImageView image = findViewById(R.id.conversation_icon);
+ if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+ // TODO: maybe use a generic group icon, or a composite of recent senders
+ image.setImageDrawable(mPkgIcon);
+ } else {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) mSbn.getNotification().extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ Icon personIcon = null;
+ if (latestMessage != null && latestMessage.getSenderPerson() != null) {
+ personIcon = latestMessage.getSenderPerson().getIcon();
+ }
+ if (personIcon != null) {
+ image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+ } else {
+ image.setImageDrawable(mPkgIcon);
+ }
+ }
+ }
+
+ private void bindPackage() {
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(
+ mPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ mPkgIcon = mPm.getApplicationIcon(info);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ mPkgIcon = mPm.getDefaultActivityIcon();
+ }
+ ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+ }
+
+ private void bindDelegate() {
+ TextView delegateView = findViewById(R.id.delegate_name);
+
+ if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+ // this notification was posted by a delegate!
+ delegateView.setVisibility(View.VISIBLE);
+ } else {
+ delegateView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindGroup() {
+ // Set group information if this channel has an associated group.
+ CharSequence groupName = null;
+ if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+ try {
+ final NotificationChannelGroup notificationChannelGroup =
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mNotificationChannel.getGroup(), mPackageName, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ TextView groupNameView = findViewById(R.id.group_name);
+ View groupDivider = findViewById(R.id.group_divider);
+ if (groupName != null) {
+ groupNameView.setText(groupName);
+ groupNameView.setVisibility(VISIBLE);
+ groupDivider.setVisibility(VISIBLE);
+ } else {
+ groupNameView.setVisibility(GONE);
+ groupDivider.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ public boolean post(Runnable action) {
+ if (mSkipPost) {
+ action.run();
+ return true;
+ } else {
+ return super.post(action);
+ }
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ }
+
+ @Override
+ public void onFinishedClosing() {
+ // TODO: do we need to do anything here?
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mGutsContainer != null &&
+ event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ if (mGutsContainer.isExposed()) {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_opened_accessibility, mAppName));
+ } else {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_closed_accessibility, mAppName));
+ }
+ }
+ }
+
+ /**
+ * Closes the controls and commits the updated importance values (indirectly).
+ *
+ * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+ * user does not have the ability to undo the action anymore.
+ */
+ @VisibleForTesting
+ void closeControls(View v, boolean save) {
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ mGutsContainer.closeControls(x, y, save, false /* force */);
+ }
+
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ }
+
+ @Override
+ public boolean willBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeSaved() {
+ return mPressedApply;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls(boolean save, boolean force) {
+ return false;
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @VisibleForTesting
+ public boolean isAnimating() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
new file mode 100644
index 000000000000..d1b405256f39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PriorityOnboardingDialogController.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.View
+import android.view.View.GONE
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.Window
+import android.view.WindowInsets.Type.statusBars
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.systemui.Prefs
+import com.android.systemui.R
+import java.lang.IllegalStateException
+import javax.inject.Inject
+
+/**
+ * Controller to handle presenting the priority conversations onboarding dialog
+ */
+class PriorityOnboardingDialogController @Inject constructor(
+ val view: View,
+ val context: Context,
+ val ignoresDnd: Boolean,
+ val showsAsBubble: Boolean
+) {
+
+ private lateinit var dialog: Dialog
+
+ fun init() {
+ initDialog()
+ }
+
+ fun show() {
+ dialog.show()
+ }
+
+ private fun done() {
+ // Log that the user has seen the onboarding
+ Prefs.putBoolean(context, Prefs.Key.HAS_SEEN_PRIORITY_ONBOARDING, true)
+ dialog.dismiss()
+ }
+
+ class Builder @Inject constructor() {
+ private lateinit var view: View
+ private lateinit var context: Context
+ private var ignoresDnd = false
+ private var showAsBubble = false
+
+ fun setView(v: View): Builder {
+ view = v
+ return this
+ }
+
+ fun setContext(c: Context): Builder {
+ context = c
+ return this
+ }
+
+ fun setIgnoresDnd(ignore: Boolean): Builder {
+ ignoresDnd = ignore
+ return this
+ }
+
+ fun setShowsAsBubble(bubble: Boolean): Builder {
+ showAsBubble = bubble
+ return this
+ }
+
+ fun build(): PriorityOnboardingDialogController {
+ val controller = PriorityOnboardingDialogController(
+ view, context, ignoresDnd, showAsBubble)
+ return controller
+ }
+ }
+
+ private fun initDialog() {
+ dialog = Dialog(context)
+
+ if (dialog.window == null) {
+ throw IllegalStateException("Need a window for the onboarding dialog to show")
+ }
+
+ dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
+ // Prevent a11y readers from reading the first element in the dialog twice
+ dialog.setTitle("\u00A0")
+ dialog.apply {
+ setContentView(view)
+ setCanceledOnTouchOutside(true)
+
+ findViewById<TextView>(R.id.done_button)?.setOnClickListener {
+ done()
+ }
+
+ if (!ignoresDnd) {
+ findViewById<LinearLayout>(R.id.ignore_dnd_tip).visibility = GONE
+ }
+
+ if (!showsAsBubble) {
+ findViewById<LinearLayout>(R.id.floating_bubble_tip).visibility = GONE
+ }
+
+ window?.apply {
+ setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ addFlags(wmFlags)
+ setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+ setWindowAnimations(com.android.internal.R.style.Animation_InputMethod)
+
+ attributes = attributes.apply {
+ format = PixelFormat.TRANSLUCENT
+ title = ChannelEditorDialogController::class.java.simpleName
+ gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+ fitInsetsTypes = attributes.fitInsetsTypes and statusBars().inv()
+ width = MATCH_PARENT
+ height = WRAP_CONTENT
+ }
+ }
+ }
+ }
+
+ private val wmFlags = (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+ or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index d3fec695f012..f26ecc32821d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
*/
public final class RowContentBindParams {
private boolean mUseLowPriority;
- private boolean mUseChildInGroup;
private boolean mUseIncreasedHeight;
private boolean mUseIncreasedHeadsUpHeight;
private boolean mViewsNeedReinflation;
@@ -56,20 +55,6 @@ public final class RowContentBindParams {
}
/**
- * Set whether content should use group child version of its content views.
- */
- public void setUseChildInGroup(boolean useChildInGroup) {
- if (mUseChildInGroup != useChildInGroup) {
- mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED);
- }
- mUseChildInGroup = useChildInGroup;
- }
-
- public boolean useChildInGroup() {
- return mUseChildInGroup;
- }
-
- /**
* Set whether content should use an increased height version of its contracted view.
*/
public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) {
@@ -163,10 +148,10 @@ public final class RowContentBindParams {
@Override
public String toString() {
return String.format("RowContentBindParams[mContentViews=%x mDirtyContentViews=%x "
- + "mUseLowPriority=%b mUseChildInGroup=%b mUseIncreasedHeight=%b "
+ + "mUseLowPriority=%b mUseIncreasedHeight=%b "
+ "mUseIncreasedHeadsUpHeight=%b mViewsNeedReinflation=%b]",
- mContentViews, mDirtyContentViews, mUseLowPriority, mUseChildInGroup,
- mUseIncreasedHeight, mUseIncreasedHeadsUpHeight, mViewsNeedReinflation);
+ mContentViews, mDirtyContentViews, mUseLowPriority, mUseIncreasedHeight,
+ mUseIncreasedHeadsUpHeight, mViewsNeedReinflation);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index c632f3eb22a2..c6f0a135cd34 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -71,7 +71,6 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
BindParams bindParams = new BindParams();
bindParams.isLowPriority = params.useLowPriority();
- bindParams.isChildInGroup = params.useChildInGroup();
bindParams.usesIncreasedHeight = params.useIncreasedHeight();
bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight();
boolean forceInflate = params.needsReinflation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
index 13e7fe5cd6d8..15499b87d56d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt
@@ -46,13 +46,13 @@ class NotificationConversationTemplateViewWrapper constructor(
)
private val conversationLayout: ConversationLayout = view as ConversationLayout
- private lateinit var conversationIcon: CachingIconView
+ private lateinit var conversationIconView: CachingIconView
private lateinit var conversationBadgeBg: View
private lateinit var expandButton: View
private lateinit var expandButtonContainer: View
private lateinit var imageMessageContainer: ViewGroup
private lateinit var messagingLinearLayout: MessagingLinearLayout
- private lateinit var conversationTitle: View
+ private lateinit var conversationTitleView: View
private lateinit var importanceRing: View
private lateinit var appName: View
private var facePileBottomBg: View? = null
@@ -63,7 +63,7 @@ class NotificationConversationTemplateViewWrapper constructor(
messagingLinearLayout = conversationLayout.messagingLinearLayout
imageMessageContainer = conversationLayout.imageMessageContainer
with(conversationLayout) {
- conversationIcon = requireViewById(com.android.internal.R.id.conversation_icon)
+ conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
expandButton = requireViewById(com.android.internal.R.id.expand_button)
@@ -71,7 +71,7 @@ class NotificationConversationTemplateViewWrapper constructor(
requireViewById(com.android.internal.R.id.expand_button_container)
importanceRing = requireViewById(com.android.internal.R.id.conversation_icon_badge_ring)
appName = requireViewById(com.android.internal.R.id.app_name_text)
- conversationTitle = requireViewById(com.android.internal.R.id.conversation_text)
+ conversationTitleView = requireViewById(com.android.internal.R.id.conversation_text)
facePileTop = findViewById(com.android.internal.R.id.conversation_face_pile_top)
facePileBottom = findViewById(com.android.internal.R.id.conversation_face_pile_bottom)
facePileBottomBg =
@@ -93,7 +93,7 @@ class NotificationConversationTemplateViewWrapper constructor(
addTransformedViews(
messagingLinearLayout,
appName,
- conversationTitle)
+ conversationTitleView)
// Let's ignore the image message container since that is transforming as part of the
// messages already
@@ -124,7 +124,7 @@ class NotificationConversationTemplateViewWrapper constructor(
)
addViewsTransformingToSimilar(
- conversationIcon,
+ conversationIconView,
conversationBadgeBg,
expandButton,
importanceRing,
@@ -136,29 +136,27 @@ class NotificationConversationTemplateViewWrapper constructor(
override fun setShelfIconVisible(visible: Boolean) {
if (conversationLayout.isImportantConversation) {
- if (conversationIcon.visibility != GONE) {
- conversationIcon.setForceHidden(visible);
+ if (conversationIconView.visibility != GONE) {
+ conversationIconView.isForceHidden = visible
// We don't want the small icon to be hidden by the extended wrapper, as force
// hiding the conversationIcon will already do that via its listener.
- return;
+ return
}
}
super.setShelfIconVisible(visible)
}
- override fun getShelfTransformationTarget(): View? {
- if (conversationLayout.isImportantConversation) {
- if (conversationIcon.visibility != GONE) {
- return conversationIcon
- } else {
- // A notification with a fallback icon was set to important. Currently
- // the transformation doesn't work for these and needs to be fixed. In the meantime
- // those are using the icon.
- return super.getShelfTransformationTarget();
- }
- }
- return super.getShelfTransformationTarget()
- }
+ override fun getShelfTransformationTarget(): View? =
+ if (conversationLayout.isImportantConversation)
+ if (conversationIconView.visibility != GONE)
+ conversationIconView
+ else
+ // A notification with a fallback icon was set to important. Currently
+ // the transformation doesn't work for these and needs to be fixed.
+ // In the meantime those are using the icon.
+ super.getShelfTransformationTarget()
+ else
+ super.getShelfTransformationTarget()
override fun setRemoteInputVisible(visible: Boolean) =
conversationLayout.showHistoricMessages(visible)
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 7ac066277c86..f8b783113ccb 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
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.row.wrapper;
import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
-import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.Notification;
import android.content.Context;
@@ -28,11 +27,13 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
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;
@@ -61,12 +62,14 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
private NotificationExpandButton mExpandButton;
protected NotificationHeaderView mNotificationHeader;
private TextView mHeaderText;
+ private TextView mAppNameText;
private ImageView mWorkProfileImage;
private View mCameraIcon;
private View mMicIcon;
private View mOverlayIcon;
private View mAppOps;
private View mAudiblyAlertedIcon;
+ private FrameLayout mIconContainer;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
@@ -109,8 +112,10 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
protected void resolveHeaderViews() {
+ mIconContainer = mView.findViewById(com.android.internal.R.id.header_icon_container);
mIcon = mView.findViewById(com.android.internal.R.id.icon);
mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
+ mAppNameText = mView.findViewById(com.android.internal.R.id.app_name_text);
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);
@@ -133,15 +138,6 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
if (mAppOps != null) {
mAppOps.setOnClickListener(listener);
}
- if (mCameraIcon != null) {
- mCameraIcon.setOnClickListener(listener);
- }
- if (mMicIcon != null) {
- mMicIcon.setOnClickListener(listener);
- }
- if (mOverlayIcon != null) {
- mOverlayIcon.setOnClickListener(listener);
- }
}
/**
@@ -192,6 +188,61 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
}
}
+ public void applyConversationSkin() {
+ if (mAppNameText != null) {
+ mAppNameText.setTextAppearance(
+ com.android.internal.R.style
+ .TextAppearance_DeviceDefault_Notification_Conversation_AppName);
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) mAppNameText.getLayoutParams();
+ layoutParams.setMarginStart(0);
+ }
+ if (mIconContainer != null) {
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) mIconContainer.getLayoutParams();
+ layoutParams.width =
+ mIconContainer.getContext().getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.conversation_content_start);
+ final int marginStart =
+ mIconContainer.getContext().getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_content_margin_start);
+ layoutParams.setMarginStart(marginStart * -1);
+ }
+ if (mIcon != null) {
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ layoutParams.setMarginEnd(0);
+ }
+ }
+
+ public void clearConversationSkin() {
+ if (mAppNameText != null) {
+ final int textAppearance = Utils.getThemeAttr(
+ mAppNameText.getContext(),
+ 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();
+ 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();
+ layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ layoutParams.setMarginStart(0);
+ }
+ if (mIcon != null) {
+ ViewGroup.MarginLayoutParams layoutParams =
+ (ViewGroup.MarginLayoutParams) mIcon.getLayoutParams();
+ final int marginEnd = mIcon.getContext().getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_header_icon_margin_end);
+ layoutParams.setMarginEnd(marginEnd);
+ }
+ }
+
/**
* Adds the remaining TransformTypes to the TransformHelper. This is done to make sure that each
* child is faded automatically and doesn't have to be manually added.
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 874d81db0bd2..b96cff830f31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
import android.media.MediaMetadata;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -187,21 +188,26 @@ public class NotificationMediaTemplateViewWrapper extends NotificationTemplateVi
com.android.systemui.R.id.quick_qs_panel);
StatusBarNotification sbn = mRow.getEntry().getSbn();
Notification notif = sbn.getNotification();
+ Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
panel.getMediaPlayer().setMediaSession(token,
- notif.getSmallIcon(),
+ iconDrawable,
+ notif.getLargeIcon(),
tintColor,
mBackgroundColor,
mActions,
compactActions,
- notif.contentIntent);
+ notif.contentIntent,
+ sbn.getKey());
QSPanel bigPanel = ctrl.getNotificationShadeView().findViewById(
com.android.systemui.R.id.quick_settings_panel);
bigPanel.addMediaSession(token,
- notif.getSmallIcon(),
+ iconDrawable,
+ notif.getLargeIcon(),
tintColor,
mBackgroundColor,
mActions,
- sbn);
+ sbn,
+ sbn.getKey());
}
boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
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 400e794b820b..c9b1318feb13 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
@@ -40,6 +40,7 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridGroupManager;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
+import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import java.util.ArrayList;
@@ -65,7 +66,7 @@ public class NotificationChildrenContainer extends ViewGroup {
}.setDuration(200);
private final List<View> mDividers = new ArrayList<>();
- private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
+ private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
private final HybridGroupManager mHybridGroupManager;
private int mChildPadding;
private int mDividerHeight;
@@ -99,12 +100,14 @@ public class NotificationChildrenContainer extends ViewGroup {
private boolean mIsLowPriority;
private OnClickListener mHeaderClickListener;
private ViewGroup mCurrentHeader;
+ private boolean mIsConversation;
private boolean mShowDividersWhenExpanded;
private boolean mHideDividersDuringExpand;
private int mTranslationForHeader;
private int mCurrentHeaderTranslation = 0;
private float mHeaderVisibleAmount = 1.0f;
+ private int mUntruncatedChildCount;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -121,7 +124,7 @@ public class NotificationChildrenContainer extends ViewGroup {
public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mHybridGroupManager = new HybridGroupManager(getContext(), this);
+ mHybridGroupManager = new HybridGroupManager(getContext());
initDimens();
setClipChildren(false);
}
@@ -153,9 +156,10 @@ public class NotificationChildrenContainer extends ViewGroup {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+ int childCount =
+ Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
for (int i = 0; i < childCount; i++) {
- View child = mChildren.get(i);
+ View child = mAttachedChildren.get(i);
// We need to layout all children even the GONE ones, such that the heights are
// calculated correctly as they are used to calculate how many we can fit on the screen
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
@@ -195,11 +199,12 @@ public class NotificationChildrenContainer extends ViewGroup {
}
int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
int height = mNotificationHeaderMargin + mNotificatonTopPadding;
- int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
+ int childCount =
+ Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
for (int i = 0; i < childCount; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
// We need to measure all children even the GONE ones, such that the heights are
// calculated correctly as they are used to calculate how many we can fit on the screen.
boolean isOverflow = i == overflowIndex;
@@ -242,14 +247,24 @@ public class NotificationChildrenContainer extends ViewGroup {
}
/**
+ * Set the untruncated number of children in the group so that the view can update the UI
+ * appropriately. Note that this may differ from the number of views attached as truncated
+ * children will not have views.
+ */
+ public void setUntruncatedChildCount(int childCount) {
+ mUntruncatedChildCount = childCount;
+ updateGroupOverflow();
+ }
+
+ /**
* Add a child notification to this view.
*
* @param row the row to add
* @param childIndex the index to add it at, if -1 it will be added at the end
*/
public void addNotification(ExpandableNotificationRow row, int childIndex) {
- int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
- mChildren.add(newIndex, row);
+ int newIndex = childIndex < 0 ? mAttachedChildren.size() : childIndex;
+ mAttachedChildren.add(newIndex, row);
addView(row);
row.setUserLocked(mUserLocked);
@@ -257,7 +272,6 @@ public class NotificationChildrenContainer extends ViewGroup {
addView(divider);
mDividers.add(newIndex, divider);
- updateGroupOverflow();
row.setContentTransformationAmount(0, false /* isLastChild */);
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
@@ -268,8 +282,8 @@ public class NotificationChildrenContainer extends ViewGroup {
}
public void removeNotification(ExpandableNotificationRow row) {
- int childIndex = mChildren.indexOf(row);
- mChildren.remove(row);
+ int childIndex = mAttachedChildren.indexOf(row);
+ mAttachedChildren.remove(row);
removeView(row);
final View divider = mDividers.remove(childIndex);
@@ -284,7 +298,6 @@ public class NotificationChildrenContainer extends ViewGroup {
row.setSystemChildExpanded(false);
row.setUserLocked(false);
- updateGroupOverflow();
if (!row.isRemoved()) {
mHeaderUtil.restoreNotificationHeader(row);
}
@@ -294,11 +307,12 @@ public class NotificationChildrenContainer extends ViewGroup {
* @return The number of notification children in the container.
*/
public int getNotificationChildCount() {
- return mChildren.size();
+ return mAttachedChildren.size();
}
- public void recreateNotificationHeader(OnClickListener listener) {
+ public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
mHeaderClickListener = listener;
+ mIsConversation = isConversation;
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
notification.getNotification());
@@ -317,7 +331,16 @@ public class NotificationChildrenContainer extends ViewGroup {
header.reapply(getContext(), mNotificationHeader);
}
mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
- recreateLowPriorityHeader(builder);
+ if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
+ NotificationHeaderViewWrapper headerWrapper =
+ (NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
+ if (isConversation) {
+ headerWrapper.applyConversationSkin();
+ } else {
+ headerWrapper.clearConversationSkin();
+ }
+ }
+ recreateLowPriorityHeader(builder, isConversation);
updateHeaderVisibility(false /* animate */);
updateChildrenHeaderAppearance();
}
@@ -327,7 +350,7 @@ public class NotificationChildrenContainer extends ViewGroup {
*
* @param builder a builder to reuse. Otherwise the builder will be recovered.
*/
- private void recreateLowPriorityHeader(Notification.Builder builder) {
+ private void recreateLowPriorityHeader(Notification.Builder builder, boolean isConversation) {
RemoteViews header;
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
if (mIsLowPriority) {
@@ -351,6 +374,15 @@ public class NotificationChildrenContainer extends ViewGroup {
header.reapply(getContext(), mNotificationHeaderLowPriority);
}
mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
+ if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
+ NotificationHeaderViewWrapper headerWrapper =
+ (NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
+ if (isConversation) {
+ headerWrapper.applyConversationSkin();
+ } else {
+ headerWrapper.clearConversationSkin();
+ }
+ }
resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
} else {
removeView(mNotificationHeaderLowPriority);
@@ -364,11 +396,10 @@ public class NotificationChildrenContainer extends ViewGroup {
}
public void updateGroupOverflow() {
- int childCount = mChildren.size();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
- if (childCount > maxAllowedVisibleChildren) {
- int number = childCount - maxAllowedVisibleChildren;
- mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number);
+ if (mUntruncatedChildCount > maxAllowedVisibleChildren) {
+ int number = mUntruncatedChildCount - maxAllowedVisibleChildren;
+ mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number, this);
if (mGroupOverFlowState == null) {
mGroupOverFlowState = new ViewState();
mNeverAppliedGroupState = true;
@@ -401,8 +432,11 @@ public class NotificationChildrenContainer extends ViewGroup {
R.layout.notification_children_divider, this, false);
}
- public List<ExpandableNotificationRow> getNotificationChildren() {
- return mChildren;
+ /**
+ * Get notification children that are attached currently.
+ */
+ public List<ExpandableNotificationRow> getAttachedChildren() {
+ return mAttachedChildren;
}
/**
@@ -420,13 +454,13 @@ public class NotificationChildrenContainer extends ViewGroup {
return false;
}
boolean result = false;
- for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ for (int i = 0; i < mAttachedChildren.size() && i < childOrder.size(); i++) {
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
ExpandableNotificationRow desiredChild = (ExpandableNotificationRow) childOrder.get(i);
if (child != desiredChild) {
if (visualStabilityManager.canReorderNotification(desiredChild)) {
- mChildren.remove(desiredChild);
- mChildren.add(i, desiredChild);
+ mAttachedChildren.remove(desiredChild);
+ mAttachedChildren.add(i, desiredChild);
result = true;
} else {
visualStabilityManager.addReorderingAllowedCallback(callback);
@@ -442,9 +476,9 @@ public class NotificationChildrenContainer extends ViewGroup {
// we don't modify it the group is expanded or if we are expanding it
return;
}
- int size = mChildren.size();
+ int size = mAttachedChildren.size();
for (int i = 0; i < size; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
child.setSystemChildExpanded(i == 0 && size == 1);
}
}
@@ -468,7 +502,7 @@ public class NotificationChildrenContainer extends ViewGroup {
}
int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
int visibleChildren = 0;
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
boolean firstChild = true;
float expandFactor = 0;
if (mUserLocked) {
@@ -499,7 +533,7 @@ public class NotificationChildrenContainer extends ViewGroup {
}
firstChild = false;
}
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
intrinsicHeight += child.getIntrinsicHeight();
visibleChildren++;
}
@@ -518,7 +552,7 @@ public class NotificationChildrenContainer extends ViewGroup {
* @param ambientState the ambient state containing ambient information
*/
public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation;
boolean firstChild = true;
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
@@ -535,7 +569,7 @@ public class NotificationChildrenContainer extends ViewGroup {
&& !mContainingNotification.isGroupExpansionChanging();
int launchTransitionCompensation = 0;
for (int i = 0; i < childCount; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
if (!firstChild) {
if (expandingToExpandedGroup) {
yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
@@ -586,7 +620,7 @@ public class NotificationChildrenContainer extends ViewGroup {
}
if (mOverflowNumber != null) {
- ExpandableNotificationRow overflowView = mChildren.get(Math.min(
+ ExpandableNotificationRow overflowView = mAttachedChildren.get(Math.min(
getMaxAllowedVisibleChildren(true /* likeCollapsed */), childCount) - 1);
mGroupOverFlowState.copyFrom(overflowView.getViewState());
@@ -672,7 +706,7 @@ public class NotificationChildrenContainer extends ViewGroup {
/** Applies state to children. */
public void applyState() {
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState();
float expandFraction = 0.0f;
if (mUserLocked) {
@@ -683,7 +717,7 @@ public class NotificationChildrenContainer extends ViewGroup {
|| (mContainingNotification.isGroupExpansionChanging()
&& !mHideDividersDuringExpand);
for (int i = 0; i < childCount; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
ExpandableViewState viewState = child.getViewState();
viewState.applyToView(child);
@@ -716,10 +750,10 @@ public class NotificationChildrenContainer extends ViewGroup {
if (mContainingNotification.hasExpandingChild()) {
return;
}
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount;
for (int i = 0; i < childCount; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == GONE) {
continue;
}
@@ -754,7 +788,7 @@ public class NotificationChildrenContainer extends ViewGroup {
/** Animate to a given state. */
public void startAnimationToState(AnimationProperties properties) {
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
ViewState tmpState = new ViewState();
float expandFraction = getGroupExpandFraction();
final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
@@ -762,7 +796,7 @@ public class NotificationChildrenContainer extends ViewGroup {
|| (mContainingNotification.isGroupExpansionChanging()
&& !mHideDividersDuringExpand);
for (int i = childCount - 1; i >= 0; i--) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
ExpandableViewState viewState = child.getViewState();
viewState.animateTo(child, properties);
@@ -799,9 +833,9 @@ public class NotificationChildrenContainer extends ViewGroup {
public ExpandableNotificationRow getViewAtPosition(float y) {
// find the view under the pointer, accounting for GONE views
- final int count = mChildren.size();
+ final int count = mAttachedChildren.size();
for (int childIdx = 0; childIdx < count; childIdx++) {
- ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
+ ExpandableNotificationRow slidingChild = mAttachedChildren.get(childIdx);
float childTop = slidingChild.getTranslationY();
float top = childTop + slidingChild.getClipTopAmount();
float bottom = childTop + slidingChild.getActualHeight();
@@ -818,9 +852,9 @@ public class NotificationChildrenContainer extends ViewGroup {
if (mNotificationHeader != null) {
mNotificationHeader.setExpanded(childrenExpanded);
}
- final int count = mChildren.size();
+ final int count = mAttachedChildren.size();
for (int childIdx = 0; childIdx < count; childIdx++) {
- ExpandableNotificationRow child = mChildren.get(childIdx);
+ ExpandableNotificationRow child = mAttachedChildren.get(childIdx);
child.setChildrenExpanded(childrenExpanded, false);
}
updateHeaderTouchability();
@@ -919,12 +953,12 @@ public class NotificationChildrenContainer extends ViewGroup {
private void startChildAlphaAnimations(boolean toVisible) {
float target = toVisible ? 1.0f : 0.0f;
float start = 1.0f - target;
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
for (int i = 0; i < childCount; i++) {
if (i >= NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED) {
break;
}
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
child.setAlpha(start);
ViewState viewState = new ViewState();
viewState.initFrom(child);
@@ -979,12 +1013,12 @@ public class NotificationChildrenContainer extends ViewGroup {
int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
+ mNotificatonTopPadding;
int visibleChildren = 0;
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
break;
}
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
float childHeight = child.isExpanded(true /* allowOnKeyguard */)
? child.getMaxExpandHeight()
: child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
@@ -1006,9 +1040,9 @@ public class NotificationChildrenContainer extends ViewGroup {
boolean showingLowPriority = showingAsLowPriority();
updateHeaderTransformation();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
for (int i = 0; i < childCount; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
float childHeight;
if (showingLowPriority) {
childHeight = child.getShowingLayout().getMinHeight(false /* likeGroupExpanded */);
@@ -1042,13 +1076,13 @@ public class NotificationChildrenContainer extends ViewGroup {
int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
+ mNotificatonTopPadding + mDividerHeight;
int visibleChildren = 0;
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
}
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
float childHeight = child.isExpanded(true /* allowOnKeyguard */)
? child.getMaxExpandHeight()
: child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
@@ -1097,7 +1131,7 @@ public class NotificationChildrenContainer extends ViewGroup {
int minExpandHeight = mNotificationHeaderMargin + headerTranslation;
int visibleChildren = 0;
boolean firstChild = true;
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
for (int i = 0; i < childCount; i++) {
if (visibleChildren >= maxAllowedVisibleChildren) {
break;
@@ -1107,7 +1141,7 @@ public class NotificationChildrenContainer extends ViewGroup {
} else {
firstChild = false;
}
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
minExpandHeight += child.getSingleLineView().getHeight();
visibleChildren++;
}
@@ -1128,7 +1162,7 @@ public class NotificationChildrenContainer extends ViewGroup {
removeView(mNotificationHeaderLowPriority);
mNotificationHeaderLowPriority = null;
}
- recreateNotificationHeader(listener);
+ recreateNotificationHeader(listener, mIsConversation);
initDimens();
for (int i = 0; i < mDividers.size(); i++) {
View prevDivider = mDividers.get(i);
@@ -1149,9 +1183,9 @@ public class NotificationChildrenContainer extends ViewGroup {
if (!mUserLocked) {
updateHeaderVisibility(false /* animate */);
}
- int childCount = mChildren.size();
+ int childCount = mAttachedChildren.size();
for (int i = 0; i < childCount; i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
child.setUserLocked(userLocked && !showingAsLowPriority());
}
updateHeaderTouchability();
@@ -1172,8 +1206,8 @@ public class NotificationChildrenContainer extends ViewGroup {
int position = mNotificationHeaderMargin + mCurrentHeaderTranslation
+ mNotificatonTopPadding;
- for (int i = 0; i < mChildren.size(); i++) {
- ExpandableNotificationRow child = mChildren.get(i);
+ for (int i = 0; i < mAttachedChildren.size(); i++) {
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
boolean notGone = child.getVisibility() != View.GONE;
if (notGone) {
position += mDividerHeight;
@@ -1212,7 +1246,7 @@ public class NotificationChildrenContainer extends ViewGroup {
public void setIsLowPriority(boolean isLowPriority) {
mIsLowPriority = isLowPriority;
if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
- recreateLowPriorityHeader(null /* existingBuilder */);
+ recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation);
updateHeaderVisibility(false /* animate */);
}
if (mUserLocked) {
@@ -1251,8 +1285,8 @@ public class NotificationChildrenContainer extends ViewGroup {
public void setCurrentBottomRoundness(float currentBottomRoundness) {
boolean last = true;
- for (int i = mChildren.size() - 1; i >= 0; i--) {
- ExpandableNotificationRow child = mChildren.get(i);
+ for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
+ ExpandableNotificationRow child = mAttachedChildren.get(i);
if (child.getVisibility() == View.GONE) {
continue;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index c4a720cb659f..09ab1d89473e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -23,6 +23,7 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.SimpleNotificationListContainer;
@@ -194,4 +195,6 @@ public interface NotificationListContainer extends ExpandableView.OnHeightChange
* @param v the item to remove
*/
void removeListItem(@NonNull NotificationListItem v);
+
+ void setNotificationActivityStarter(NotificationActivityStarter notificationActivityStarter);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListItem.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListItem.java
index 8991abe52ce1..c2dd2296aa17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListItem.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListItem.java
@@ -43,7 +43,7 @@ public interface NotificationListItem {
// This generic is kind of ugly - we should change this once the old VHM is gone
/** @return list of the children of this item */
- List<? extends NotificationListItem> getNotificationChildren();
+ List<? extends NotificationListItem> getAttachedChildren();
/** remove all children from this list item */
void removeAllChildren();
@@ -54,6 +54,9 @@ public interface NotificationListItem {
/** add an item as a child */
void addChildNotification(NotificationListItem child, int childIndex);
+ /** set the child count view should display */
+ void setUntruncatedChildCount(int count);
+
/** Update the order of the children with the new list */
boolean applyChildOrder(
List<? extends NotificationListItem> childOrderList,
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 6054b507185e..7093dd8b39e3 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
@@ -29,6 +29,7 @@ import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEX
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.app.TaskStackBuilder;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeAnimator;
@@ -50,6 +51,7 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.ServiceManager;
import android.provider.Settings;
@@ -117,6 +119,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
+import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -255,6 +258,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
private final AmbientState mAmbientState;
private NotificationGroupManager mGroupManager;
+ private NotificationActivityStarter mNotificationActivityStarter;
private HashSet<ExpandableView> mChildrenToAddAnimated = new HashSet<>();
private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
private ArrayList<ExpandableView> mChildrenToRemoveAnimated = new ArrayList<>();
@@ -628,8 +632,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mHighPriorityBeforeSpeedBump = "1".equals(newValue);
} else if (key.equals(Settings.Secure.NOTIFICATION_DISMISS_RTL)) {
updateDismissRtlSetting("1".equals(newValue));
+ } else if (key.equals(Settings.Secure.NOTIFICATION_HISTORY_ENABLED)) {
+ updateFooter();
}
- }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL);
+ }, HIGH_PRIORITY, Settings.Secure.NOTIFICATION_DISMISS_RTL,
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED);
mFeatureFlags = featureFlags;
mNotifPipeline = notifPipeline;
@@ -742,12 +749,17 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
@VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void updateFooter() {
+ if (mFooterView == null) {
+ return;
+ }
boolean showDismissView = mClearAllEnabled && hasActiveClearableNotifications(ROWS_ALL);
boolean showFooterView = (showDismissView || hasActiveNotifications())
&& mStatusBarState != StatusBarState.KEYGUARD
&& !mRemoteInputManager.getController().isRemoteInputActive();
+ boolean showHistory = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0) == 1;
- updateFooterView(showFooterView, showDismissView);
+ updateFooterView(showFooterView, showDismissView, showHistory);
}
/**
@@ -2377,7 +2389,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
List<ExpandableNotificationRow> notificationChildren =
- row.getNotificationChildren();
+ row.getAttachedChildren();
for (int childIndex = 0; childIndex < notificationChildren.size();
childIndex++) {
ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
@@ -4638,7 +4650,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
row.setHeadsUpAnimatingAway(false);
if (row.isSummaryWithChildren()) {
- for (ExpandableNotificationRow child : row.getNotificationChildren()) {
+ for (ExpandableNotificationRow child : row.getAttachedChildren()) {
child.setHeadsUpAnimatingAway(false);
}
}
@@ -4979,13 +4991,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- public void updateFooterView(boolean visible, boolean showDismissView) {
+ public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
if (mFooterView == null) {
return;
}
boolean animate = mIsExpanded && mAnimationsEnabled;
mFooterView.setVisible(visible, animate);
mFooterView.setSecondaryVisible(showDismissView, animate);
+ mFooterView.showHistory(showHistory);
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -5566,12 +5579,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- public void manageNotifications(View v) {
- Intent intent = new Intent(Settings.ACTION_NOTIFICATION_HISTORY);
- mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
- }
-
- @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@VisibleForTesting
void clearNotifications(
@SelectedRows int selection,
@@ -5598,7 +5605,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
&& (!hasClipBounds || mTmpRect.height() > 0)) {
parentVisible = true;
}
- List<ExpandableNotificationRow> children = row.getNotificationChildren();
+ List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (children != null) {
for (ExpandableNotificationRow childRow : children) {
if (includeChildInDismissAll(row, selection)) {
@@ -5696,6 +5703,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
}
}
+ @Override
+ public void setNotificationActivityStarter(
+ NotificationActivityStarter notificationActivityStarter) {
+ mNotificationActivityStarter = notificationActivityStarter;
+ }
+
@VisibleForTesting
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
protected void inflateFooterView() {
@@ -5705,7 +5718,9 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
clearNotifications(ROWS_ALL, true /* closeShade */);
});
- footerView.setManageButtonClickListener(this::manageNotifications);
+ footerView.setManageButtonClickListener(v -> {
+ mNotificationActivityStarter.startHistoryIntent(mFooterView.isHistoryShown());
+ });
setFooterView(footerView);
}
@@ -5714,6 +5729,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
EmptyShadeView view = (EmptyShadeView) LayoutInflater.from(mContext).inflate(
R.layout.status_bar_no_notifications, this, false);
view.setText(R.string.empty_shade_text);
+ view.setOnClickListener(v -> {
+ final boolean showHistory = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0) == 1;
+ Intent intent = showHistory ? new Intent(
+ Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
+ Settings.ACTION_NOTIFICATION_SETTINGS);
+ mStatusBar.startActivity(intent, true, true, Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ });
setEmptyShadeView(view);
}
@@ -6388,7 +6411,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd
if (parent != null && parent.areChildrenExpanded()
&& (parent.areGutsExposed()
|| mSwipeHelper.getExposedMenuView() == parent
- || (parent.getNotificationChildren().size() == 1
+ || (parent.getAttachedChildren().size() == 1
&& parent.getEntry().isClearable()))) {
// In this case the group is expanded and showing the menu for the
// group, further interaction should apply to the group, not any
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 9646c01c8c41..1a15377566e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -297,7 +297,7 @@ public class StackScrollAlgorithm {
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
// handle the notgoneIndex for the children as well
- List<ExpandableNotificationRow> children = row.getNotificationChildren();
+ List<ExpandableNotificationRow> children = row.getAttachedChildren();
if (row.isSummaryWithChildren() && children != null) {
for (ExpandableNotificationRow childRow : children) {
if (childRow.getVisibility() != View.GONE) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8c9bb6c75828..303a0831b52f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -29,6 +29,9 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+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.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardConstants;
@@ -50,6 +53,8 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.Optional;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -64,6 +69,7 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
private static final boolean DEBUG_BIO_WAKELOCK = KeyguardConstants.DEBUG_BIOMETRIC_WAKELOCK;
private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
+ private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@IntDef(prefix = { "MODE_" }, value = {
MODE_NONE,
@@ -171,6 +177,68 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
}
}
+ @VisibleForTesting
+ public enum BiometricUiEvent implements UiEventLogger.UiEventEnum {
+
+ @UiEvent(doc = "A biometric event of type fingerprint succeeded.")
+ BIOMETRIC_FINGERPRINT_SUCCESS(396),
+
+ @UiEvent(doc = "A biometric event of type fingerprint failed.")
+ BIOMETRIC_FINGERPRINT_FAILURE(397),
+
+ @UiEvent(doc = "A biometric event of type fingerprint errored.")
+ BIOMETRIC_FINGERPRINT_ERROR(398),
+
+ @UiEvent(doc = "A biometric event of type face unlock succeeded.")
+ BIOMETRIC_FACE_SUCCESS(399),
+
+ @UiEvent(doc = "A biometric event of type face unlock failed.")
+ BIOMETRIC_FACE_FAILURE(400),
+
+ @UiEvent(doc = "A biometric event of type face unlock errored.")
+ BIOMETRIC_FACE_ERROR(401),
+
+ @UiEvent(doc = "A biometric event of type iris succeeded.")
+ BIOMETRIC_IRIS_SUCCESS(402),
+
+ @UiEvent(doc = "A biometric event of type iris failed.")
+ BIOMETRIC_IRIS_FAILURE(403),
+
+ @UiEvent(doc = "A biometric event of type iris errored.")
+ BIOMETRIC_IRIS_ERROR(404);
+
+ private final int mId;
+
+ BiometricUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+
+ static final Map<BiometricSourceType, BiometricUiEvent> ERROR_EVENT_BY_SOURCE_TYPE = Map.of(
+ BiometricSourceType.FINGERPRINT, BiometricUiEvent.BIOMETRIC_FINGERPRINT_ERROR,
+ BiometricSourceType.FACE, BiometricUiEvent.BIOMETRIC_FACE_ERROR,
+ BiometricSourceType.IRIS, BiometricUiEvent.BIOMETRIC_IRIS_ERROR
+ );
+
+ static final Map<BiometricSourceType, BiometricUiEvent> SUCCESS_EVENT_BY_SOURCE_TYPE =
+ Map.of(
+ BiometricSourceType.FINGERPRINT, BiometricUiEvent.BIOMETRIC_FINGERPRINT_SUCCESS,
+ BiometricSourceType.FACE, BiometricUiEvent.BIOMETRIC_FACE_SUCCESS,
+ BiometricSourceType.IRIS, BiometricUiEvent.BIOMETRIC_IRIS_SUCCESS
+ );
+
+ static final Map<BiometricSourceType, BiometricUiEvent> FAILURE_EVENT_BY_SOURCE_TYPE =
+ Map.of(
+ BiometricSourceType.FINGERPRINT, BiometricUiEvent.BIOMETRIC_FINGERPRINT_FAILURE,
+ BiometricSourceType.FACE, BiometricUiEvent.BIOMETRIC_FACE_FAILURE,
+ BiometricSourceType.IRIS, BiometricUiEvent.BIOMETRIC_IRIS_FAILURE
+ );
+ }
+
@Inject
public BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
@@ -274,6 +342,9 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
}
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_SUCCESS).setSubtype(toSubtype(biometricSourceType)));
+ Optional.ofNullable(BiometricUiEvent.SUCCESS_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
+ .ifPresent(UI_EVENT_LOGGER::log);
+
boolean unlockAllowed = mKeyguardBypassController.onBiometricAuthenticated(
biometricSourceType, isStrongBiometric);
if (unlockAllowed) {
@@ -504,6 +575,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_FAILURE).setSubtype(toSubtype(biometricSourceType)));
+ Optional.ofNullable(BiometricUiEvent.FAILURE_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
+ .ifPresent(UI_EVENT_LOGGER::log);
cleanup();
}
@@ -513,6 +586,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mMetricsLogger.write(new LogMaker(MetricsEvent.BIOMETRIC_AUTH)
.setType(MetricsEvent.TYPE_ERROR).setSubtype(toSubtype(biometricSourceType))
.addTaggedData(MetricsEvent.FIELD_BIOMETRIC_AUTH_ERROR, msgId));
+ Optional.ofNullable(BiometricUiEvent.ERROR_EVENT_BY_SOURCE_TYPE.get(biometricSourceType))
+ .ifPresent(UI_EVENT_LOGGER::log);
cleanup();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index abae4d8eb96e..9bf14e43da03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -77,7 +77,6 @@ public final class DozeServiceHost implements DozeHost {
private final BatteryController mBatteryController;
private final ScrimController mScrimController;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
- private BiometricUnlockController mBiometricUnlockController;
private final KeyguardViewMediator mKeyguardViewMediator;
private final Lazy<AssistManager> mAssistManagerLazy;
private final DozeScrimController mDozeScrimController;
@@ -148,9 +147,9 @@ public final class DozeServiceHost implements DozeHost {
mNotificationPanel = notificationPanel;
mNotificationShadeWindowViewController = notificationShadeWindowViewController;
mAmbientIndicationContainer = ambientIndicationContainer;
- mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
}
+
@Override
public String toString() {
return "PSB.DozeServiceHost[mCallbacks=" + mCallbacks.size() + "]";
@@ -206,11 +205,11 @@ public final class DozeServiceHost implements DozeHost {
boolean
dozing =
mDozingRequested && mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- || mBiometricUnlockController.getMode()
+ || mBiometricUnlockControllerLazy.get().getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
// When in wake-and-unlock we may not have received a change to StatusBarState
// but we still should not be dozing, manually set to false.
- if (mBiometricUnlockController.getMode()
+ if (mBiometricUnlockControllerLazy.get().getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK) {
dozing = false;
}
@@ -311,7 +310,7 @@ public final class DozeServiceHost implements DozeHost {
@Override
public boolean isPulsingBlocked() {
- return mBiometricUnlockController.getMode()
+ return mBiometricUnlockControllerLazy.get().getMode()
== BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
}
@@ -323,7 +322,7 @@ public final class DozeServiceHost implements DozeHost {
@Override
public boolean isBlockingDoze() {
- if (mBiometricUnlockController.hasPendingAuthentication()) {
+ if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) {
Log.i(StatusBar.TAG, "Blocking AOD because fingerprint has authenticated");
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index f103bd01fc3f..ba8a63428cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -53,11 +53,13 @@ import android.view.WindowManagerGlobal;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.NavigationEdgeBackPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.QuickStepContract;
@@ -74,7 +76,7 @@ import java.util.concurrent.Executor;
/**
* Utility class to handle edge swipes for back gesture
*/
-public class EdgeBackGestureHandler implements DisplayListener,
+public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener,
PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
private static final String TAG = "EdgeBackGestureHandler";
@@ -165,6 +167,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
private boolean mIsGesturalModeEnabled;
private boolean mIsEnabled;
private boolean mIsNavBarShownTransiently;
+ private boolean mIsBackGestureAllowed;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -200,7 +203,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
SysUiState sysUiFlagContainer, PluginManager pluginManager) {
- final Resources res = context.getResources();
+ super(Dependency.get(BroadcastDispatcher.class));
mContext = context;
mDisplayId = context.getDisplayId();
mMainExecutor = context.getMainExecutor();
@@ -216,20 +219,30 @@ public class EdgeBackGestureHandler implements DisplayListener,
ViewConfiguration.getLongPressTimeout());
mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
- mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res));
+ mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources);
- updateCurrentUserResources(res);
+ updateCurrentUserResources();
sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
}
- public void updateCurrentUserResources(Resources res) {
+ public void updateCurrentUserResources() {
+ Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext()
+ .getResources();
mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+ mIsBackGestureAllowed =
+ !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
mBottomGestureHeight = res.getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_height);
}
+ @Override
+ public void onUserSwitched(int newUserId) {
+ updateIsEnabled();
+ updateCurrentUserResources();
+ }
+
/**
* @see NavigationBarView#onAttachedToWindow()
*/
@@ -243,6 +256,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME),
false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL);
updateIsEnabled();
+ startTracking();
}
/**
@@ -255,6 +269,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
}
mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver);
updateIsEnabled();
+ stopTracking();
}
private void setRotationCallbacks(boolean enable) {
@@ -269,10 +284,13 @@ public class EdgeBackGestureHandler implements DisplayListener,
}
}
- public void onNavigationModeChanged(int mode, Context currentUserContext) {
+ /**
+ * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
+ */
+ public void onNavigationModeChanged(int mode) {
mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
updateIsEnabled();
- updateCurrentUserResources(currentUserContext.getResources());
+ updateCurrentUserResources();
}
public void onNavBarTransientStateChanged(boolean isTransient) {
@@ -312,7 +330,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
WindowManagerGlobal.getWindowManagerService()
.unregisterSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
- } catch (RemoteException e) {
+ } catch (RemoteException | IllegalArgumentException e) {
Log.e(TAG, "Failed to unregister window manager callbacks", e);
}
@@ -326,7 +344,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
WindowManagerGlobal.getWindowManagerService()
.registerSystemGestureExclusionListener(
mGestureExclusionListener, mDisplayId);
- } catch (RemoteException e) {
+ } catch (RemoteException | IllegalArgumentException e) {
Log.e(TAG, "Failed to register window manager callbacks", e);
}
@@ -363,6 +381,10 @@ public class EdgeBackGestureHandler implements DisplayListener,
updateDisplaySize();
}
+ public boolean isHandlingGestures() {
+ return mIsEnabled && mIsBackGestureAllowed;
+ }
+
private WindowManager.LayoutParams createLayoutParams() {
Resources resources = mContext.getResources();
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
@@ -469,9 +491,9 @@ public class EdgeBackGestureHandler implements DisplayListener,
mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
mLogGesture = false;
mInRejectedExclusion = false;
- mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
- && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
- && !mDisabledForQuickstep;
+ mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
+ && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
+ && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
mEdgeBackPlugin.onMotionEvent(ev);
@@ -534,7 +556,8 @@ public class EdgeBackGestureHandler implements DisplayListener,
private void updateDisabledForQuickstep() {
int rotation = mContext.getResources().getConfiguration().windowConfiguration.getRotation();
- mDisabledForQuickstep = mStartingQuickstepRotation != rotation;
+ mDisabledForQuickstep = mStartingQuickstepRotation > -1 &&
+ mStartingQuickstepRotation != rotation;
}
@Override
@@ -599,6 +622,7 @@ public class EdgeBackGestureHandler implements DisplayListener,
public void dump(PrintWriter pw) {
pw.println("EdgeBackGestureHandler:");
pw.println(" mIsEnabled=" + mIsEnabled);
+ pw.println(" mIsBackGestureAllowed=" + mIsBackGestureAllowed);
pw.println(" mAllowGesture=" + mAllowGesture);
pw.println(" mDisabledForQuickstep=" + mDisabledForQuickstep);
pw.println(" mInRejectedExclusion" + mInRejectedExclusion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 90bc075b399d..ae7867d68af4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -85,6 +85,8 @@ import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
import com.android.systemui.tuner.TunerService;
+import java.util.concurrent.Executor;
+
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
@@ -553,7 +555,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
if (!mKeyguardStateController.canDismissLockScreen()) {
- AsyncTask.execute(runnable);
+ Dependency.get(Executor.class).execute(runnable);
} else {
boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
&& Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 82e02b47974c..39949c82661f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -400,6 +400,9 @@ public class KeyguardBouncer {
mExpansionCallback.onFullyHidden();
} else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) {
mExpansionCallback.onStartingToHide();
+ if (mKeyguardView != null) {
+ mKeyguardView.onStartingToHide();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d35e1e1e176a..3e5eb5fba8f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -33,6 +33,7 @@ import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.policy.BatteryController;
import java.io.FileDescriptor;
@@ -58,6 +59,7 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
private int mStatusBarMode;
private int mNavigationBarMode;
+ private int mNavigationMode;
private final Color mDarkModeColor;
/**
@@ -84,11 +86,14 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
@Inject
public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher,
- BatteryController batteryController) {
+ BatteryController batteryController, NavigationModeController navModeController) {
mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
mBatteryController.addCallback(this);
+ mNavigationMode = navModeController.addListener((mode) -> {
+ mNavigationMode = mode;
+ });
}
public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -234,7 +239,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
}
private void updateNavigation() {
- if (mNavigationBarController != null) {
+ if (mNavigationBarController != null
+ && !QuickStepContract.isGesturalMode(mNavigationMode)) {
mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index a19d35ac4e81..ec54b302b055 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -65,7 +65,6 @@ public class LockIcon extends KeyguardAffordanceView {
mPredrawRegistered = false;
int newState = mState;
- mOldState = mState;
Drawable icon = getIcon(newState);
setImageDrawable(icon, false);
@@ -135,6 +134,7 @@ public class LockIcon extends KeyguardAffordanceView {
}
void update(int newState, boolean pulsing, boolean dozing, boolean keyguardJustShown) {
+ mOldState = mState;
mState = newState;
mPulsing = pulsing;
mDozing = dozing;
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 a633e1979bad..a2e7306d4931 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenLockIconController.java
@@ -470,8 +470,10 @@ public class LockscreenLockIconController {
}
private int getState() {
- if ((mKeyguardStateController.canDismissLockScreen() || !mKeyguardShowing
- || mKeyguardStateController.isKeyguardGoingAway()) && !mSimLocked) {
+ if ((mKeyguardStateController.canDismissLockScreen()
+ || !mKeyguardStateController.isShowing()
+ || mKeyguardStateController.isKeyguardGoingAway()
+ || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
return STATE_LOCK_OPEN;
} else if (mTransientBiometricsError) {
return STATE_BIOMETRICS_ERROR;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 3105155f28e8..b2aa769f1bff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -28,6 +28,7 @@ import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_B
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -91,11 +92,13 @@ import android.view.accessibility.AccessibilityManager.AccessibilityServicesStat
import androidx.annotation.VisibleForTesting;
+import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.R;
+import com.android.systemui.accessibility.SystemActions;
import com.android.systemui.assist.AssistHandleViewController;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -183,6 +186,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
private WindowManager mWindowManager;
private final CommandQueue mCommandQueue;
private long mLastLockToAppLongPress;
+ private final SystemActions mSystemActions;
private Locale mLocale;
private int mLayoutDirection;
@@ -371,6 +375,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
Optional<Recents> recentsOptional, Lazy<StatusBar> statusBarLazy,
ShadeController shadeController,
NotificationRemoteInputManager notificationRemoteInputManager,
+ SystemActions systemActions,
@Main Handler mainHandler) {
mAccessibilityManagerWrapper = accessibilityManagerWrapper;
mDeviceProvisionedController = deviceProvisionedController;
@@ -389,6 +394,7 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
mCommandQueue = commandQueue;
mDivider = divider;
mRecentsOptional = recentsOptional;
+ mSystemActions = systemActions;
mHandler = mainHandler;
}
@@ -1136,10 +1142,10 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
}
private boolean onAccessibilityLongClick(View v) {
- Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
+ final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
- AccessibilityManager.ACCESSIBILITY_BUTTON);
+ final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
+ intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
v.getContext().startActivityAsUser(intent, UserHandle.CURRENT);
return true;
}
@@ -1166,6 +1172,16 @@ public class NavigationBarFragment extends LifecycleFragment implements Callback
.setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
.setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible())
.commitUpdate(mDisplayId);
+ registerAction(clickable, SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON);
+ registerAction(longClickable, SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER);
+ }
+
+ private void registerAction(boolean register, int actionId) {
+ if (register) {
+ mSystemActions.register(actionId);
+ } else {
+ mSystemActions.unregister(actionId);
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 84aecd4e0759..2978772cac5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -123,7 +123,7 @@ public class NavigationBarView extends FrameLayout implements
private KeyButtonDrawable mRecentIcon;
private KeyButtonDrawable mDockedIcon;
- private final EdgeBackGestureHandler mEdgeBackGestureHandler;
+ private EdgeBackGestureHandler mEdgeBackGestureHandler;
private final DeadZone mDeadZone;
private boolean mDeadZoneConsuming = false;
private final NavigationBarTransitions mBarTransitions;
@@ -244,7 +244,7 @@ public class NavigationBarView extends FrameLayout implements
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
// When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
// gestural mode, the entire nav bar should be touchable.
- if (!isGesturalMode(mNavBarMode) || mImeVisible) {
+ if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) {
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
return;
}
@@ -296,8 +296,6 @@ public class NavigationBarView extends FrameLayout implements
R.style.RotateButtonCCWStart90,
isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
- final ContextualButton backButton = new ContextualButton(R.id.back, 0);
-
mConfiguration = new Configuration();
mTmpLastConfiguration = new Configuration();
mConfiguration.updateFrom(context.getResources().getConfiguration());
@@ -305,7 +303,7 @@ public class NavigationBarView extends FrameLayout implements
mScreenPinningNotify = new ScreenPinningNotify(mContext);
mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
- mButtonDispatchers.put(R.id.back, backButton);
+ mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
@@ -659,7 +657,7 @@ public class NavigationBarView extends FrameLayout implements
boolean disableHomeHandle = disableRecent
&& ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
- boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
+ boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
|| ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
// When screen pinning, don't hide back and home when connected service or back and
@@ -686,9 +684,9 @@ public class NavigationBarView extends FrameLayout implements
}
}
- getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
- getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
- getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
+ getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
+ getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
+ getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
}
@@ -838,10 +836,9 @@ public class NavigationBarView extends FrameLayout implements
@Override
public void onNavigationModeChanged(int mode) {
- Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext();
mNavBarMode = mode;
mBarTransitions.onNavigationModeChanged(mNavBarMode);
- mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
+ mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
@@ -864,6 +861,7 @@ public class NavigationBarView extends FrameLayout implements
@Override
public void onFinishInflate() {
+ super.onFinishInflate();
mNavigationInflaterView = findViewById(R.id.navigation_inflater);
mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index d24ccf343a3a..6061b1e73d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -17,9 +17,6 @@
package com.android.systemui.statusbar.phone;
import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED;
-import static android.os.UserHandle.USER_CURRENT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
@@ -38,17 +35,14 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.util.Log;
-import android.util.SparseBooleanArray;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -70,104 +64,34 @@ public class NavigationModeController implements Dumpable {
private final Context mContext;
private Context mCurrentUserContext;
private final IOverlayManager mOverlayManager;
- private final DeviceProvisionedController mDeviceProvisionedController;
private final Executor mUiBgExecutor;
- private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
-
private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case ACTION_OVERLAY_CHANGED:
if (DEBUG) {
Log.d(TAG, "ACTION_OVERLAY_CHANGED");
}
updateCurrentInteractionMode(true /* notify */);
- break;
- }
}
};
- private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- if (DEBUG) {
- Log.d(TAG, "onDeviceProvisionedChanged: "
- + mDeviceProvisionedController.isDeviceProvisioned());
- }
- // Once the device has been provisioned, check if we can restore gestural nav
- restoreGesturalNavOverlayIfNecessary();
- }
-
- @Override
- public void onUserSetupChanged() {
- if (DEBUG) {
- Log.d(TAG, "onUserSetupChanged: "
- + mDeviceProvisionedController.isCurrentUserSetup());
- }
- // Once the user has been setup, check if we can restore gestural nav
- restoreGesturalNavOverlayIfNecessary();
- }
-
- @Override
- public void onUserSwitched() {
- if (DEBUG) {
- Log.d(TAG, "onUserSwitched: "
- + ActivityManagerWrapper.getInstance().getCurrentUserId());
- }
-
- // Update the nav mode for the current user
- updateCurrentInteractionMode(true /* notify */);
-
- // When switching users, defer enabling the gestural nav overlay until the user
- // is all set up
- deferGesturalNavOverlayIfNecessary();
- }
- };
-
@Inject
- public NavigationModeController(Context context,
- DeviceProvisionedController deviceProvisionedController,
- @UiBackground Executor uiBgExecutor) {
+ public NavigationModeController(Context context, @UiBackground Executor uiBgExecutor) {
mContext = context;
mCurrentUserContext = context;
mOverlayManager = IOverlayManager.Stub.asInterface(
ServiceManager.getService(Context.OVERLAY_SERVICE));
mUiBgExecutor = uiBgExecutor;
- mDeviceProvisionedController = deviceProvisionedController;
- mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
overlayFilter.addDataScheme("package");
overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
- IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null,
- null);
-
updateCurrentInteractionMode(false /* notify */);
-
- // Check if we need to defer enabling gestural nav
- deferGesturalNavOverlayIfNecessary();
- }
-
- private boolean setGestureModeOverlayForMainLauncher() {
- if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) {
- // Already in gesture mode
- return true;
- }
-
- Log.d(TAG, "Switching system navigation to full-gesture mode:"
- + " contextUser="
- + mCurrentUserContext.getUserId());
-
- setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
- return true;
}
public void updateCurrentInteractionMode(boolean notify) {
@@ -176,10 +100,9 @@ public class NavigationModeController implements Dumpable {
if (mode == NAV_BAR_MODE_GESTURAL) {
switchToDefaultGestureNavOverlayIfNecessary();
}
- mUiBgExecutor.execute(() -> {
+ mUiBgExecutor.execute(() ->
Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
- Secure.NAVIGATION_MODE, String.valueOf(mode));
- });
+ Secure.NAVIGATION_MODE, String.valueOf(mode)));
if (DEBUG) {
Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode);
dumpAssetPaths(mCurrentUserContext);
@@ -230,61 +153,11 @@ public class NavigationModeController implements Dumpable {
}
}
- private void deferGesturalNavOverlayIfNecessary() {
- final int userId = mDeviceProvisionedController.getCurrentUser();
- mRestoreGesturalNavBarMode.put(userId, false);
- if (mDeviceProvisionedController.isDeviceProvisioned()
- && mDeviceProvisionedController.isCurrentUserSetup()) {
- // User is already setup and device is provisioned, nothing to do
- if (DEBUG) {
- Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
- + "setup");
- }
- return;
- }
-
- ArrayList<String> defaultOverlays = new ArrayList<>();
- try {
- defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
- } catch (RemoteException e) {
- Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
- }
- if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
- // No default gesture nav overlay
- if (DEBUG) {
- Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
- + "default=" + defaultOverlays);
- }
- return;
- }
-
- // If the default is gestural, force-enable three button mode until the device is
- // provisioned
- setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
- mRestoreGesturalNavBarMode.put(userId, true);
-
- if (DEBUG) {
- Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
- }
- }
-
- private void restoreGesturalNavOverlayIfNecessary() {
- if (DEBUG) {
- Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
- + mRestoreGesturalNavBarMode);
- }
- final int userId = mDeviceProvisionedController.getCurrentUser();
- if (mRestoreGesturalNavBarMode.get(userId)) {
- // Restore the gestural state if necessary
- setGestureModeOverlayForMainLauncher();
- mRestoreGesturalNavBarMode.put(userId, false);
- }
- }
-
private void switchToDefaultGestureNavOverlayIfNecessary() {
final int userId = mCurrentUserContext.getUserId();
try {
- final IOverlayManager om = mOverlayManager;
+ final IOverlayManager om = IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE));
final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
if (info != null && !info.isEnabled()) {
// Enable the default gesture nav overlay, and move the back gesture inset scale to
@@ -309,20 +182,6 @@ public class NavigationModeController implements Dumpable {
}
}
- public void setModeOverlay(String overlayPkg, int userId) {
- mUiBgExecutor.execute(() -> {
- try {
- mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
- if (DEBUG) {
- Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg
- + " userId=" + userId);
- }
- } catch (SecurityException | IllegalStateException | RemoteException e) {
- Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
- }
- });
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("NavigationModeController:");
@@ -334,11 +193,6 @@ public class NavigationModeController implements Dumpable {
defaultOverlays = "failed_to_fetch";
}
pw.println(" defaultOverlays=" + defaultOverlays);
- pw.println(" restoreGesturalNavMode:");
- for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) {
- pw.println(" userId=" + mRestoreGesturalNavBarMode.keyAt(i)
- + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i));
- }
dumpAssetPaths(mCurrentUserContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 31797d1faa61..c9716d39590e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -1780,7 +1780,13 @@ public class NotificationPanelViewController extends PanelViewController {
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
+ public void onAnimationStart(Animator animation) {
+ notifyExpandingStarted();
+ }
+
+ @Override
public void onAnimationEnd(Animator animation) {
+ notifyExpandingFinished();
mNotificationStackScroller.resetCheckSnoozeLeavebehind();
mQsExpansionAnimator = null;
if (onFinishRunnable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index f7d403f667cb..81dc9e1cf0e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -157,7 +157,7 @@ public abstract class PanelViewController {
protected void onExpandingStarted() {
}
- private void notifyExpandingStarted() {
+ protected void notifyExpandingStarted() {
if (!mExpanding) {
mExpanding = true;
onExpandingStarted();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index 1a6b415f87db..bf52a7ae2bf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -148,11 +148,6 @@ public class RegionSamplingHelper implements View.OnAttachStateChangeListener,
updateSamplingRect();
}
- private void postUpdateSamplingListener() {
- mHandler.removeCallbacks(mUpdateSamplingListener);
- mHandler.post(mUpdateSamplingListener);
- }
-
private void updateSamplingListener() {
boolean isSamplingEnabled = mSamplingEnabled
&& !mSamplingRequestBounds.isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 900b3ea97010..ac5557b571d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -116,6 +116,9 @@ import android.widget.DateTimeView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.MetricsLogger;
+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.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.RegisterStatusBarResult;
@@ -140,6 +143,7 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.fragments.ExtensionFragmentListener;
import com.android.systemui.fragments.FragmentHostManager;
@@ -193,7 +197,6 @@ import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -301,6 +304,8 @@ public class StatusBar extends SystemUI implements DemoMode,
/** If true, the lockscreen will show a distinct wallpaper */
public static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
+ private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
+
static {
boolean onlyCoreApps;
try {
@@ -459,6 +464,44 @@ public class StatusBar extends SystemUI implements DemoMode,
}
};
+ @VisibleForTesting
+ public enum StatusBarUiEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "Secured lockscreen is opened.")
+ LOCKSCREEN_OPEN_SECURE(405),
+
+ @UiEvent(doc = "Lockscreen without security is opened.")
+ LOCKSCREEN_OPEN_INSECURE(406),
+
+ @UiEvent(doc = "Secured lockscreen is closed.")
+ LOCKSCREEN_CLOSE_SECURE(407),
+
+ @UiEvent(doc = "Lockscreen without security is closed.")
+ LOCKSCREEN_CLOSE_INSECURE(408),
+
+ @UiEvent(doc = "Secured bouncer is opened.")
+ BOUNCER_OPEN_SECURE(409),
+
+ @UiEvent(doc = "Bouncer without security is opened.")
+ BOUNCER_OPEN_INSECURE(410),
+
+ @UiEvent(doc = "Secured bouncer is closed.")
+ BOUNCER_CLOSE_SECURE(411),
+
+ @UiEvent(doc = "Bouncer without security is closed.")
+ BOUNCER_CLOSE_INSECURE(412);
+
+ private final int mId;
+
+ StatusBarUiEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
protected final H mHandler = createHandler();
private int mInteractingWindows;
@@ -468,6 +511,7 @@ public class StatusBar extends SystemUI implements DemoMode,
private final ScrimController mScrimController;
protected DozeScrimController mDozeScrimController;
private final Executor mUiBgExecutor;
+ private final Executor mMainExecutor;
protected boolean mDozing;
@@ -623,10 +667,10 @@ public class StatusBar extends SystemUI implements DemoMode,
NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationViewHierarchyManager notificationViewHierarchyManager,
KeyguardViewMediator keyguardViewMediator,
- NotificationAlertingManager notificationAlertingManager, // need to inject for now
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
@UiBackground Executor uiBgExecutor,
+ @Main Executor mainExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
@@ -707,6 +751,7 @@ public class StatusBar extends SystemUI implements DemoMode,
mDisplayMetrics = displayMetrics;
mMetricsLogger = metricsLogger;
mUiBgExecutor = uiBgExecutor;
+ mMainExecutor = mainExecutor;
mMediaManager = notificationMediaManager;
mLockscreenUserManager = lockScreenUserManager;
mRemoteInputManager = remoteInputManager;
@@ -1234,7 +1279,8 @@ public class StatusBar extends SystemUI implements DemoMode,
mActivityLaunchAnimator = new ActivityLaunchAnimator(
mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
mNotificationShadeDepthControllerLazy.get(),
- (NotificationListContainer) mStackScroller);
+ (NotificationListContainer) mStackScroller,
+ mMainExecutor);
// TODO: inject this.
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
@@ -1255,6 +1301,9 @@ public class StatusBar extends SystemUI implements DemoMode,
.setNotificationPanelViewController(mNotificationPanelViewController)
.build();
+ ((NotificationListContainer) mStackScroller)
+ .setNotificationActivityStarter(mNotificationActivityStarter);
+
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
mNotificationsController.initialize(
@@ -2906,6 +2955,12 @@ public class StatusBar extends SystemUI implements DemoMode,
isSecure ? 1 : 0,
unlocked ? 1 : 0);
mLastLoggedStateFingerprint = stateFingerprint;
+
+ StringBuilder uiEventValueBuilder = new StringBuilder();
+ uiEventValueBuilder.append(isBouncerShowing ? "BOUNCER" : "LOCKSCREEN");
+ uiEventValueBuilder.append(isShowing ? "_OPEN" : "_CLOSE");
+ uiEventValueBuilder.append(isSecure ? "_SECURE" : "_INSECURE");
+ sUiEventLogger.log(StatusBarUiEvent.valueOf(uiEventValueBuilder.toString()));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ed25db40fea6..bc94cdeba37f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -152,6 +152,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected boolean mShowing;
protected boolean mOccluded;
protected boolean mRemoteInputActive;
+ private boolean mGlobalActionsVisible = false;
+ private boolean mLastGlobalActionsVisible = false;
private boolean mDozing;
private boolean mPulsing;
private boolean mGesturalNav;
@@ -293,6 +295,14 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
updateLockIcon();
}
+ /**
+ * Update the global actions visibility state in order to show the navBar when active.
+ */
+ public void setGlobalActionsVisible(boolean isVisible) {
+ mGlobalActionsVisible = isVisible;
+ updateStates();
+ }
+
private void updateLockIcon() {
// Not all form factors have a lock icon
if (mLockIconContainer == null) {
@@ -820,6 +830,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mFirstUpdate = false;
mLastShowing = showing;
+ mLastGlobalActionsVisible = mGlobalActionsVisible;
mLastOccluded = occluded;
mLastBouncerShowing = bouncerShowing;
mLastBouncerDismissible = bouncerDismissible;
@@ -864,7 +875,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean keyguardWithGestureNav = (keyguardShowing && !mDozing || mPulsing && !mIsDocked)
&& mGesturalNav;
return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
- || mRemoteInputActive || keyguardWithGestureNav);
+ || mRemoteInputActive || keyguardWithGestureNav
+ || mGlobalActionsVisible);
}
/**
@@ -876,7 +888,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
boolean keyguardWithGestureNav = (keyguardShowing && !mLastDozing
|| mLastPulsing && !mLastIsDocked) && mLastGesturalNav;
return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
- || mLastRemoteInputActive || keyguardWithGestureNav);
+ || mLastRemoteInputActive || keyguardWithGestureNav
+ || mLastGlobalActionsVisible);
}
public boolean shouldDismissOnMenuPressed() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 53fa2630a9c3..d40b5f9728dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -35,12 +35,12 @@ import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
-import android.util.Log;
import android.view.RemoteAnimationAdapter;
import android.view.View;
@@ -91,92 +91,119 @@ import dagger.Lazy;
*/
public class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
- private static final String TAG = "NotifActivityStarter";
- protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Context mContext;
+ private final CommandQueue mCommandQueue;
+ private final Handler mMainThreadHandler;
+ private final Handler mBackgroundHandler;
+ private final Executor mUiBgExecutor;
+
+ private final NotificationEntryManager mEntryManager;
+ private final NotifPipeline mNotifPipeline;
+ private final NotifCollection mNotifCollection;
+ private final HeadsUpManagerPhone mHeadsUpManager;
+ private final ActivityStarter mActivityStarter;
+ private final IStatusBarService mBarService;
+ private final StatusBarStateController mStatusBarStateController;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final KeyguardManager mKeyguardManager;
+ private final IDreamManager mDreamManager;
+ private final BubbleController mBubbleController;
private final Lazy<AssistManager> mAssistManagerLazy;
- private final NotificationGroupManager mGroupManager;
- private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
private final NotificationRemoteInputManager mRemoteInputManager;
+ private final NotificationGroupManager mGroupManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final ShadeController mShadeController;
- private final StatusBar mStatusBar;
private final KeyguardStateController mKeyguardStateController;
- private final ActivityStarter mActivityStarter;
- private final NotificationEntryManager mEntryManager;
- private final NotifPipeline mNotifPipeline;
- private final NotifCollection mNotifCollection;
- private final FeatureFlags mFeatureFlags;
- private final StatusBarStateController mStatusBarStateController;
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+ private final LockPatternUtils mLockPatternUtils;
+ private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
+ private final ActivityIntentHelper mActivityIntentHelper;
+
+ private final FeatureFlags mFeatureFlags;
private final MetricsLogger mMetricsLogger;
- private final Context mContext;
- private final NotificationPanelViewController mNotificationPanel;
+ private final StatusBarNotificationActivityStarterLogger mLogger;
+
+ private final StatusBar mStatusBar;
private final NotificationPresenter mPresenter;
- private final LockPatternUtils mLockPatternUtils;
- private final HeadsUpManagerPhone mHeadsUpManager;
- private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private final KeyguardManager mKeyguardManager;
+ private final NotificationPanelViewController mNotificationPanel;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
- private final IStatusBarService mBarService;
- private final CommandQueue mCommandQueue;
- private final IDreamManager mDreamManager;
- private final Handler mMainThreadHandler;
- private final Handler mBackgroundHandler;
- private final ActivityIntentHelper mActivityIntentHelper;
- private final BubbleController mBubbleController;
- private final Executor mUiBgExecutor;
private boolean mIsCollapsingToShowActivityOverLockscreen;
- private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue,
- Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel,
- NotificationPresenter presenter, NotificationEntryManager entryManager,
- HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter,
- ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService,
+ private StatusBarNotificationActivityStarter(
+ Context context,
+ CommandQueue commandQueue,
+ Handler mainThreadHandler,
+ Handler backgroundHandler,
+ Executor uiBgExecutor,
+ NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
+ HeadsUpManagerPhone headsUpManager,
+ ActivityStarter activityStarter,
+ IStatusBarService statusBarService,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
- IDreamManager dreamManager, NotificationRemoteInputManager remoteInputManager,
- StatusBarRemoteInputCallback remoteInputCallback, NotificationGroupManager groupManager,
+ IDreamManager dreamManager,
+ BubbleController bubbleController,
+ Lazy<AssistManager> assistManagerLazy,
+ NotificationRemoteInputManager remoteInputManager,
+ NotificationGroupManager groupManager,
NotificationLockscreenUserManager lockscreenUserManager,
- ShadeController shadeController, StatusBar statusBar,
+ ShadeController shadeController,
KeyguardStateController keyguardStateController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
- Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
- ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
- FeatureFlags featureFlags, NotifPipeline notifPipeline,
- NotifCollection notifCollection) {
+ LockPatternUtils lockPatternUtils,
+ StatusBarRemoteInputCallback remoteInputCallback,
+ ActivityIntentHelper activityIntentHelper,
+
+ FeatureFlags featureFlags,
+ MetricsLogger metricsLogger,
+ StatusBarNotificationActivityStarterLogger logger,
+
+ StatusBar statusBar,
+ NotificationPresenter presenter,
+ NotificationPanelViewController panel,
+ ActivityLaunchAnimator activityLaunchAnimator) {
mContext = context;
- mNotificationPanel = panel;
- mPresenter = presenter;
+ mCommandQueue = commandQueue;
+ mMainThreadHandler = mainThreadHandler;
+ mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
+ mEntryManager = entryManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
mHeadsUpManager = headsUpManager;
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mActivityStarter = activityStarter;
mBarService = statusBarService;
- mCommandQueue = commandQueue;
+ mStatusBarStateController = statusBarStateController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
+ mBubbleController = bubbleController;
+ mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
+ mGroupManager = groupManager;
mLockscreenUserManager = lockscreenUserManager;
mShadeController = shadeController;
- // TODO: use KeyguardStateController#isOccluded to remove this dependency
- mStatusBar = statusBar;
mKeyguardStateController = keyguardStateController;
- mActivityStarter = activityStarter;
- mEntryManager = entryManager;
- mStatusBarStateController = statusBarStateController;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mMetricsLogger = metricsLogger;
- mAssistManagerLazy = assistManagerLazy;
- mGroupManager = groupManager;
mLockPatternUtils = lockPatternUtils;
- mBackgroundHandler = backgroundHandler;
- mUiBgExecutor = uiBgExecutor;
+ mStatusBarRemoteInputCallback = remoteInputCallback;
+ mActivityIntentHelper = activityIntentHelper;
+
mFeatureFlags = featureFlags;
- mNotifPipeline = notifPipeline;
- mNotifCollection = notifCollection;
+ mMetricsLogger = metricsLogger;
+ mLogger = logger;
+
+ // TODO: use KeyguardStateController#isOccluded to remove this dependency
+ mStatusBar = statusBar;
+ mPresenter = presenter;
+ mNotificationPanel = panel;
+ mActivityLaunchAnimator = activityLaunchAnimator;
+
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -192,11 +219,6 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
});
}
-
- mStatusBarRemoteInputCallback = remoteInputCallback;
- mMainThreadHandler = mainThreadHandler;
- mActivityIntentHelper = activityIntentHelper;
- mBubbleController = bubbleController;
}
/**
@@ -207,6 +229,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
*/
@Override
public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+ mLogger.logStartingActivityFromClick(sbn.getKey());
+
RemoteInputController controller = mRemoteInputManager.getController();
if (controller.isRemoteInputActive(row.getEntry())
&& !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
@@ -225,7 +249,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
// The only valid case is Bubble notifications. Guard against other cases
// entering here.
if (intent == null && !isBubble) {
- Log.e(TAG, "onNotificationClicked called for non-clickable notification!");
+ mLogger.logNonClickableNotification(sbn.getKey());
return;
}
@@ -258,6 +282,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent,
boolean wasOccluded,
boolean showOverLockscreen) {
+ mLogger.logHandleClickAfterKeyguardDismissed(sbn.getKey());
+
// TODO: Some of this code may be able to move to NotificationEntryManager.
if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(sbn.getKey())) {
// Release the HUN notification to the shade.
@@ -304,6 +330,8 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
boolean isActivityIntent,
boolean wasOccluded,
NotificationEntry parentToCancelFinal) {
+ mLogger.logHandleClickAfterPanelCollapsed(sbn.getKey());
+
String notificationKey = sbn.getKey();
try {
// The intent we are sending is for the application, which
@@ -343,9 +371,11 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
remoteInputText.toString());
}
if (isBubble) {
+ mLogger.logExpandingBubble(notificationKey);
expandBubbleStackOnMainThread(notificationKey);
} else {
- startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
+ startNotificationIntent(
+ intent, fillInIntent, entry, row, wasOccluded, isActivityIntent);
}
if (isActivityIntent || isBubble) {
mAssistManagerLazy.get().hideAssist();
@@ -392,10 +422,16 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
}
- private void startNotificationIntent(PendingIntent intent, Intent fillInIntent,
- View row, boolean wasOccluded, boolean isActivityIntent) {
+ private void startNotificationIntent(
+ PendingIntent intent,
+ Intent fillInIntent,
+ NotificationEntry entry,
+ View row,
+ boolean wasOccluded,
+ boolean isActivityIntent) {
RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
wasOccluded);
+ mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
if (adapter != null) {
ActivityTaskManager.getService()
@@ -408,7 +444,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
} catch (RemoteException | PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
- Log.w(TAG, "Sending contentIntent failed: " + e);
+ mLogger.logSendingIntentFailed(e);
// TODO: Dismiss Keyguard.
}
}
@@ -435,16 +471,35 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}, null, false /* afterKeyguardGone */);
}
+ @Override
+ public void startHistoryIntent(boolean showHistory) {
+ mActivityStarter.dismissKeyguardThenExecute(() -> {
+ AsyncTask.execute(() -> {
+ Intent intent = showHistory ? new Intent(
+ Settings.ACTION_NOTIFICATION_HISTORY) : new Intent(
+ Settings.ACTION_NOTIFICATION_SETTINGS);
+ TaskStackBuilder tsb = TaskStackBuilder.create(mContext)
+ .addNextIntent(new Intent(Settings.ACTION_NOTIFICATION_SETTINGS));
+ if (showHistory) {
+ tsb.addNextIntent(intent);
+ }
+ tsb.startActivities();
+ if (shouldCollapse()) {
+ // Putting it back on the main thread, since we're touching views
+ mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+ }
+ });
+ return true;
+ }, null, false /* afterKeyguardGone */);
+ }
+
private void handleFullScreenIntent(NotificationEntry entry) {
if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
if (shouldSuppressFullScreenIntent(entry)) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.getKey());
- }
+ mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey());
} else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) {
- Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.getKey());
- }
+ mLogger.logFullScreenIntentNotImportantEnough(entry.getKey());
} else {
// Stop screensaver if the notification has a fullscreen intent.
// (like an incoming phone call)
@@ -457,13 +512,13 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
});
// not immersive & a fullscreen alert should be shown
- if (DEBUG) {
- Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
- }
+ final PendingIntent fullscreenIntent =
+ entry.getSbn().getNotification().fullScreenIntent;
+ mLogger.logSendingFullScreenIntent(entry.getKey(), fullscreenIntent);
try {
EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
entry.getKey());
- entry.getSbn().getNotification().fullScreenIntent.send();
+ fullscreenIntent.send();
entry.notifyFullScreenIntentLaunched();
mMetricsLogger.count("note_fullscreen", 1);
} catch (PendingIntent.CanceledException e) {
@@ -578,9 +633,10 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
public static class Builder {
private final Context mContext;
private final CommandQueue mCommandQueue;
- private final Lazy<AssistManager> mAssistManagerLazy;
+ private final Handler mMainThreadHandler;
+ private final Handler mBackgroundHandler;
+ private final Executor mUiBgExecutor;
private final NotificationEntryManager mEntryManager;
- private final FeatureFlags mFeatureFlags;
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
private final HeadsUpManagerPhone mHeadsUpManager;
@@ -590,30 +646,37 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
+ private final BubbleController mBubbleController;
+ private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
- private final StatusBarRemoteInputCallback mRemoteInputCallback;
private final NotificationGroupManager mGroupManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
- private final MetricsLogger mMetricsLogger;
+ private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final LockPatternUtils mLockPatternUtils;
- private final Handler mMainThreadHandler;
- private final Handler mBackgroundHandler;
- private final Executor mUiBgExecutor;
+ private final StatusBarRemoteInputCallback mRemoteInputCallback;
private final ActivityIntentHelper mActivityIntentHelper;
- private final BubbleController mBubbleController;
- private NotificationPanelViewController mNotificationPanelViewController;
- private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
- private final ShadeController mShadeController;
+
+ private final FeatureFlags mFeatureFlags;
+ private final MetricsLogger mMetricsLogger;
+ private final StatusBarNotificationActivityStarterLogger mLogger;
+
+ private StatusBar mStatusBar;
private NotificationPresenter mNotificationPresenter;
+ private NotificationPanelViewController mNotificationPanelViewController;
private ActivityLaunchAnimator mActivityLaunchAnimator;
- private StatusBar mStatusBar;
@Inject
- public Builder(Context context,
+ public Builder(
+ Context context,
CommandQueue commandQueue,
- Lazy<AssistManager> assistManagerLazy,
+ @Main Handler mainThreadHandler,
+ @Background Handler backgroundHandler,
+ @UiBackground Executor uiBgExecutor,
NotificationEntryManager entryManager,
+ NotifPipeline notifPipeline,
+ NotifCollection notifCollection,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
IStatusBarService statusBarService,
@@ -621,27 +684,30 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
+ BubbleController bubbleController,
+ Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
- StatusBarRemoteInputCallback remoteInputCallback,
NotificationGroupManager groupManager,
NotificationLockscreenUserManager lockscreenUserManager,
+ ShadeController shadeController,
KeyguardStateController keyguardStateController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
- MetricsLogger metricsLogger,
LockPatternUtils lockPatternUtils,
- @Main Handler mainThreadHandler,
- @Background Handler backgroundHandler,
- @UiBackground Executor uiBgExecutor,
+ StatusBarRemoteInputCallback remoteInputCallback,
ActivityIntentHelper activityIntentHelper,
- BubbleController bubbleController,
- ShadeController shadeController,
+
FeatureFlags featureFlags,
- NotifPipeline notifPipeline,
- NotifCollection notifCollection) {
+ MetricsLogger metricsLogger,
+ StatusBarNotificationActivityStarterLogger logger) {
+
mContext = context;
mCommandQueue = commandQueue;
- mAssistManagerLazy = assistManagerLazy;
+ mMainThreadHandler = mainThreadHandler;
+ mBackgroundHandler = backgroundHandler;
+ mUiBgExecutor = uiBgExecutor;
mEntryManager = entryManager;
+ mNotifPipeline = notifPipeline;
+ mNotifCollection = notifCollection;
mHeadsUpManager = headsUpManager;
mActivityStarter = activityStarter;
mStatusBarService = statusBarService;
@@ -649,23 +715,21 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
+ mBubbleController = bubbleController;
+ mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
- mRemoteInputCallback = remoteInputCallback;
mGroupManager = groupManager;
mLockscreenUserManager = lockscreenUserManager;
+ mShadeController = shadeController;
mKeyguardStateController = keyguardStateController;
mNotificationInterruptStateProvider = notificationInterruptStateProvider;
- mMetricsLogger = metricsLogger;
mLockPatternUtils = lockPatternUtils;
- mMainThreadHandler = mainThreadHandler;
- mBackgroundHandler = backgroundHandler;
- mUiBgExecutor = uiBgExecutor;
+ mRemoteInputCallback = remoteInputCallback;
mActivityIntentHelper = activityIntentHelper;
- mBubbleController = bubbleController;
- mShadeController = shadeController;
+
mFeatureFlags = featureFlags;
- mNotifPipeline = notifPipeline;
- mNotifCollection = notifCollection;
+ mMetricsLogger = metricsLogger;
+ mLogger = logger;
}
/** Sets the status bar to use as {@link StatusBar}. */
@@ -692,37 +756,42 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
public StatusBarNotificationActivityStarter build() {
- return new StatusBarNotificationActivityStarter(mContext,
- mCommandQueue, mAssistManagerLazy,
- mNotificationPanelViewController,
- mNotificationPresenter,
+ return new StatusBarNotificationActivityStarter(
+ mContext,
+ mCommandQueue,
+ mMainThreadHandler,
+ mBackgroundHandler,
+ mUiBgExecutor,
mEntryManager,
+ mNotifPipeline,
+ mNotifCollection,
mHeadsUpManager,
mActivityStarter,
- mActivityLaunchAnimator,
mStatusBarService,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
mKeyguardManager,
mDreamManager,
+ mBubbleController,
+ mAssistManagerLazy,
mRemoteInputManager,
- mRemoteInputCallback,
mGroupManager,
mLockscreenUserManager,
mShadeController,
- mStatusBar,
mKeyguardStateController,
mNotificationInterruptStateProvider,
- mMetricsLogger,
mLockPatternUtils,
- mMainThreadHandler,
- mBackgroundHandler,
- mUiBgExecutor,
+ mRemoteInputCallback,
mActivityIntentHelper,
- mBubbleController,
+
mFeatureFlags,
- mNotifPipeline,
- mNotifCollection);
+ mMetricsLogger,
+ mLogger,
+
+ mStatusBar,
+ mNotificationPresenter,
+ mNotificationPanelViewController,
+ mActivityLaunchAnimator);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
new file mode 100644
index 000000000000..d118747a0365
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.app.PendingIntent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.NotifInteractionLog
+import javax.inject.Inject
+
+class StatusBarNotificationActivityStarterLogger @Inject constructor(
+ @NotifInteractionLog private val buffer: LogBuffer
+) {
+ fun logStartingActivityFromClick(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "(1/4) onNotificationClicked: $str1"
+ })
+ }
+
+ fun logHandleClickAfterKeyguardDismissed(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1"
+ })
+ }
+
+ fun logHandleClickAfterPanelCollapsed(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "(3/4) handleNotificationClickAfterPanelCollapsed: $str1"
+ })
+ }
+
+ fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = pendingIntent.intent.toString()
+ }, {
+ "(4/4) Starting $str2 for notification $str1"
+ })
+ }
+
+ fun logExpandingBubble(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "Expanding bubble for $str1 (rather than firing intent)"
+ })
+ }
+
+ fun logSendingIntentFailed(e: Exception) {
+ buffer.log(TAG, WARNING, {
+ str1 = e.toString()
+ }, {
+ "Sending contentIntentFailed: $str1"
+ })
+ }
+
+ fun logNonClickableNotification(key: String) {
+ buffer.log(TAG, ERROR, {
+ str1 = key
+ }, {
+ "onNotificationClicked called for non-clickable notification! $str1"
+ })
+ }
+
+ fun logFullScreenIntentSuppressedByDnD(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "No Fullscreen intent: suppressed by DND: $str1"
+ })
+ }
+
+ fun logFullScreenIntentNotImportantEnough(key: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = key
+ }, {
+ "No Fullscreen intent: not important enough: $str1"
+ })
+ }
+
+ fun logSendingFullScreenIntent(key: String, pendingIntent: PendingIntent) {
+ buffer.log(TAG, INFO, {
+ str1 = key
+ str2 = pendingIntent.intent.toString()
+ }, {
+ "Notification $str1 has fullScreenIntent; sending fullScreenIntent $str2"
+ })
+ }
+}
+
+private const val TAG = "NotifActivityStarter"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 6193a8e9005b..428de9e9adbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -30,6 +30,7 @@ import android.content.IntentSender;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.util.Log;
import android.view.View;
import android.view.ViewParent;
@@ -47,6 +48,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import java.util.concurrent.atomic.AtomicReference;
+
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -54,7 +57,8 @@ import javax.inject.Singleton;
*/
@Singleton
public class StatusBarRemoteInputCallback implements Callback, Callbacks,
- StatusBarStateController.StateListener {
+ StatusBarStateController.StateListener, KeyguardStateController.Callback {
+ private static final String TAG = StatusBarRemoteInputCallback.class.getSimpleName();
private final KeyguardStateController mKeyguardStateController;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -72,6 +76,7 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
private int mDisabled2;
protected BroadcastReceiver mChallengeReceiver = new ChallengeReceiver();
private Handler mMainHandler = new Handler();
+ private final AtomicReference<Intent> mPendingConfirmCredentialIntent = new AtomicReference();
/**
*/
@@ -98,6 +103,9 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
mCommandQueue.addCallback(this);
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mGroupManager = groupManager;
+ // Listen to onKeyguardShowingChanged in case a managed profile needs to be unlocked
+ // once the primary profile's keyguard is no longer shown.
+ mKeyguardStateController.addCallback(this);
}
@Override
@@ -201,12 +209,39 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
// Clear pending remote view, as we do not want to trigger pending remote input view when
// it's called by other code
mPendingWorkRemoteInputView = null;
- // Begin old BaseStatusBar.startWorkChallengeIfNecessary.
+
+ final Intent newIntent = createConfirmDeviceCredentialIntent(
+ userId, intendSender, notificationKey);
+ if (newIntent == null) {
+ Log.w(TAG, String.format("Cannot create intent to unlock user %d", userId));
+ return false;
+ }
+
+ mPendingConfirmCredentialIntent.set(newIntent);
+
+ // If the Keyguard is currently showing, starting the ConfirmDeviceCredentialActivity
+ // would cause it to pause, not letting the user actually unlock the managed profile.
+ // Instead, wait until we receive a callback indicating it is no longer showing and
+ // then start the pending intent.
+ if (mKeyguardStateController.isShowing()) {
+ // Do nothing, since the callback will get the pending intent and start it.
+ Log.w(TAG, String.format("Keyguard is showing, waiting until it's not"));
+ } else {
+ startPendingConfirmDeviceCredentialIntent();
+ }
+
+ return true;
+ }
+
+ private Intent createConfirmDeviceCredentialIntent(
+ int userId, IntentSender intendSender, String notificationKey) {
final Intent newIntent = mKeyguardManager.createConfirmDeviceCredentialIntent(null,
null, userId);
+
if (newIntent == null) {
- return false;
+ return null;
}
+
final Intent callBackIntent = new Intent(NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION);
callBackIntent.putExtra(Intent.EXTRA_INTENT, intendSender);
callBackIntent.putExtra(Intent.EXTRA_INDEX, notificationKey);
@@ -222,14 +257,40 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
newIntent.putExtra(
Intent.EXTRA_INTENT,
callBackPendingIntent.getIntentSender());
+
+ return newIntent;
+ }
+
+ private void startPendingConfirmDeviceCredentialIntent() {
+ final Intent pendingIntent = mPendingConfirmCredentialIntent.getAndSet(null);
+ if (pendingIntent == null) {
+ return;
+ }
+
try {
- ActivityManager.getService().startConfirmDeviceCredentialIntent(newIntent,
+ if (mKeyguardStateController.isShowing()) {
+ Log.w(TAG, "Keyguard is showing while starting confirm device credential intent.");
+ }
+ ActivityManager.getService().startConfirmDeviceCredentialIntent(pendingIntent,
null /*options*/);
} catch (RemoteException ex) {
// ignore
}
- return true;
- // End old BaseStatusBar.startWorkChallengeIfNecessary.
+ }
+
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mKeyguardStateController.isShowing()) {
+ // In order to avoid jarring UX where/ the managed profile challenge is shown and
+ // immediately dismissed, do not attempt to start the confirm device credential
+ // activity if the keyguard is still showing.
+ if (mPendingConfirmCredentialIntent.get() != null) {
+ Log.w(TAG, "There's a pending unlock intent but keyguard is still showing, abort.");
+ }
+ return;
+ }
+
+ startPendingConfirmDeviceCredentialIntent();
}
@Override
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 b81a5198b498..62a3cf040d7e 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
@@ -33,6 +33,7 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -62,7 +63,6 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.BypassHeadsUpNotifier;
-import com.android.systemui.statusbar.notification.interruption.NotificationAlertingManager;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -143,10 +143,10 @@ public interface StatusBarPhoneModule {
NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationViewHierarchyManager notificationViewHierarchyManager,
KeyguardViewMediator keyguardViewMediator,
- NotificationAlertingManager notificationAlertingManager,
DisplayMetrics displayMetrics,
MetricsLogger metricsLogger,
@UiBackground Executor uiBgExecutor,
+ @Main Executor mainExecutor,
NotificationMediaManager notificationMediaManager,
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
@@ -223,10 +223,10 @@ public interface StatusBarPhoneModule {
notificationInterruptStateProvider,
notificationViewHierarchyManager,
keyguardViewMediator,
- notificationAlertingManager,
displayMetrics,
metricsLogger,
uiBgExecutor,
+ mainExecutor,
notificationMediaManager,
lockScreenUserManager,
remoteInputManager,
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 17cd98ff7f2c..e65b6fe7c3f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -197,8 +197,13 @@ public class MobileSignalController extends SignalController<
TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EHRPD),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+ if (mConfig.show4gFor3g) {
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+ TelephonyIcons.FOUR_G);
+ } else {
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
TelephonyIcons.THREE_G);
+ }
mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_TD_SCDMA),
TelephonyIcons.THREE_G);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 6e5f8a0ae5e9..a284335c972e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -357,7 +357,18 @@ public class NetworkControllerImpl extends BroadcastReceiver
mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
+ // Initial setup of connectivity. Handled as if we had received a sticky broadcast of
+ // ConnectivityManager.CONNECTIVITY_ACTION or ConnectivityManager.INET_CONDITION_ACTION.
+ mReceiverHandler.post(this::updateConnectivity);
+
+ // Initial setup of WifiSignalController. Handled as if we had received a sticky broadcast
+ // of WifiManager.WIFI_STATE_CHANGED_ACTION or WifiManager.NETWORK_STATE_CHANGED_ACTION
+ mReceiverHandler.post(mWifiSignalController::fetchInitialState);
updateMobileControllers();
+
+ // Initial setup of emergency information. Handled as if we had received a sticky broadcast
+ // of TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED.
+ mReceiverHandler.post(this::recalculateEmergency);
}
private void unregisterListeners() {
@@ -367,7 +378,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mobileSignalController.unregisterListener();
}
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
- mContext.unregisterReceiver(this);
+ mBroadcastDispatcher.unregisterReceiver(this);
}
public int getConnectedWifiLevel() {
@@ -859,6 +870,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
pw.println(" - telephony ------");
pw.print(" hasVoiceCallingFeature()=");
pw.println(hasVoiceCallingFeature());
+ pw.println(" mListening=" + mListening);
pw.println(" - connectivity ------");
pw.print(" mConnectedTransports=");
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 408b3a619ff1..53ac65700a05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -57,7 +57,6 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
-import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
@@ -656,9 +655,10 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
- String[] allowedDataTypes = mRemoteInputView.mRemoteInput.getAllowedDataTypes()
- .toArray(new String[0]);
- EditorInfoCompat.setContentMimeTypes(outAttrs, allowedDataTypes);
+ // 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 =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index bb0b5e00ff67..412962cc797a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -48,6 +48,7 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.UserIcons;
import com.android.settingslib.RestrictedLockUtilsInternal;
@@ -61,6 +62,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.qs.QSUserSwitcherEvent;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -108,13 +110,15 @@ public class UserSwitcherController implements Dumpable {
private int mSecondaryUser = UserHandle.USER_NULL;
private Intent mSecondaryUserServiceIntent;
private SparseBooleanArray mForcePictureLoadForUserId = new SparseBooleanArray(2);
+ private final UiEventLogger mUiEventLogger;
@Inject
public UserSwitcherController(Context context, KeyguardStateController keyguardStateController,
@Main Handler handler, ActivityStarter activityStarter,
- BroadcastDispatcher broadcastDispatcher) {
+ BroadcastDispatcher broadcastDispatcher, UiEventLogger uiEventLogger) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
+ mUiEventLogger = uiEventLogger;
if (!UserManager.isGuestUserEphemeral()) {
mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
}
@@ -801,7 +805,7 @@ public class UserSwitcherController implements Dumpable {
UserDetailView v;
if (!(convertView instanceof UserDetailView)) {
v = UserDetailView.inflate(context, parent, false);
- v.createAndSetAdapter(UserSwitcherController.this);
+ v.createAndSetAdapter(UserSwitcherController.this, mUiEventLogger);
} else {
v = (UserDetailView) convertView;
}
@@ -827,6 +831,21 @@ public class UserSwitcherController implements Dumpable {
public int getMetricsCategory() {
return MetricsEvent.QS_USERDETAIL;
}
+
+ @Override
+ public UiEventLogger.UiEventEnum openDetailEvent() {
+ return QSUserSwitcherEvent.QS_USER_DETAIL_OPEN;
+ }
+
+ @Override
+ public UiEventLogger.UiEventEnum closeDetailEvent() {
+ return QSUserSwitcherEvent.QS_USER_DETAIL_CLOSE;
+ }
+
+ @Override
+ public UiEventLogger.UiEventEnum moreSettingsEvent() {
+ return QSUserSwitcherEvent.QS_USER_MORE_SETTINGS;
+ }
};
private final KeyguardStateController.Callback mCallback =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index b258fd47871a..5257ce4c6bd9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -102,6 +102,20 @@ public class WifiSignalController extends
}
/**
+ * Fetches wifi initial state replacing the initial sticky broadcast.
+ */
+ public void fetchInitialState() {
+ mWifiTracker.fetchInitialState();
+ mCurrentState.enabled = mWifiTracker.enabled;
+ mCurrentState.connected = mWifiTracker.connected;
+ mCurrentState.ssid = mWifiTracker.ssid;
+ mCurrentState.rssi = mWifiTracker.rssi;
+ mCurrentState.level = mWifiTracker.level;
+ mCurrentState.statusLabel = mWifiTracker.statusLabel;
+ notifyListenersIfNecessary();
+ }
+
+ /**
* Extract wifi state directly from broadcasts about changes in wifi state.
*/
public void handleBroadcast(Intent intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
index ca4b67db0d46..242f7cde9d3b 100644
--- a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -187,16 +187,23 @@ class FloatingContentCoordinator @Inject constructor() {
// Tell that content to get out of the way, and save the bounds it says it's moving
// (or animating) to.
.forEach { (content, bounds) ->
- content.moveToBounds(
- content.calculateNewBoundsOnOverlap(
- conflictingNewBounds,
- // Pass all of the content bounds except the bounds of the
- // content we're asking to move, and the conflicting new bounds
- // (since those are passed separately).
- otherContentBounds = allContentBounds.values
- .minus(bounds)
- .minus(conflictingNewBounds)))
- allContentBounds[content] = content.getFloatingBoundsOnScreen()
+ val newBounds = content.calculateNewBoundsOnOverlap(
+ conflictingNewBounds,
+ // Pass all of the content bounds except the bounds of the
+ // content we're asking to move, and the conflicting new bounds
+ // (since those are passed separately).
+ otherContentBounds = allContentBounds.values
+ .minus(bounds)
+ .minus(conflictingNewBounds))
+
+ // If the new bounds are empty, it means there's no non-overlapping position
+ // that is in bounds. Just leave the content where it is. This should normally
+ // not happen, but sometimes content like PIP reports incorrect bounds
+ // temporarily.
+ if (!newBounds.isEmpty) {
+ content.moveToBounds(newBounds)
+ allContentBounds[content] = content.getFloatingBoundsOnScreen()
+ }
}
currentlyResolvingConflicts = false
@@ -229,8 +236,8 @@ class FloatingContentCoordinator @Inject constructor() {
* @param allowedBounds The area within which we're allowed to find new bounds for the
* content.
* @return New bounds for the content that don't intersect the exclusion rects or the
- * newly overlapping rect, and that is within bounds unless no possible in-bounds position
- * exists.
+ * newly overlapping rect, and that is within bounds - or an empty Rect if no in-bounds
+ * position exists.
*/
@JvmStatic
fun findAreaForContentVertically(
@@ -274,7 +281,13 @@ class FloatingContentCoordinator @Inject constructor() {
!overlappingContentPushingDown && !positionAboveInBounds
// Return the content rect, but offset to reflect the new position.
- return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove
+ val newBounds = if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove
+
+ // If the new bounds are within the allowed bounds, return them. If not, it means that
+ // there are no legal new bounds. This can happen if the new content's bounds are too
+ // large (for example, full-screen PIP). Since there is no reasonable action to take
+ // here, return an empty Rect and we will just not move the content.
+ return if (allowedBounds.contains(newBounds)) newBounds else Rect()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
index d65b285adb0c..8880df9959c1 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -115,7 +115,9 @@ abstract class RelativeTouchListener : View.OnTouchListener {
performedLongClick = false
handler.postDelayed({
- performedLongClick = v.performLongClick()
+ if (v.isLongClickable) {
+ performedLongClick = v.performLongClick()
+ }
}, ViewConfiguration.getLongPressTimeout().toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index 8625d63a3c7e..db08d64acc10 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -61,14 +61,17 @@ internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
/**
* Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided.
+ * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
*/
-private val defaultSpring = PhysicsAnimator.SpringConfig(
+private val globalDefaultSpring = PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_MEDIUM,
SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
-/** Default fling configuration to use for animations where friction was not provided. */
-private val defaultFling = PhysicsAnimator.FlingConfig(
+/**
+ * Default fling configuration to use for animations where friction was not provided, and a default
+ * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig].
+ */
+private val globalDefaultFling = PhysicsAnimator.FlingConfig(
friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
/** Whether to log helpful debug information about animations. */
@@ -111,6 +114,12 @@ class PhysicsAnimator<T> private constructor (val target: T) {
/** End actions to run when all animations have completed. */
private val endActions = ArrayList<EndAction>()
+ /** SpringConfig to use by default for properties whose springs were not provided. */
+ private var defaultSpring: SpringConfig = globalDefaultSpring
+
+ /** FlingConfig to use by default for properties whose fling configs were not provided. */
+ private var defaultFling: FlingConfig = globalDefaultFling
+
/**
* Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
* the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
@@ -204,6 +213,19 @@ class PhysicsAnimator<T> private constructor (val target: T) {
}
/**
+ * Springs a property to a given value using the provided configuration options, and a start
+ * velocity of 0f.
+ *
+ * @see spring
+ */
+ fun spring(
+ property: FloatPropertyCompat<in T>,
+ toPosition: Float
+ ): PhysicsAnimator<T> {
+ return spring(property, toPosition, 0f)
+ }
+
+ /**
* Flings a property using the given start velocity, using a [FlingAnimation] configured using
* the provided configuration settings.
*
@@ -392,6 +414,14 @@ class PhysicsAnimator<T> private constructor (val target: T) {
return this
}
+ fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
+ this.defaultSpring = defaultSpring
+ }
+
+ fun setDefaultFlingConfig(defaultFling: FlingConfig) {
+ this.defaultFling = defaultFling
+ }
+
/** Starts the animations! */
fun start() {
startAction()
@@ -752,7 +782,7 @@ class PhysicsAnimator<T> private constructor (val target: T) {
) {
constructor() :
- this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+ this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
@@ -782,10 +812,10 @@ class PhysicsAnimator<T> private constructor (val target: T) {
internal var startVelocity: Float
) {
- constructor() : this(defaultFling.friction)
+ constructor() : this(globalDefaultFling.friction)
constructor(friction: Float) :
- this(friction, defaultFling.min, defaultFling.max)
+ this(friction, globalDefaultFling.min, globalDefaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
this(friction, min, max, startVelocity = 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index cc6d607a60cf..8acfbf2b6996 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -137,6 +137,36 @@ public abstract class ConcurrencyModule {
}
/**
+ * Provide a Background-Thread Executor by default.
+ */
+ @Provides
+ @Singleton
+ public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
+ return new RepeatableExecutorImpl(exec);
+ }
+
+ /**
+ * Provide a Background-Thread Executor.
+ */
+ @Provides
+ @Singleton
+ @Background
+ public static RepeatableExecutor provideBackgroundRepeatableExecutor(
+ @Background DelayableExecutor exec) {
+ return new RepeatableExecutorImpl(exec);
+ }
+
+ /**
+ * Provide a Main-Thread Executor.
+ */
+ @Provides
+ @Singleton
+ @Main
+ public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
+ return new RepeatableExecutorImpl(exec);
+ }
+
+ /**
* Provide an Executor specifically for running UI operations on a separate thread.
*
* Keep submitted runnables short and to the point, just as with any other UI code.
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java
new file mode 100644
index 000000000000..aefdc992e831
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A sub-class of {@link Executor} that allows scheduling commands to execute periodically.
+ */
+public interface RepeatableExecutor extends Executor {
+
+ /**
+ * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with
+ * the given delay between the termination of one execution and the commencement of the next.
+ *
+ * Each invocation of the supplied Runnable will be scheduled after the previous invocation
+ * completes. For example, if you schedule the Runnable with a 60 second delay, and the Runnable
+ * itself takes 1 second, the effective delay will be 61 seconds between each invocation.
+ *
+ * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable,
+ * long, long)}
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ default Runnable executeRepeatedly(Runnable r, long initialDelayMillis, long delayMillis) {
+ return executeRepeatedly(r, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with
+ * the given delay between the termination of one execution and the commencement of the next..
+ *
+ * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable,
+ * long, long)}
+ *
+ * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+ */
+ Runnable executeRepeatedly(Runnable r, long initialDelay, long delay, TimeUnit unit);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java
new file mode 100644
index 000000000000..c03e10e5c981
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.concurrency;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link RepeatableExecutor} for SystemUI.
+ */
+class RepeatableExecutorImpl implements RepeatableExecutor {
+
+ private final DelayableExecutor mExecutor;
+
+ RepeatableExecutorImpl(DelayableExecutor executor) {
+ mExecutor = executor;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ mExecutor.execute(command);
+ }
+
+ @Override
+ public Runnable executeRepeatedly(Runnable r, long initDelay, long delay, TimeUnit unit) {
+ ExecutionToken token = new ExecutionToken(r, delay, unit);
+ token.start(initDelay, unit);
+ return token::cancel;
+ }
+
+ private class ExecutionToken implements Runnable {
+ private final Runnable mCommand;
+ private final long mDelay;
+ private final TimeUnit mUnit;
+ private final Object mLock = new Object();
+ private Runnable mCancel;
+
+ ExecutionToken(Runnable r, long delay, TimeUnit unit) {
+ mCommand = r;
+ mDelay = delay;
+ mUnit = unit;
+ }
+
+ @Override
+ public void run() {
+ mCommand.run();
+ synchronized (mLock) {
+ if (mCancel != null) {
+ mCancel = mExecutor.executeDelayed(this, mDelay, mUnit);
+ }
+ }
+ }
+
+ /** Starts execution that will repeat the command until {@link cancel}. */
+ public void start(long startDelay, TimeUnit unit) {
+ synchronized (mLock) {
+ mCancel = mExecutor.executeDelayed(this, startDelay, unit);
+ }
+ }
+
+ /** Cancel repeated execution of command. */
+ public void cancel() {
+ synchronized (mLock) {
+ if (mCancel != null) {
+ mCancel.run();
+ mCancel = null;
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
index f27bdbfbeda0..e905e6772074 100644
--- a/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/magnetictarget/MagnetizedObject.kt
@@ -27,6 +27,7 @@ import android.provider.Settings
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
+import android.view.ViewConfiguration
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringForce
@@ -146,6 +147,10 @@ abstract class MagnetizedObject<T : Any>(
private val velocityTracker: VelocityTracker = VelocityTracker.obtain()
private val vibrator: Vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
+ private var touchDown = PointF()
+ private var touchSlop = 0
+ private var movedBeyondSlop = false
+
/** Whether touch events are presently occurring within the magnetic field area of a target. */
val objectStuckToTarget: Boolean
get() = targetObjectIsStuckTo != null
@@ -324,15 +329,32 @@ abstract class MagnetizedObject<T : Any>(
// When a gesture begins, recalculate target views' positions on the screen in case they
// have changed. Also, clear state.
if (ev.action == MotionEvent.ACTION_DOWN) {
- updateTargetViewLocations()
+ updateTargetViews()
- // Clear the velocity tracker and assume we're not stuck to a target yet.
+ // Clear the velocity tracker and stuck target.
velocityTracker.clear()
targetObjectIsStuckTo = null
+
+ // Set the touch down coordinates and reset movedBeyondSlop.
+ touchDown.set(ev.rawX, ev.rawY)
+ movedBeyondSlop = false
}
+ // Always pass events to the VelocityTracker.
addMovement(ev)
+ // If we haven't yet moved beyond the slop distance, check if we have.
+ if (!movedBeyondSlop) {
+ val dragDistance = hypot(ev.rawX - touchDown.x, ev.rawY - touchDown.y)
+ if (dragDistance > touchSlop) {
+ // If we're beyond the slop distance, save that and continue.
+ movedBeyondSlop = true
+ } else {
+ // Otherwise, don't do anything yet.
+ return false
+ }
+ }
+
val targetObjectIsInMagneticFieldOf = associatedTargets.firstOrNull { target ->
val distanceFromTargetCenter = hypot(
ev.rawX - target.centerOnScreen.x,
@@ -559,8 +581,14 @@ abstract class MagnetizedObject<T : Any>(
}
/** Updates the locations on screen of all of the [associatedTargets]. */
- internal fun updateTargetViewLocations() {
+ internal fun updateTargetViews() {
associatedTargets.forEach { it.updateLocationOnScreen() }
+
+ // Update the touch slop, since the configuration may have changed.
+ if (associatedTargets.size > 0) {
+ touchSlop =
+ ViewConfiguration.get(associatedTargets[0].targetView.context).scaledTouchSlop
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 7bb987ca7cf0..0cd4fb9578ff 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -56,9 +56,12 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDndEvent;
+import com.android.systemui.qs.QSEvents;
import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.FileDescriptor;
@@ -103,6 +106,7 @@ public class ZenModePanel extends FrameLayout {
private final TransitionHelper mTransitionHelper = new TransitionHelper();
private final Uri mForeverId;
private final ConfigurableTexts mConfigurableTexts;
+ private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
private String mTag = TAG + "/" + Integer.toHexString(System.identityHashCode(this));
@@ -662,6 +666,7 @@ public class ZenModePanel extends FrameLayout {
tag.rb.setChecked(true);
if (DEBUG) Log.d(mTag, "onCheckedChanged " + conditionId);
MetricsLogger.action(mContext, MetricsEvent.QS_DND_CONDITION_SELECT);
+ mUiEventLogger.log(QSDndEvent.QS_DND_CONDITION_SELECT);
select(tag.condition);
announceConditionSelection(tag);
}
@@ -767,6 +772,7 @@ public class ZenModePanel extends FrameLayout {
private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
MetricsLogger.action(mContext, MetricsEvent.QS_DND_TIME, up);
+ mUiEventLogger.log(up ? QSDndEvent.QS_DND_TIME_UP : QSDndEvent.QS_DND_TIME_DOWN);
Condition newCondition = null;
final int N = MINUTE_BUCKETS.length;
if (mBucketIndex == -1) {
diff --git a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
index e5da603321cd..0b6e4b2ab598 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/SystemWindows.java
@@ -32,6 +32,7 @@ import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DragEvent;
+import android.view.IScrollCaptureController;
import android.view.IWindow;
import android.view.IWindowManager;
import android.view.IWindowSession;
@@ -200,6 +201,14 @@ public class SystemWindows {
attrs.flags |= FLAG_HARDWARE_ACCELERATED;
viewRoot.setView(view, attrs);
mViewRoots.put(view, viewRoot);
+
+ try {
+ mWmService.setShellRootAccessibilityWindow(mDisplayId, windowType,
+ viewRoot.getWindowToken());
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error setting accessibility window for " + mDisplayId + ":"
+ + windowType, e);
+ }
}
SysUiWindowManager addRoot(int windowType) {
@@ -352,5 +361,14 @@ public class SystemWindows {
@Override
public void dispatchPointerCaptureChanged(boolean hasCapture) {}
+
+ @Override
+ public void requestScrollCapture(IScrollCaptureController controller) {
+ try {
+ controller.onClientUnavailable();
+ } catch (RemoteException ex) {
+ // ignore
+ }
+ }
}
}